@onjmin/koe 1.0.3 → 1.0.5
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/dist/index.d.ts +166 -112
- package/dist/index.js +1240 -606
- package/dist/index.js.map +1 -1
- package/dist/koe-convert.js +182 -154
- package/dist/koe-worklet.js +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -1,669 +1,1302 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
view.
|
|
9
|
-
return
|
|
1
|
+
// src/converter/frq.ts
|
|
2
|
+
function parseFrqAverageF0(buffer) {
|
|
3
|
+
if (buffer.byteLength < 20) return null;
|
|
4
|
+
const view = new DataView(buffer);
|
|
5
|
+
let header = "";
|
|
6
|
+
for (let i = 0; i < 8; i++) header += String.fromCharCode(view.getUint8(i));
|
|
7
|
+
if (header !== "FREQ0003") return null;
|
|
8
|
+
const avg = view.getFloat64(12, true);
|
|
9
|
+
return Number.isFinite(avg) && avg > 0 ? avg : null;
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
return { jsonLength: view.getUint32(4, true) };
|
|
11
|
+
function frqFileName(wavName) {
|
|
12
|
+
const dot = wavName.lastIndexOf(".");
|
|
13
|
+
const base = dot >= 0 ? wavName.slice(0, dot) : wavName;
|
|
14
|
+
const ext = dot >= 0 ? wavName.slice(dot + 1) : "wav";
|
|
15
|
+
return `${base}_${ext}.frq`;
|
|
17
16
|
}
|
|
18
|
-
var pcmBase = (jsonLength) => 8 + jsonLength;
|
|
19
17
|
|
|
20
|
-
// src/
|
|
21
|
-
var
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return this.blob.slice(start, start + length).arrayBuffer();
|
|
31
|
-
}
|
|
18
|
+
// src/converter/pitch.ts
|
|
19
|
+
var SAMPLE_RATE = 48e3;
|
|
20
|
+
var NAME_SEMITONE = {
|
|
21
|
+
c: 0,
|
|
22
|
+
d: 2,
|
|
23
|
+
e: 4,
|
|
24
|
+
f: 5,
|
|
25
|
+
g: 7,
|
|
26
|
+
a: 9,
|
|
27
|
+
b: 11
|
|
32
28
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
function noteNameToHz(name) {
|
|
30
|
+
const m = /^([A-Ga-g])([#b]?)(-?\d+)$/.exec(name);
|
|
31
|
+
if (!m) return null;
|
|
32
|
+
let semi = NAME_SEMITONE[m[1].toLowerCase()];
|
|
33
|
+
if (m[2] === "#") semi++;
|
|
34
|
+
else if (m[2] === "b") semi--;
|
|
35
|
+
const midi = (parseInt(m[3], 10) + 1) * 12 + semi;
|
|
36
|
+
return 440 * 2 ** ((midi - 69) / 12);
|
|
37
|
+
}
|
|
38
|
+
function pitchFromAliasSuffix(alias) {
|
|
39
|
+
const m = /_([A-Ga-g][#b]?-?\d+)$/.exec(alias);
|
|
40
|
+
return m ? noteNameToHz(m[1]) : null;
|
|
41
|
+
}
|
|
42
|
+
function detectF0(pcm, start, end) {
|
|
43
|
+
const DECIM = 4;
|
|
44
|
+
const sr = SAMPLE_RATE / DECIM;
|
|
45
|
+
const minLag = Math.floor(sr / 700);
|
|
46
|
+
const maxLag = Math.floor(sr / 70);
|
|
47
|
+
const outLen = Math.floor((end - start) / DECIM);
|
|
48
|
+
if (outLen < maxLag + 2) return 0;
|
|
49
|
+
const win = Math.min(outLen, 1500);
|
|
50
|
+
const buf = new Float32Array(win);
|
|
51
|
+
let mean = 0;
|
|
52
|
+
for (let i = 0; i < win; i++) {
|
|
53
|
+
let s = 0;
|
|
54
|
+
const base = start + i * DECIM;
|
|
55
|
+
for (let j = 0; j < DECIM; j++) s += pcm[base + j];
|
|
56
|
+
buf[i] = s;
|
|
57
|
+
mean += s;
|
|
37
58
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
mean /= win;
|
|
60
|
+
const sq = new Float64Array(win + 1);
|
|
61
|
+
for (let i = 0; i < win; i++) {
|
|
62
|
+
buf[i] -= mean;
|
|
63
|
+
sq[i + 1] = sq[i] + buf[i] * buf[i];
|
|
64
|
+
}
|
|
65
|
+
if (sq[win] < 1) return 0;
|
|
66
|
+
const norm = (lag) => {
|
|
67
|
+
const n = win - lag;
|
|
68
|
+
let r = 0;
|
|
69
|
+
for (let i = 0; i < n; i++) r += buf[i] * buf[i + lag];
|
|
70
|
+
const e = sq[n] + (sq[lag + n] - sq[lag]);
|
|
71
|
+
return e > 0 ? 2 * r / e : 0;
|
|
72
|
+
};
|
|
73
|
+
let bestLag = -1;
|
|
74
|
+
let best = 0;
|
|
75
|
+
for (let lag = minLag; lag <= maxLag; lag++) {
|
|
76
|
+
const v = norm(lag);
|
|
77
|
+
if (v > best) {
|
|
78
|
+
best = v;
|
|
79
|
+
bestLag = lag;
|
|
47
80
|
}
|
|
48
|
-
return res.arrayBuffer();
|
|
49
81
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return res.arrayBuffer();
|
|
82
|
+
if (bestLag < 1 || best < 0.4) return 0;
|
|
83
|
+
const y0 = norm(bestLag - 1);
|
|
84
|
+
const y1 = best;
|
|
85
|
+
const y2 = norm(bestLag + 1);
|
|
86
|
+
const denom = y0 - 2 * y1 + y2;
|
|
87
|
+
const shift = denom !== 0 ? 0.5 * (y0 - y2) / denom : 0;
|
|
88
|
+
return sr / (bestLag + shift);
|
|
58
89
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
90
|
+
|
|
91
|
+
// src/converter/pack.ts
|
|
92
|
+
var TARGET_RATE = 48e3;
|
|
93
|
+
function msToSamples(ms) {
|
|
94
|
+
return Math.round(ms / 1e3 * TARGET_RATE);
|
|
95
|
+
}
|
|
96
|
+
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
97
|
+
function trimToOto(pcm, oto, recordedPitch = 0) {
|
|
98
|
+
const full = pcm.length;
|
|
99
|
+
const start = clamp(msToSamples(oto.offset), 0, full);
|
|
100
|
+
const end = oto.cutoff < 0 ? clamp(start + msToSamples(-oto.cutoff), start, full) : clamp(full - msToSamples(oto.cutoff), start, full);
|
|
101
|
+
const slice = pcm.subarray(start, end);
|
|
102
|
+
const length = slice.length;
|
|
103
|
+
const pre = clamp(msToSamples(oto.pre), 0, length);
|
|
104
|
+
const overlap = clamp(msToSamples(oto.overlap), 0, length);
|
|
105
|
+
const consonant = clamp(msToSamples(oto.consonant), 0, length);
|
|
106
|
+
const pitch = recordedPitch > 0 ? recordedPitch : detectF0(
|
|
107
|
+
slice,
|
|
108
|
+
Math.min(Math.max(pre, consonant), Math.max(0, length - 1)),
|
|
109
|
+
length
|
|
110
|
+
);
|
|
111
|
+
return {
|
|
112
|
+
pcm: slice,
|
|
113
|
+
entry: { length, pre, overlap, consonant, pitch }
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function pack(inputs, referencePitch = 220) {
|
|
117
|
+
const phonemes = {};
|
|
118
|
+
const chunks = [];
|
|
119
|
+
let byteOffset = 0;
|
|
120
|
+
for (const { oto, pcm, recordedPitch } of inputs) {
|
|
121
|
+
const { pcm: slice, entry } = trimToOto(pcm, oto, recordedPitch);
|
|
122
|
+
if (slice.length === 0) continue;
|
|
123
|
+
phonemes[oto.alias] = { offset: byteOffset, ...entry };
|
|
124
|
+
byteOffset += slice.byteLength;
|
|
125
|
+
chunks.push(slice);
|
|
63
126
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (typeof koe === "string") {
|
|
72
|
-
const header2 = await rangeFetch(koe, 0, 8);
|
|
73
|
-
const { jsonLength: jsonLength2 } = parseKoeHeader(header2);
|
|
74
|
-
const json2 = await rangeFetch(koe, 8, jsonLength2);
|
|
75
|
-
const manifest2 = JSON.parse(new TextDecoder().decode(json2));
|
|
76
|
-
return new _VoiceBank(
|
|
77
|
-
manifest2,
|
|
78
|
-
new RangeVoiceSource(koe, pcmBase(jsonLength2))
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
const header = await koe.slice(0, 8).arrayBuffer();
|
|
82
|
-
const { jsonLength } = parseKoeHeader(header);
|
|
83
|
-
const json = await koe.slice(8, 8 + jsonLength).arrayBuffer();
|
|
84
|
-
const manifest = JSON.parse(new TextDecoder().decode(json));
|
|
85
|
-
return new _VoiceBank(
|
|
86
|
-
manifest,
|
|
87
|
-
new BlobVoiceSource(koe, pcmBase(jsonLength))
|
|
127
|
+
const bin = new ArrayBuffer(byteOffset);
|
|
128
|
+
const view = new Uint8Array(bin);
|
|
129
|
+
let pos = 0;
|
|
130
|
+
for (const chunk of chunks) {
|
|
131
|
+
view.set(
|
|
132
|
+
new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength),
|
|
133
|
+
pos
|
|
88
134
|
);
|
|
135
|
+
pos += chunk.byteLength;
|
|
89
136
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
* worker / AudioWorklet.
|
|
98
|
-
*/
|
|
99
|
-
async readPcmBytes(phoneme) {
|
|
100
|
-
const entry = this.manifest.phonemes[phoneme];
|
|
101
|
-
if (!entry) return null;
|
|
102
|
-
return this.source.readBytes(entry.offset, entry.length * 2);
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* A phoneme's PCM as a Float64Array normalised to [-1, 1], or null if unknown.
|
|
106
|
-
* Intended for external analysis / resynthesis such as the WORLD vocoder.
|
|
107
|
-
*/
|
|
108
|
-
async getPcm(phoneme) {
|
|
109
|
-
const buf = await this.readPcmBytes(phoneme);
|
|
110
|
-
if (!buf) return null;
|
|
111
|
-
const int16 = new Int16Array(buf);
|
|
112
|
-
const f64 = new Float64Array(int16.length);
|
|
113
|
-
for (let i = 0; i < int16.length; i++) f64[i] = int16[i] / 32768;
|
|
114
|
-
return f64;
|
|
115
|
-
}
|
|
116
|
-
};
|
|
137
|
+
const manifest = {
|
|
138
|
+
sampleRate: 48e3,
|
|
139
|
+
referencePitch,
|
|
140
|
+
phonemes
|
|
141
|
+
};
|
|
142
|
+
return { manifest, bin };
|
|
143
|
+
}
|
|
117
144
|
|
|
118
|
-
// src/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
// src/converter/parse-oto.ts
|
|
146
|
+
function parseOto(content) {
|
|
147
|
+
const entries = [];
|
|
148
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
149
|
+
const line = raw.trim();
|
|
150
|
+
if (!line || line.startsWith("#")) continue;
|
|
151
|
+
const eq = line.indexOf("=");
|
|
152
|
+
if (eq === -1) continue;
|
|
153
|
+
const wav = line.slice(0, eq).trim();
|
|
154
|
+
const parts = line.slice(eq + 1).split(",");
|
|
155
|
+
if (parts.length < 6) continue;
|
|
156
|
+
const [alias, offsetStr, consonantStr, cutoffStr, preStr, overlapStr] = parts;
|
|
157
|
+
const aliasStr = alias.trim() || wav.replace(/\.[^.]+$/, "");
|
|
158
|
+
const entry = {
|
|
159
|
+
wav,
|
|
160
|
+
alias: aliasStr,
|
|
161
|
+
offset: parseFloat(offsetStr) || 0,
|
|
162
|
+
consonant: parseFloat(consonantStr) || 0,
|
|
163
|
+
cutoff: parseFloat(cutoffStr) || 0,
|
|
164
|
+
pre: parseFloat(preStr) || 0,
|
|
165
|
+
overlap: parseFloat(overlapStr) || 0
|
|
166
|
+
};
|
|
167
|
+
if (!entry.alias) continue;
|
|
168
|
+
entries.push(entry);
|
|
132
169
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
170
|
+
return entries;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/converter/wav.ts
|
|
174
|
+
function parseWav(buf) {
|
|
175
|
+
const view = new DataView(buf);
|
|
176
|
+
const riff = readFourCC(view, 0);
|
|
177
|
+
if (riff !== "RIFF") throw new Error(`Not a RIFF file (got "${riff}")`);
|
|
178
|
+
let sampleRate = 0;
|
|
179
|
+
let channels = 0;
|
|
180
|
+
let bitsPerSample = 0;
|
|
181
|
+
let audioFormat = 1;
|
|
182
|
+
let dataOffset = 0;
|
|
183
|
+
let dataLength = 0;
|
|
184
|
+
let pos = 12;
|
|
185
|
+
while (pos < view.byteLength - 8) {
|
|
186
|
+
const id = readFourCC(view, pos);
|
|
187
|
+
const size = view.getUint32(pos + 4, true);
|
|
188
|
+
pos += 8;
|
|
189
|
+
if (id === "fmt ") {
|
|
190
|
+
audioFormat = view.getUint16(pos, true);
|
|
191
|
+
channels = view.getUint16(pos + 2, true);
|
|
192
|
+
sampleRate = view.getUint32(pos + 4, true);
|
|
193
|
+
bitsPerSample = view.getUint16(pos + 14, true);
|
|
194
|
+
if (audioFormat === 65534 && size >= 40) {
|
|
195
|
+
audioFormat = view.getUint16(pos + 24, true);
|
|
196
|
+
}
|
|
197
|
+
} else if (id === "data") {
|
|
198
|
+
dataOffset = pos;
|
|
199
|
+
dataLength = Math.min(size, view.byteLength - pos);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
pos += size + (size & 1);
|
|
139
203
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.bank = await VoiceBank.load(koe);
|
|
147
|
-
this.delivered.clear();
|
|
148
|
-
this.pending.clear();
|
|
149
|
-
this.node = new AudioWorkletNode(this.ctx, "koe-processor", {
|
|
150
|
-
numberOfInputs: 0,
|
|
151
|
-
numberOfOutputs: 1,
|
|
152
|
-
outputChannelCount: [1]
|
|
153
|
-
});
|
|
154
|
-
this.node.port.postMessage({ type: "init", manifest: this.bank.manifest });
|
|
155
|
-
this.node.connect(this.ctx.destination);
|
|
156
|
-
console.log(
|
|
157
|
-
"[koe] ready \u2014",
|
|
158
|
-
Object.keys(this.bank.manifest.phonemes).length,
|
|
159
|
-
"phonemes (on-demand)"
|
|
204
|
+
if (!dataOffset) throw new Error("WAV has no data chunk");
|
|
205
|
+
if (!channels || !sampleRate) throw new Error("WAV fmt chunk missing");
|
|
206
|
+
const supported = audioFormat === 3 && bitsPerSample === 32 || audioFormat === 1 && (bitsPerSample === 8 || bitsPerSample === 16 || bitsPerSample === 24);
|
|
207
|
+
if (!supported) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Unsupported WAV format ${audioFormat} / ${bitsPerSample}-bit (need PCM 8/16/24-bit or IEEE float 32-bit)`
|
|
160
210
|
);
|
|
161
211
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
]);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
212
|
+
const bytesPerSample = bitsPerSample >> 3;
|
|
213
|
+
const totalSamples = Math.floor(dataLength / bytesPerSample);
|
|
214
|
+
const samples = new Float32Array(totalSamples);
|
|
215
|
+
for (let i = 0; i < totalSamples; i++) {
|
|
216
|
+
const p = dataOffset + i * bytesPerSample;
|
|
217
|
+
if (audioFormat === 3) {
|
|
218
|
+
samples[i] = view.getFloat32(p, true);
|
|
219
|
+
} else if (bitsPerSample === 8) {
|
|
220
|
+
samples[i] = (view.getUint8(p) - 128) / 128;
|
|
221
|
+
} else if (bitsPerSample === 16) {
|
|
222
|
+
samples[i] = view.getInt16(p, true) / 32768;
|
|
223
|
+
} else if (bitsPerSample === 24) {
|
|
224
|
+
const lo = view.getUint8(p) | view.getUint8(p + 1) << 8;
|
|
225
|
+
let hi = view.getUint8(p + 2);
|
|
226
|
+
if (hi & 128) hi = hi | 4294967040;
|
|
227
|
+
samples[i] = (hi << 16 | lo) / 8388608;
|
|
228
|
+
}
|
|
178
229
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
230
|
+
return { sampleRate, channels, samples };
|
|
231
|
+
}
|
|
232
|
+
function toMono(wav) {
|
|
233
|
+
if (wav.channels === 1) return wav;
|
|
234
|
+
const len = wav.samples.length / wav.channels;
|
|
235
|
+
const out = new Float32Array(len);
|
|
236
|
+
for (let i = 0; i < len; i++) {
|
|
237
|
+
let sum = 0;
|
|
238
|
+
for (let c = 0; c < wav.channels; c++)
|
|
239
|
+
sum += wav.samples[i * wav.channels + c];
|
|
240
|
+
out[i] = sum / wav.channels;
|
|
186
241
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
242
|
+
return { sampleRate: wav.sampleRate, channels: 1, samples: out };
|
|
243
|
+
}
|
|
244
|
+
function resample(wav, targetRate) {
|
|
245
|
+
if (wav.sampleRate === targetRate) return wav;
|
|
246
|
+
const ratio = wav.sampleRate / targetRate;
|
|
247
|
+
const outLen = Math.floor(wav.samples.length / ratio);
|
|
248
|
+
const out = new Float32Array(outLen);
|
|
249
|
+
const src = wav.samples;
|
|
250
|
+
for (let i = 0; i < outLen; i++) {
|
|
251
|
+
const x = i * ratio;
|
|
252
|
+
const xi = Math.floor(x);
|
|
253
|
+
const frac = x - xi;
|
|
254
|
+
out[i] = (src[xi] ?? 0) + ((src[xi + 1] ?? 0) - (src[xi] ?? 0)) * frac;
|
|
190
255
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
256
|
+
return { sampleRate: targetRate, channels: 1, samples: out };
|
|
257
|
+
}
|
|
258
|
+
function toInt16(samples) {
|
|
259
|
+
const out = new Int16Array(samples.length);
|
|
260
|
+
for (let i = 0; i < samples.length; i++) {
|
|
261
|
+
out[i] = Math.round(Math.max(-1, Math.min(1, samples[i])) * 32767);
|
|
194
262
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
function normalizePcm(buf) {
|
|
266
|
+
const wav = parseWav(buf);
|
|
267
|
+
const mono = toMono(wav);
|
|
268
|
+
const resampled = resample(mono, 48e3);
|
|
269
|
+
return toInt16(resampled.samples);
|
|
270
|
+
}
|
|
271
|
+
function readFourCC(view, pos) {
|
|
272
|
+
return String.fromCharCode(
|
|
273
|
+
view.getUint8(pos),
|
|
274
|
+
view.getUint8(pos + 1),
|
|
275
|
+
view.getUint8(pos + 2),
|
|
276
|
+
view.getUint8(pos + 3)
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/browser.js
|
|
281
|
+
var u8 = Uint8Array;
|
|
282
|
+
var u16 = Uint16Array;
|
|
283
|
+
var i32 = Int32Array;
|
|
284
|
+
var fleb = new u8([
|
|
285
|
+
0,
|
|
286
|
+
0,
|
|
287
|
+
0,
|
|
288
|
+
0,
|
|
289
|
+
0,
|
|
290
|
+
0,
|
|
291
|
+
0,
|
|
292
|
+
0,
|
|
293
|
+
1,
|
|
294
|
+
1,
|
|
295
|
+
1,
|
|
296
|
+
1,
|
|
297
|
+
2,
|
|
298
|
+
2,
|
|
299
|
+
2,
|
|
300
|
+
2,
|
|
301
|
+
3,
|
|
302
|
+
3,
|
|
303
|
+
3,
|
|
304
|
+
3,
|
|
305
|
+
4,
|
|
306
|
+
4,
|
|
307
|
+
4,
|
|
308
|
+
4,
|
|
309
|
+
5,
|
|
310
|
+
5,
|
|
311
|
+
5,
|
|
312
|
+
5,
|
|
313
|
+
0,
|
|
314
|
+
/* unused */
|
|
315
|
+
0,
|
|
316
|
+
0,
|
|
317
|
+
/* impossible */
|
|
318
|
+
0
|
|
319
|
+
]);
|
|
320
|
+
var fdeb = new u8([
|
|
321
|
+
0,
|
|
322
|
+
0,
|
|
323
|
+
0,
|
|
324
|
+
0,
|
|
325
|
+
1,
|
|
326
|
+
1,
|
|
327
|
+
2,
|
|
328
|
+
2,
|
|
329
|
+
3,
|
|
330
|
+
3,
|
|
331
|
+
4,
|
|
332
|
+
4,
|
|
333
|
+
5,
|
|
334
|
+
5,
|
|
335
|
+
6,
|
|
336
|
+
6,
|
|
337
|
+
7,
|
|
338
|
+
7,
|
|
339
|
+
8,
|
|
340
|
+
8,
|
|
341
|
+
9,
|
|
342
|
+
9,
|
|
343
|
+
10,
|
|
344
|
+
10,
|
|
345
|
+
11,
|
|
346
|
+
11,
|
|
347
|
+
12,
|
|
348
|
+
12,
|
|
349
|
+
13,
|
|
350
|
+
13,
|
|
351
|
+
/* unused */
|
|
352
|
+
0,
|
|
353
|
+
0
|
|
354
|
+
]);
|
|
355
|
+
var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
|
|
356
|
+
var freb = function(eb, start) {
|
|
357
|
+
var b = new u16(31);
|
|
358
|
+
for (var i = 0; i < 31; ++i) {
|
|
359
|
+
b[i] = start += 1 << eb[i - 1];
|
|
360
|
+
}
|
|
361
|
+
var r = new i32(b[30]);
|
|
362
|
+
for (var i = 1; i < 30; ++i) {
|
|
363
|
+
for (var j = b[i]; j < b[i + 1]; ++j) {
|
|
364
|
+
r[j] = j - b[i] << 5 | i;
|
|
365
|
+
}
|
|
202
366
|
}
|
|
367
|
+
return { b, r };
|
|
203
368
|
};
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
var
|
|
207
|
-
|
|
208
|
-
var
|
|
209
|
-
var
|
|
210
|
-
var
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
369
|
+
var _a = freb(fleb, 2);
|
|
370
|
+
var fl = _a.b;
|
|
371
|
+
var revfl = _a.r;
|
|
372
|
+
fl[28] = 258, revfl[258] = 28;
|
|
373
|
+
var _b = freb(fdeb, 0);
|
|
374
|
+
var fd = _b.b;
|
|
375
|
+
var revfd = _b.r;
|
|
376
|
+
var rev = new u16(32768);
|
|
377
|
+
for (i = 0; i < 32768; ++i) {
|
|
378
|
+
x = (i & 43690) >> 1 | (i & 21845) << 1;
|
|
379
|
+
x = (x & 52428) >> 2 | (x & 13107) << 2;
|
|
380
|
+
x = (x & 61680) >> 4 | (x & 3855) << 4;
|
|
381
|
+
rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
|
|
216
382
|
}
|
|
217
|
-
var
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
383
|
+
var x;
|
|
384
|
+
var i;
|
|
385
|
+
var hMap = (function(cd, mb, r) {
|
|
386
|
+
var s = cd.length;
|
|
387
|
+
var i = 0;
|
|
388
|
+
var l = new u16(mb);
|
|
389
|
+
for (; i < s; ++i) {
|
|
390
|
+
if (cd[i])
|
|
391
|
+
++l[cd[i] - 1];
|
|
392
|
+
}
|
|
393
|
+
var le = new u16(mb);
|
|
394
|
+
for (i = 1; i < mb; ++i) {
|
|
395
|
+
le[i] = le[i - 1] + l[i - 1] << 1;
|
|
396
|
+
}
|
|
397
|
+
var co;
|
|
398
|
+
if (r) {
|
|
399
|
+
co = new u16(1 << mb);
|
|
400
|
+
var rvb = 15 - mb;
|
|
401
|
+
for (i = 0; i < s; ++i) {
|
|
402
|
+
if (cd[i]) {
|
|
403
|
+
var sv = i << 4 | cd[i];
|
|
404
|
+
var r_1 = mb - cd[i];
|
|
405
|
+
var v = le[cd[i] - 1]++ << r_1;
|
|
406
|
+
for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
|
|
407
|
+
co[rev[v] >> rvb] = sv;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
co = new u16(s);
|
|
413
|
+
for (i = 0; i < s; ++i) {
|
|
414
|
+
if (cd[i]) {
|
|
415
|
+
co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return co;
|
|
420
|
+
});
|
|
421
|
+
var flt = new u8(288);
|
|
422
|
+
for (i = 0; i < 144; ++i)
|
|
423
|
+
flt[i] = 8;
|
|
424
|
+
var i;
|
|
425
|
+
for (i = 144; i < 256; ++i)
|
|
426
|
+
flt[i] = 9;
|
|
427
|
+
var i;
|
|
428
|
+
for (i = 256; i < 280; ++i)
|
|
429
|
+
flt[i] = 7;
|
|
430
|
+
var i;
|
|
431
|
+
for (i = 280; i < 288; ++i)
|
|
432
|
+
flt[i] = 8;
|
|
433
|
+
var i;
|
|
434
|
+
var fdt = new u8(32);
|
|
435
|
+
for (i = 0; i < 32; ++i)
|
|
436
|
+
fdt[i] = 5;
|
|
437
|
+
var i;
|
|
438
|
+
var flrm = /* @__PURE__ */ hMap(flt, 9, 1);
|
|
439
|
+
var fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
|
|
440
|
+
var max = function(a) {
|
|
441
|
+
var m = a[0];
|
|
442
|
+
for (var i = 1; i < a.length; ++i) {
|
|
443
|
+
if (a[i] > m)
|
|
444
|
+
m = a[i];
|
|
445
|
+
}
|
|
446
|
+
return m;
|
|
447
|
+
};
|
|
448
|
+
var bits = function(d, p, m) {
|
|
449
|
+
var o = p / 8 | 0;
|
|
450
|
+
return (d[o] | d[o + 1] << 8) >> (p & 7) & m;
|
|
451
|
+
};
|
|
452
|
+
var bits16 = function(d, p) {
|
|
453
|
+
var o = p / 8 | 0;
|
|
454
|
+
return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
|
|
455
|
+
};
|
|
456
|
+
var shft = function(p) {
|
|
457
|
+
return (p + 7) / 8 | 0;
|
|
458
|
+
};
|
|
459
|
+
var slc = function(v, s, e) {
|
|
460
|
+
if (s == null || s < 0)
|
|
461
|
+
s = 0;
|
|
462
|
+
if (e == null || e > v.length)
|
|
463
|
+
e = v.length;
|
|
464
|
+
return new u8(v.subarray(s, e));
|
|
465
|
+
};
|
|
466
|
+
var ec = [
|
|
467
|
+
"unexpected EOF",
|
|
468
|
+
"invalid block type",
|
|
469
|
+
"invalid length/literal",
|
|
470
|
+
"invalid distance",
|
|
471
|
+
"stream finished",
|
|
472
|
+
"no stream handler",
|
|
473
|
+
,
|
|
474
|
+
// determined by compression function
|
|
475
|
+
"no callback",
|
|
476
|
+
"invalid UTF-8 data",
|
|
477
|
+
"extra field too long",
|
|
478
|
+
"date not in range 1980-2099",
|
|
479
|
+
"filename too long",
|
|
480
|
+
"stream finishing",
|
|
481
|
+
"invalid zip data"
|
|
482
|
+
// determined by unknown compression method
|
|
483
|
+
];
|
|
484
|
+
var err = function(ind, msg, nt) {
|
|
485
|
+
var e = new Error(msg || ec[ind]);
|
|
486
|
+
e.code = ind;
|
|
487
|
+
if (Error.captureStackTrace)
|
|
488
|
+
Error.captureStackTrace(e, err);
|
|
489
|
+
if (!nt)
|
|
490
|
+
throw e;
|
|
491
|
+
return e;
|
|
492
|
+
};
|
|
493
|
+
var inflt = function(dat, st, buf, dict) {
|
|
494
|
+
var sl = dat.length, dl = dict ? dict.length : 0;
|
|
495
|
+
if (!sl || st.f && !st.l)
|
|
496
|
+
return buf || new u8(0);
|
|
497
|
+
var noBuf = !buf;
|
|
498
|
+
var resize = noBuf || st.i != 2;
|
|
499
|
+
var noSt = st.i;
|
|
500
|
+
if (noBuf)
|
|
501
|
+
buf = new u8(sl * 3);
|
|
502
|
+
var cbuf = function(l2) {
|
|
503
|
+
var bl = buf.length;
|
|
504
|
+
if (l2 > bl) {
|
|
505
|
+
var nbuf = new u8(Math.max(bl * 2, l2));
|
|
506
|
+
nbuf.set(buf);
|
|
507
|
+
buf = nbuf;
|
|
226
508
|
}
|
|
227
|
-
const s = document.createElement("script");
|
|
228
|
-
s.src = src;
|
|
229
|
-
s.dataset.koeWorldline = src;
|
|
230
|
-
s.onload = () => resolve();
|
|
231
|
-
s.onerror = () => reject(new Error(`worldline: failed to load ${src}`));
|
|
232
|
-
document.head.appendChild(s);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
function loadWasm(scriptUrl) {
|
|
236
|
-
const cached = moduleCache.get(scriptUrl);
|
|
237
|
-
if (cached) return cached;
|
|
238
|
-
const baseUrl = scriptUrl.slice(0, scriptUrl.lastIndexOf("/") + 1);
|
|
239
|
-
const instantiate = () => {
|
|
240
|
-
const factory = globalThis.WorldlineModule;
|
|
241
|
-
if (!factory)
|
|
242
|
-
throw new Error(
|
|
243
|
-
"worldline: WorldlineModule global was not defined by the script"
|
|
244
|
-
);
|
|
245
|
-
return factory({ locateFile: (f) => baseUrl + f });
|
|
246
509
|
};
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
510
|
+
var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
|
|
511
|
+
var tbts = sl * 8;
|
|
512
|
+
do {
|
|
513
|
+
if (!lm) {
|
|
514
|
+
final = bits(dat, pos, 1);
|
|
515
|
+
var type = bits(dat, pos + 1, 3);
|
|
516
|
+
pos += 3;
|
|
517
|
+
if (!type) {
|
|
518
|
+
var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
|
|
519
|
+
if (t > sl) {
|
|
520
|
+
if (noSt)
|
|
521
|
+
err(0);
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
if (resize)
|
|
525
|
+
cbuf(bt + l);
|
|
526
|
+
buf.set(dat.subarray(s, t), bt);
|
|
527
|
+
st.b = bt += l, st.p = pos = t * 8, st.f = final;
|
|
528
|
+
continue;
|
|
529
|
+
} else if (type == 1)
|
|
530
|
+
lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
|
|
531
|
+
else if (type == 2) {
|
|
532
|
+
var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
|
|
533
|
+
var tl = hLit + bits(dat, pos + 5, 31) + 1;
|
|
534
|
+
pos += 14;
|
|
535
|
+
var ldt = new u8(tl);
|
|
536
|
+
var clt = new u8(19);
|
|
537
|
+
for (var i = 0; i < hcLen; ++i) {
|
|
538
|
+
clt[clim[i]] = bits(dat, pos + i * 3, 7);
|
|
539
|
+
}
|
|
540
|
+
pos += hcLen * 3;
|
|
541
|
+
var clb = max(clt), clbmsk = (1 << clb) - 1;
|
|
542
|
+
var clm = hMap(clt, clb, 1);
|
|
543
|
+
for (var i = 0; i < tl; ) {
|
|
544
|
+
var r = clm[bits(dat, pos, clbmsk)];
|
|
545
|
+
pos += r & 15;
|
|
546
|
+
var s = r >> 4;
|
|
547
|
+
if (s < 16) {
|
|
548
|
+
ldt[i++] = s;
|
|
549
|
+
} else {
|
|
550
|
+
var c = 0, n = 0;
|
|
551
|
+
if (s == 16)
|
|
552
|
+
n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];
|
|
553
|
+
else if (s == 17)
|
|
554
|
+
n = 3 + bits(dat, pos, 7), pos += 3;
|
|
555
|
+
else if (s == 18)
|
|
556
|
+
n = 11 + bits(dat, pos, 127), pos += 7;
|
|
557
|
+
while (n--)
|
|
558
|
+
ldt[i++] = c;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
|
|
562
|
+
lbt = max(lt);
|
|
563
|
+
dbt = max(dt);
|
|
564
|
+
lm = hMap(lt, lbt, 1);
|
|
565
|
+
dm = hMap(dt, dbt, 1);
|
|
566
|
+
} else
|
|
567
|
+
err(1);
|
|
568
|
+
if (pos > tbts) {
|
|
569
|
+
if (noSt)
|
|
570
|
+
err(0);
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (resize)
|
|
575
|
+
cbuf(bt + 131072);
|
|
576
|
+
var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
|
|
577
|
+
var lpos = pos;
|
|
578
|
+
for (; ; lpos = pos) {
|
|
579
|
+
var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
|
|
580
|
+
pos += c & 15;
|
|
581
|
+
if (pos > tbts) {
|
|
582
|
+
if (noSt)
|
|
583
|
+
err(0);
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
if (!c)
|
|
587
|
+
err(2);
|
|
588
|
+
if (sym < 256)
|
|
589
|
+
buf[bt++] = sym;
|
|
590
|
+
else if (sym == 256) {
|
|
591
|
+
lpos = pos, lm = null;
|
|
592
|
+
break;
|
|
593
|
+
} else {
|
|
594
|
+
var add = sym - 254;
|
|
595
|
+
if (sym > 264) {
|
|
596
|
+
var i = sym - 257, b = fleb[i];
|
|
597
|
+
add = bits(dat, pos, (1 << b) - 1) + fl[i];
|
|
598
|
+
pos += b;
|
|
599
|
+
}
|
|
600
|
+
var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
|
|
601
|
+
if (!d)
|
|
602
|
+
err(3);
|
|
603
|
+
pos += d & 15;
|
|
604
|
+
var dt = fd[dsym];
|
|
605
|
+
if (dsym > 3) {
|
|
606
|
+
var b = fdeb[dsym];
|
|
607
|
+
dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
|
|
608
|
+
}
|
|
609
|
+
if (pos > tbts) {
|
|
610
|
+
if (noSt)
|
|
611
|
+
err(0);
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
if (resize)
|
|
615
|
+
cbuf(bt + 131072);
|
|
616
|
+
var end = bt + add;
|
|
617
|
+
if (bt < dt) {
|
|
618
|
+
var shift = dl - dt, dend = Math.min(dt, end);
|
|
619
|
+
if (shift + bt < 0)
|
|
620
|
+
err(3);
|
|
621
|
+
for (; bt < dend; ++bt)
|
|
622
|
+
buf[bt] = dict[shift + bt];
|
|
623
|
+
}
|
|
624
|
+
for (; bt < end; ++bt)
|
|
625
|
+
buf[bt] = buf[bt - dt];
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
st.l = lm, st.p = lpos, st.b = bt, st.f = final;
|
|
629
|
+
if (lm)
|
|
630
|
+
final = 1, st.m = lbt, st.d = dm, st.n = dbt;
|
|
631
|
+
} while (!final);
|
|
632
|
+
return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
|
|
633
|
+
};
|
|
634
|
+
var et = /* @__PURE__ */ new u8(0);
|
|
635
|
+
var b2 = function(d, b) {
|
|
636
|
+
return d[b] | d[b + 1] << 8;
|
|
637
|
+
};
|
|
638
|
+
var b4 = function(d, b) {
|
|
639
|
+
return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0;
|
|
640
|
+
};
|
|
641
|
+
var b8 = function(d, b) {
|
|
642
|
+
return b4(d, b) + b4(d, b + 4) * 4294967296;
|
|
643
|
+
};
|
|
644
|
+
function inflateSync(data, opts) {
|
|
645
|
+
return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);
|
|
646
|
+
}
|
|
647
|
+
var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
|
|
648
|
+
var tds = 0;
|
|
649
|
+
try {
|
|
650
|
+
td.decode(et, { stream: true });
|
|
651
|
+
tds = 1;
|
|
652
|
+
} catch (e) {
|
|
653
|
+
}
|
|
654
|
+
var dutf8 = function(d) {
|
|
655
|
+
for (var r = "", i = 0; ; ) {
|
|
656
|
+
var c = d[i++];
|
|
657
|
+
var eb = (c > 127) + (c > 223) + (c > 239);
|
|
658
|
+
if (i + eb > d.length)
|
|
659
|
+
return { s: r, r: slc(d, i - 1) };
|
|
660
|
+
if (!eb)
|
|
661
|
+
r += String.fromCharCode(c);
|
|
662
|
+
else if (eb == 3) {
|
|
663
|
+
c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | d[i++] & 63) - 65536, r += String.fromCharCode(55296 | c >> 10, 56320 | c & 1023);
|
|
664
|
+
} else if (eb & 1)
|
|
665
|
+
r += String.fromCharCode((c & 31) << 6 | d[i++] & 63);
|
|
666
|
+
else
|
|
667
|
+
r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | d[i++] & 63);
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
function strFromU8(dat, latin1) {
|
|
671
|
+
if (latin1) {
|
|
672
|
+
var r = "";
|
|
673
|
+
for (var i = 0; i < dat.length; i += 16384)
|
|
674
|
+
r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384));
|
|
675
|
+
return r;
|
|
676
|
+
} else if (td) {
|
|
677
|
+
return td.decode(dat);
|
|
255
678
|
} else {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
);
|
|
679
|
+
var _a2 = dutf8(dat), s = _a2.s, r = _a2.r;
|
|
680
|
+
if (r.length)
|
|
681
|
+
err(8);
|
|
682
|
+
return s;
|
|
261
683
|
}
|
|
262
|
-
moduleCache.set(scriptUrl, promise);
|
|
263
|
-
return promise;
|
|
264
684
|
}
|
|
265
|
-
var
|
|
266
|
-
|
|
267
|
-
|
|
685
|
+
var slzh = function(d, b) {
|
|
686
|
+
return b + 30 + b2(d, b + 26) + b2(d, b + 28);
|
|
687
|
+
};
|
|
688
|
+
var zh = function(d, b, z) {
|
|
689
|
+
var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
|
|
690
|
+
var _a2 = z64hs(d, es, efl, z, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
|
|
691
|
+
return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
|
|
692
|
+
};
|
|
693
|
+
var z64hs = function(d, b, l, z, sc, su, off) {
|
|
694
|
+
var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
|
|
695
|
+
var nf = nsc + nsu + noff;
|
|
696
|
+
if (z && nf) {
|
|
697
|
+
for (; b + 4 < e; b += 4 + b2(d, b + 2)) {
|
|
698
|
+
if (b2(d, b) == 1) {
|
|
699
|
+
return [
|
|
700
|
+
nsc ? b8(d, b + 4 + 8 * nsu) : sc,
|
|
701
|
+
nsu ? b8(d, b + 4) : su,
|
|
702
|
+
noff ? b8(d, b + 4 + 8 * (nsu + nsc)) : off,
|
|
703
|
+
1
|
|
704
|
+
];
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (z < 2)
|
|
708
|
+
err(13);
|
|
268
709
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
*/
|
|
278
|
-
static async load(options) {
|
|
279
|
-
return new _Worldline(await loadWasm(options.scriptUrl));
|
|
710
|
+
return [sc, su, off, 0];
|
|
711
|
+
};
|
|
712
|
+
function unzipSync(data, opts) {
|
|
713
|
+
var files = {};
|
|
714
|
+
var e = data.length - 22;
|
|
715
|
+
for (; b4(data, e) != 101010256; --e) {
|
|
716
|
+
if (!e || data.length - e > 65558)
|
|
717
|
+
err(13);
|
|
280
718
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
*/
|
|
294
|
-
renderNote(params) {
|
|
295
|
-
const { pcm, pitch, durationMs, preMs, consonantMs, tempo = 120 } = params;
|
|
296
|
-
if (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;
|
|
297
|
-
const WL = this.wasm;
|
|
298
|
-
const FS = WORLDLINE_SAMPLE_RATE;
|
|
299
|
-
const midiNote = Math.round(69 + 12 * Math.log2(pitch / 440));
|
|
300
|
-
const posMs = 0;
|
|
301
|
-
const reqLen = preMs + durationMs;
|
|
302
|
-
const cutMs = WL_FRAME_MS * 2;
|
|
303
|
-
const ps = WL._PhraseSynthNew();
|
|
304
|
-
if (!ps) return null;
|
|
305
|
-
const reqPtr = WL._malloc(SYNTH_REQ_SIZE);
|
|
306
|
-
if (!reqPtr) {
|
|
307
|
-
WL._PhraseSynthDelete(ps);
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
const samplePtr = WL._malloc(pcm.length * 8);
|
|
311
|
-
if (!samplePtr) {
|
|
312
|
-
WL._free(reqPtr);
|
|
313
|
-
WL._PhraseSynthDelete(ps);
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
WL.HEAPF64.set(pcm, samplePtr >> 3);
|
|
317
|
-
const sv = (off, val, type) => WL.setValue(reqPtr + off, val, type);
|
|
318
|
-
sv(0, FS, "i32");
|
|
319
|
-
sv(4, pcm.length, "i32");
|
|
320
|
-
sv(8, samplePtr, "*");
|
|
321
|
-
sv(12, 0, "i32");
|
|
322
|
-
sv(16, 0, "*");
|
|
323
|
-
sv(20, midiNote, "i32");
|
|
324
|
-
sv(24, 100, "double");
|
|
325
|
-
sv(32, 0, "double");
|
|
326
|
-
sv(40, reqLen, "double");
|
|
327
|
-
sv(48, consonantMs, "double");
|
|
328
|
-
sv(56, cutMs, "double");
|
|
329
|
-
sv(64, 100, "double");
|
|
330
|
-
sv(72, 0, "double");
|
|
331
|
-
sv(80, tempo, "double");
|
|
332
|
-
sv(88, 0, "i32");
|
|
333
|
-
sv(92, 0, "*");
|
|
334
|
-
sv(96, 0, "i32");
|
|
335
|
-
sv(100, 0, "i32");
|
|
336
|
-
sv(104, 100, "i32");
|
|
337
|
-
sv(108, 0, "i32");
|
|
338
|
-
sv(112, 0, "i32");
|
|
339
|
-
sv(116, 100, "i32");
|
|
340
|
-
WL._PhraseSynthAddRequest(ps, reqPtr, posMs, 0, reqLen, 0, 0, 0);
|
|
341
|
-
WL._free(samplePtr);
|
|
342
|
-
WL._free(reqPtr);
|
|
343
|
-
const totalMs = posMs + reqLen + WL_FRAME_MS * 2;
|
|
344
|
-
const nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;
|
|
345
|
-
const f0Arr = new Float64Array(nFrames).fill(pitch);
|
|
346
|
-
const gArr = new Float64Array(nFrames).fill(0.5);
|
|
347
|
-
const tArr = new Float64Array(nFrames).fill(0.5);
|
|
348
|
-
const bArr = new Float64Array(nFrames).fill(0.5);
|
|
349
|
-
const vArr = new Float64Array(nFrames).fill(1);
|
|
350
|
-
const f0Ptr = WL._malloc(nFrames * 8);
|
|
351
|
-
const gPtr = WL._malloc(nFrames * 8);
|
|
352
|
-
const tPtr = WL._malloc(nFrames * 8);
|
|
353
|
-
const bPtr = WL._malloc(nFrames * 8);
|
|
354
|
-
const vPtr = WL._malloc(nFrames * 8);
|
|
355
|
-
if (!f0Ptr || !gPtr || !tPtr || !bPtr || !vPtr) {
|
|
356
|
-
if (f0Ptr) WL._free(f0Ptr);
|
|
357
|
-
if (gPtr) WL._free(gPtr);
|
|
358
|
-
if (tPtr) WL._free(tPtr);
|
|
359
|
-
if (bPtr) WL._free(bPtr);
|
|
360
|
-
if (vPtr) WL._free(vPtr);
|
|
361
|
-
WL._PhraseSynthDelete(ps);
|
|
362
|
-
return null;
|
|
719
|
+
;
|
|
720
|
+
var c = b2(data, e + 8);
|
|
721
|
+
if (!c)
|
|
722
|
+
return {};
|
|
723
|
+
var o = b4(data, e + 16);
|
|
724
|
+
var z = b4(data, e - 20) == 117853008;
|
|
725
|
+
if (z) {
|
|
726
|
+
var ze = b4(data, e - 12);
|
|
727
|
+
z = b4(data, ze) == 101075792;
|
|
728
|
+
if (z) {
|
|
729
|
+
c = b4(data, ze + 32);
|
|
730
|
+
o = b4(data, ze + 48);
|
|
363
731
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
WL._free(tPtr);
|
|
382
|
-
WL._free(bPtr);
|
|
383
|
-
WL._free(vPtr);
|
|
384
|
-
const yPtrPtr = WL._malloc(4);
|
|
385
|
-
if (!yPtrPtr) {
|
|
386
|
-
WL._PhraseSynthDelete(ps);
|
|
387
|
-
return null;
|
|
732
|
+
}
|
|
733
|
+
var fltr = opts && opts.filter;
|
|
734
|
+
for (var i = 0; i < c; ++i) {
|
|
735
|
+
var _a2 = zh(data, o, z), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
|
|
736
|
+
o = no;
|
|
737
|
+
if (!fltr || fltr({
|
|
738
|
+
name: fn,
|
|
739
|
+
size: sc,
|
|
740
|
+
originalSize: su,
|
|
741
|
+
compression: c_2
|
|
742
|
+
})) {
|
|
743
|
+
if (!c_2)
|
|
744
|
+
files[fn] = slc(data, b, b + sc);
|
|
745
|
+
else if (c_2 == 8)
|
|
746
|
+
files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
|
|
747
|
+
else
|
|
748
|
+
err(14, "unknown compression type " + c_2);
|
|
388
749
|
}
|
|
389
|
-
const outLen = WL._PhraseSynthSynth(ps, yPtrPtr, 0);
|
|
390
|
-
const yPtr = WL.getValue(yPtrPtr, "*");
|
|
391
|
-
const audio = outLen > 0 ? new Float32Array(WL.HEAPF32.buffer, yPtr, outLen).slice() : null;
|
|
392
|
-
WL._free(yPtrPtr);
|
|
393
|
-
WL._PhraseSynthDelete(ps);
|
|
394
|
-
return audio;
|
|
395
750
|
}
|
|
396
|
-
|
|
751
|
+
return files;
|
|
752
|
+
}
|
|
397
753
|
|
|
398
|
-
// src/converter/
|
|
399
|
-
function
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
pre: parseFloat(preStr) || 0,
|
|
418
|
-
overlap: parseFloat(overlapStr) || 0
|
|
754
|
+
// src/converter/zip.ts
|
|
755
|
+
function unzipToFileMap(data) {
|
|
756
|
+
const bytes = new Uint8Array(data);
|
|
757
|
+
const unzipped = unzipSync(bytes);
|
|
758
|
+
const isUtf8ByIndex = readUtf8Flags(bytes);
|
|
759
|
+
const sjisDecoder = new TextDecoder("shift_jis");
|
|
760
|
+
const fileMap = {};
|
|
761
|
+
Object.keys(unzipped).forEach((rawName, i) => {
|
|
762
|
+
const fileBytes = unzipped[rawName];
|
|
763
|
+
const name = isUtf8ByIndex[i] ? rawName : sjisDecoder.decode(Uint8Array.from(rawName, (c) => c.charCodeAt(0)));
|
|
764
|
+
const path = name.replace(/\\/g, "/");
|
|
765
|
+
if (path.endsWith("/")) return;
|
|
766
|
+
fileMap[path] = {
|
|
767
|
+
arrayBuffer: () => Promise.resolve(
|
|
768
|
+
fileBytes.buffer.slice(
|
|
769
|
+
fileBytes.byteOffset,
|
|
770
|
+
fileBytes.byteOffset + fileBytes.byteLength
|
|
771
|
+
)
|
|
772
|
+
)
|
|
419
773
|
};
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
return entries;
|
|
774
|
+
});
|
|
775
|
+
return Promise.resolve(fileMap);
|
|
424
776
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
let channels = 0;
|
|
433
|
-
let bitsPerSample = 0;
|
|
434
|
-
let audioFormat = 1;
|
|
435
|
-
let dataOffset = 0;
|
|
436
|
-
let dataLength = 0;
|
|
437
|
-
let pos = 12;
|
|
438
|
-
while (pos < view.byteLength - 8) {
|
|
439
|
-
const id = readFourCC(view, pos);
|
|
440
|
-
const size = view.getUint32(pos + 4, true);
|
|
441
|
-
pos += 8;
|
|
442
|
-
if (id === "fmt ") {
|
|
443
|
-
audioFormat = view.getUint16(pos, true);
|
|
444
|
-
channels = view.getUint16(pos + 2, true);
|
|
445
|
-
sampleRate = view.getUint32(pos + 4, true);
|
|
446
|
-
bitsPerSample = view.getUint16(pos + 14, true);
|
|
447
|
-
} else if (id === "data") {
|
|
448
|
-
dataOffset = pos;
|
|
449
|
-
dataLength = size;
|
|
777
|
+
function readUtf8Flags(buf) {
|
|
778
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
779
|
+
let eocd = -1;
|
|
780
|
+
const maxBack = Math.min(buf.length, 65557);
|
|
781
|
+
for (let i = buf.length - 22; i >= buf.length - maxBack && i >= 0; i--) {
|
|
782
|
+
if (view.getUint32(i, true) === 101010256) {
|
|
783
|
+
eocd = i;
|
|
450
784
|
break;
|
|
451
785
|
}
|
|
452
|
-
pos += size + (size & 1);
|
|
453
786
|
}
|
|
454
|
-
if (
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
samples[i] = view.getInt16(p, true) / 32768;
|
|
467
|
-
} else if (bitsPerSample === 24) {
|
|
468
|
-
const lo = view.getUint8(p) | view.getUint8(p + 1) << 8;
|
|
469
|
-
let hi = view.getUint8(p + 2);
|
|
470
|
-
if (hi & 128) hi = hi | 4294967040;
|
|
471
|
-
samples[i] = (hi << 16 | lo) / 8388608;
|
|
472
|
-
}
|
|
787
|
+
if (eocd < 0) return [];
|
|
788
|
+
const entryCount = view.getUint16(eocd + 10, true);
|
|
789
|
+
let cdOffset = view.getUint32(eocd + 16, true);
|
|
790
|
+
const flags = [];
|
|
791
|
+
for (let i = 0; i < entryCount; i++) {
|
|
792
|
+
if (view.getUint32(cdOffset, true) !== 33639248) break;
|
|
793
|
+
const flag = view.getUint16(cdOffset + 8, true);
|
|
794
|
+
const nameLen = view.getUint16(cdOffset + 28, true);
|
|
795
|
+
const extraLen = view.getUint16(cdOffset + 30, true);
|
|
796
|
+
const commentLen = view.getUint16(cdOffset + 32, true);
|
|
797
|
+
flags.push((flag & 2048) !== 0);
|
|
798
|
+
cdOffset += 46 + nameLen + extraLen + commentLen;
|
|
473
799
|
}
|
|
474
|
-
return
|
|
800
|
+
return flags;
|
|
475
801
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
802
|
+
|
|
803
|
+
// src/koe.ts
|
|
804
|
+
var MAGIC = 1263486208;
|
|
805
|
+
function packKoe(manifest, pcmParts) {
|
|
806
|
+
const json = new TextEncoder().encode(JSON.stringify(manifest));
|
|
807
|
+
const header = new ArrayBuffer(8);
|
|
808
|
+
const view = new DataView(header);
|
|
809
|
+
view.setUint32(0, MAGIC, false);
|
|
810
|
+
view.setUint32(4, json.byteLength, true);
|
|
811
|
+
return new Blob([header, json, ...pcmParts]);
|
|
812
|
+
}
|
|
813
|
+
function parseKoeHeader(headerBytes) {
|
|
814
|
+
const view = new DataView(headerBytes);
|
|
815
|
+
if (view.byteLength < 8 || view.getUint32(0, false) !== MAGIC) {
|
|
816
|
+
throw new Error("Not a .koe file (bad magic)");
|
|
485
817
|
}
|
|
486
|
-
return {
|
|
818
|
+
return { jsonLength: view.getUint32(4, true) };
|
|
487
819
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const frac = x - xi;
|
|
498
|
-
out[i] = (src[xi] ?? 0) + ((src[xi + 1] ?? 0) - (src[xi] ?? 0)) * frac;
|
|
820
|
+
var pcmBase = (jsonLength) => 8 + jsonLength;
|
|
821
|
+
|
|
822
|
+
// src/engine/voice-bank.ts
|
|
823
|
+
var MAX_PHONEME_SAMPLES = 5242880;
|
|
824
|
+
var MAX_JSON_LENGTH = 50 * 1024 * 1024;
|
|
825
|
+
var BlobVoiceSource = class {
|
|
826
|
+
constructor(blob, base) {
|
|
827
|
+
this.blob = blob;
|
|
828
|
+
this.base = base;
|
|
499
829
|
}
|
|
500
|
-
|
|
830
|
+
blob;
|
|
831
|
+
base;
|
|
832
|
+
readBytes(offset, length) {
|
|
833
|
+
const start = this.base + offset;
|
|
834
|
+
return this.blob.slice(start, start + length).arrayBuffer();
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
var RangeVoiceSource = class {
|
|
838
|
+
constructor(url, base) {
|
|
839
|
+
this.url = url;
|
|
840
|
+
this.base = base;
|
|
841
|
+
}
|
|
842
|
+
url;
|
|
843
|
+
base;
|
|
844
|
+
async readBytes(offset, length) {
|
|
845
|
+
const start = this.base + offset;
|
|
846
|
+
return rangeFetch(this.url, start, length);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
async function rangeFetch(url, start, length) {
|
|
850
|
+
const res = await fetch(url, {
|
|
851
|
+
headers: { Range: `bytes=${start}-${start + length - 1}` },
|
|
852
|
+
credentials: "omit"
|
|
853
|
+
// never leak cookies / auth to a MML-supplied URL
|
|
854
|
+
});
|
|
855
|
+
if (res.status !== 206) {
|
|
856
|
+
throw new Error(
|
|
857
|
+
`.koe fetch failed: expected 206 Partial Content, got ${res.status}`
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
return readCapped(res, length);
|
|
501
861
|
}
|
|
502
|
-
function
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
862
|
+
async function readCapped(res, length) {
|
|
863
|
+
const reader = res.body?.getReader();
|
|
864
|
+
if (!reader) {
|
|
865
|
+
const buf = await res.arrayBuffer();
|
|
866
|
+
if (buf.byteLength > length) {
|
|
867
|
+
throw new Error(
|
|
868
|
+
`.koe fetch failed: response exceeds requested ${length} bytes`
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
return buf;
|
|
506
872
|
}
|
|
507
|
-
|
|
873
|
+
const out = new Uint8Array(length);
|
|
874
|
+
let received = 0;
|
|
875
|
+
for (; ; ) {
|
|
876
|
+
const { done, value } = await reader.read();
|
|
877
|
+
if (done) break;
|
|
878
|
+
if (received + value.byteLength > length) {
|
|
879
|
+
await reader.cancel();
|
|
880
|
+
throw new Error(
|
|
881
|
+
`.koe fetch failed: response exceeds requested ${length} bytes`
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
out.set(value, received);
|
|
885
|
+
received += value.byteLength;
|
|
886
|
+
}
|
|
887
|
+
return received === length ? out.buffer : out.buffer.slice(0, received);
|
|
508
888
|
}
|
|
509
|
-
function
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return toInt16(resampled.samples);
|
|
514
|
-
}
|
|
515
|
-
function readFourCC(view, pos) {
|
|
516
|
-
return String.fromCharCode(
|
|
517
|
-
view.getUint8(pos),
|
|
518
|
-
view.getUint8(pos + 1),
|
|
519
|
-
view.getUint8(pos + 2),
|
|
520
|
-
view.getUint8(pos + 3)
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// src/converter/pitch.ts
|
|
525
|
-
var SAMPLE_RATE = 48e3;
|
|
526
|
-
var NAME_SEMITONE = {
|
|
527
|
-
c: 0,
|
|
528
|
-
d: 2,
|
|
529
|
-
e: 4,
|
|
530
|
-
f: 5,
|
|
531
|
-
g: 7,
|
|
532
|
-
a: 9,
|
|
533
|
-
b: 11
|
|
534
|
-
};
|
|
535
|
-
function noteNameToHz(name) {
|
|
536
|
-
const m = /^([A-Ga-g])([#b]?)(-?\d+)$/.exec(name);
|
|
537
|
-
if (!m) return null;
|
|
538
|
-
let semi = NAME_SEMITONE[m[1].toLowerCase()];
|
|
539
|
-
if (m[2] === "#") semi++;
|
|
540
|
-
else if (m[2] === "b") semi--;
|
|
541
|
-
const midi = (parseInt(m[3], 10) + 1) * 12 + semi;
|
|
542
|
-
return 440 * 2 ** ((midi - 69) / 12);
|
|
889
|
+
function validateJsonLength(jsonLength) {
|
|
890
|
+
if (!Number.isInteger(jsonLength) || jsonLength < 0 || jsonLength > MAX_JSON_LENGTH) {
|
|
891
|
+
throw new Error(`manifest JSON length out of bounds: ${jsonLength}`);
|
|
892
|
+
}
|
|
543
893
|
}
|
|
544
|
-
function
|
|
545
|
-
const
|
|
546
|
-
|
|
894
|
+
function parseManifest(json) {
|
|
895
|
+
const manifest = JSON.parse(new TextDecoder().decode(json));
|
|
896
|
+
if (!manifest || typeof manifest !== "object" || typeof manifest.phonemes !== "object" || manifest.phonemes === null) {
|
|
897
|
+
throw new Error("invalid manifest: missing phonemes table");
|
|
898
|
+
}
|
|
899
|
+
return manifest;
|
|
547
900
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const maxLag = Math.floor(sr / 70);
|
|
553
|
-
const outLen = Math.floor((end - start) / DECIM);
|
|
554
|
-
if (outLen < maxLag + 2) return 0;
|
|
555
|
-
const win = Math.min(outLen, 1500);
|
|
556
|
-
const buf = new Float32Array(win);
|
|
557
|
-
let mean = 0;
|
|
558
|
-
for (let i = 0; i < win; i++) {
|
|
559
|
-
let s = 0;
|
|
560
|
-
const base = start + i * DECIM;
|
|
561
|
-
for (let j = 0; j < DECIM; j++) s += pcm[base + j];
|
|
562
|
-
buf[i] = s;
|
|
563
|
-
mean += s;
|
|
901
|
+
var VoiceBank = class _VoiceBank {
|
|
902
|
+
constructor(manifest, source) {
|
|
903
|
+
this.manifest = manifest;
|
|
904
|
+
this.source = source;
|
|
564
905
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
906
|
+
manifest;
|
|
907
|
+
source;
|
|
908
|
+
/**
|
|
909
|
+
* Parse a .koe archive header + manifest and bind a lazy PCM source.
|
|
910
|
+
* @param koe a Blob/File of the .koe archive, or a URL (served with Range support)
|
|
911
|
+
*/
|
|
912
|
+
static async load(koe) {
|
|
913
|
+
try {
|
|
914
|
+
if (typeof koe === "string") {
|
|
915
|
+
if (/^blob:/i.test(koe)) {
|
|
916
|
+
const res = await fetch(koe);
|
|
917
|
+
if (!res.ok) {
|
|
918
|
+
throw new Error(`blob: URL fetch failed: ${res.status}`);
|
|
919
|
+
}
|
|
920
|
+
return await _VoiceBank.fromBlob(await res.blob());
|
|
921
|
+
}
|
|
922
|
+
if (!/^https?:/i.test(koe)) {
|
|
923
|
+
throw new Error(`unsupported URL protocol: ${koe}`);
|
|
924
|
+
}
|
|
925
|
+
const header = await rangeFetch(koe, 0, 8);
|
|
926
|
+
const { jsonLength } = parseKoeHeader(header);
|
|
927
|
+
validateJsonLength(jsonLength);
|
|
928
|
+
const json = await rangeFetch(koe, 8, jsonLength);
|
|
929
|
+
const manifest = parseManifest(json);
|
|
930
|
+
return new _VoiceBank(
|
|
931
|
+
manifest,
|
|
932
|
+
new RangeVoiceSource(koe, pcmBase(jsonLength))
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
return await _VoiceBank.fromBlob(koe);
|
|
936
|
+
} catch (error) {
|
|
937
|
+
throw new Error(
|
|
938
|
+
`Failed to load .koe voice bank: ${error instanceof Error ? error.message : String(error)}`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
570
941
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
return
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
942
|
+
static async fromBlob(koe) {
|
|
943
|
+
const header = await koe.slice(0, 8).arrayBuffer();
|
|
944
|
+
const { jsonLength } = parseKoeHeader(header);
|
|
945
|
+
validateJsonLength(jsonLength);
|
|
946
|
+
const json = await koe.slice(8, 8 + jsonLength).arrayBuffer();
|
|
947
|
+
const manifest = parseManifest(json);
|
|
948
|
+
return new _VoiceBank(
|
|
949
|
+
manifest,
|
|
950
|
+
new BlobVoiceSource(koe, pcmBase(jsonLength))
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
/** True if the bank contains a phoneme under this alias. */
|
|
954
|
+
has(phoneme) {
|
|
955
|
+
return Object.hasOwn(this.manifest.phonemes, phoneme);
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Raw Int16 PCM bytes (48 kHz / mono) for a phoneme, or null if unknown.
|
|
959
|
+
* The returned ArrayBuffer is freshly allocated and safe to transfer to a
|
|
960
|
+
* worker / AudioWorklet.
|
|
961
|
+
*/
|
|
962
|
+
async readPcmBytes(phoneme) {
|
|
963
|
+
if (!Object.hasOwn(this.manifest.phonemes, phoneme)) return null;
|
|
964
|
+
const entry = this.manifest.phonemes[phoneme];
|
|
965
|
+
if (!Number.isInteger(entry.offset) || !Number.isInteger(entry.length) || entry.offset < 0 || entry.length < 0 || entry.length > MAX_PHONEME_SAMPLES) {
|
|
966
|
+
throw new Error(`manifest entry out of bounds for phoneme: ${phoneme}`);
|
|
586
967
|
}
|
|
968
|
+
return this.source.readBytes(entry.offset, entry.length * 2);
|
|
587
969
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
970
|
+
/**
|
|
971
|
+
* A phoneme's PCM as a Float64Array normalised to [-1, 1], or null if unknown.
|
|
972
|
+
* Intended for external analysis / resynthesis such as the WORLD vocoder.
|
|
973
|
+
*/
|
|
974
|
+
async getPcm(phoneme) {
|
|
975
|
+
const buf = await this.readPcmBytes(phoneme);
|
|
976
|
+
if (!buf) return null;
|
|
977
|
+
const int16 = new Int16Array(buf, 0, Math.floor(buf.byteLength / 2));
|
|
978
|
+
const f64 = new Float64Array(int16.length);
|
|
979
|
+
for (let i = 0; i < int16.length; i++) f64[i] = int16[i] / 32768;
|
|
980
|
+
return f64;
|
|
981
|
+
}
|
|
982
|
+
};
|
|
596
983
|
|
|
597
|
-
// src/
|
|
598
|
-
var
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
const length = slice.length;
|
|
609
|
-
const pre = clamp(msToSamples(oto.pre), 0, length);
|
|
610
|
-
const overlap = clamp(msToSamples(oto.overlap), 0, length);
|
|
611
|
-
const consonant = clamp(msToSamples(oto.consonant), 0, length);
|
|
612
|
-
const pitch = recordedPitch > 0 ? recordedPitch : detectF0(
|
|
613
|
-
slice,
|
|
614
|
-
Math.min(Math.max(pre, consonant), Math.max(0, length - 1)),
|
|
615
|
-
length
|
|
616
|
-
);
|
|
617
|
-
return {
|
|
618
|
-
pcm: slice,
|
|
619
|
-
entry: { length, pre, overlap, consonant, pitch }
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
function pack(inputs, referencePitch = 220) {
|
|
623
|
-
const phonemes = {};
|
|
624
|
-
const chunks = [];
|
|
625
|
-
let byteOffset = 0;
|
|
626
|
-
for (const { oto, pcm, recordedPitch } of inputs) {
|
|
627
|
-
const { pcm: slice, entry } = trimToOto(pcm, oto, recordedPitch);
|
|
628
|
-
if (slice.length === 0) continue;
|
|
629
|
-
phonemes[oto.alias] = { offset: byteOffset, ...entry };
|
|
630
|
-
byteOffset += slice.byteLength;
|
|
631
|
-
chunks.push(slice);
|
|
984
|
+
// src/engine/index.ts
|
|
985
|
+
var KoeEngine = class {
|
|
986
|
+
ctx;
|
|
987
|
+
workletUrl;
|
|
988
|
+
node = null;
|
|
989
|
+
bank = null;
|
|
990
|
+
delivered = /* @__PURE__ */ new Set();
|
|
991
|
+
pending = /* @__PURE__ */ new Map();
|
|
992
|
+
constructor(options = {}) {
|
|
993
|
+
this.ctx = new AudioContext({ sampleRate: 48e3 });
|
|
994
|
+
this.workletUrl = options.workletUrl ?? "./koe-worklet.js";
|
|
632
995
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
996
|
+
get audioContext() {
|
|
997
|
+
return this.ctx;
|
|
998
|
+
}
|
|
999
|
+
get manifest() {
|
|
1000
|
+
return this.bank?.manifest ?? null;
|
|
1001
|
+
}
|
|
1002
|
+
/** The underlying voice bank (manifest + on-demand PCM), or null before load(). */
|
|
1003
|
+
get voiceBank() {
|
|
1004
|
+
return this.bank;
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Register the worklet and bind a .koe voice bank.
|
|
1008
|
+
* @param koe a Blob/File of the .koe archive, or a URL (served with Range support)
|
|
1009
|
+
*/
|
|
1010
|
+
async load(koe) {
|
|
1011
|
+
await this.ctx.audioWorklet.addModule(this.workletUrl);
|
|
1012
|
+
this.node?.disconnect();
|
|
1013
|
+
this.node = null;
|
|
1014
|
+
this.bank = await VoiceBank.load(koe);
|
|
1015
|
+
this.delivered.clear();
|
|
1016
|
+
this.pending.clear();
|
|
1017
|
+
this.node = new AudioWorkletNode(this.ctx, "koe-processor", {
|
|
1018
|
+
numberOfInputs: 0,
|
|
1019
|
+
numberOfOutputs: 1,
|
|
1020
|
+
outputChannelCount: [1]
|
|
1021
|
+
});
|
|
1022
|
+
this.node.port.postMessage({ type: "init", manifest: this.bank.manifest });
|
|
1023
|
+
this.node.connect(this.ctx.destination);
|
|
1024
|
+
console.log(
|
|
1025
|
+
"[koe] ready \u2014",
|
|
1026
|
+
Object.keys(this.bank.manifest.phonemes).length,
|
|
1027
|
+
"phonemes (on-demand)"
|
|
640
1028
|
);
|
|
641
|
-
pos += chunk.byteLength;
|
|
642
1029
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
1030
|
+
/** Fetch one phoneme's PCM and deliver it to the worklet (deduped, cached). */
|
|
1031
|
+
ensurePhoneme(name) {
|
|
1032
|
+
if (this.delivered.has(name)) return Promise.resolve();
|
|
1033
|
+
const existing = this.pending.get(name);
|
|
1034
|
+
if (existing) return existing;
|
|
1035
|
+
if (!this.bank || !this.node) return Promise.resolve();
|
|
1036
|
+
const load = this.bank.readPcmBytes(name).then((buf) => {
|
|
1037
|
+
if (!buf || !this.node) return;
|
|
1038
|
+
this.node.port.postMessage({ type: "phoneme", name, buffer: buf }, [
|
|
1039
|
+
buf
|
|
1040
|
+
]);
|
|
1041
|
+
this.delivered.add(name);
|
|
1042
|
+
}).finally(() => {
|
|
1043
|
+
this.pending.delete(name);
|
|
1044
|
+
});
|
|
1045
|
+
this.pending.set(name, load);
|
|
1046
|
+
return load;
|
|
1047
|
+
}
|
|
1048
|
+
/** Stop current playback, preload the phonemes for `notes`, then queue them. */
|
|
1049
|
+
async play(notes) {
|
|
1050
|
+
if (!this.node) throw new Error("KoeEngine: call load() before play()");
|
|
1051
|
+
this.node.port.postMessage({ type: "stop" });
|
|
1052
|
+
const names = [...new Set(notes.map((n) => n.phoneme))].filter(Boolean);
|
|
1053
|
+
await Promise.all(names.map((n) => this.ensurePhoneme(n)));
|
|
1054
|
+
this.node.port.postMessage({ type: "play", notes });
|
|
1055
|
+
}
|
|
1056
|
+
/** Stop playback and clear the queue. */
|
|
1057
|
+
stop() {
|
|
1058
|
+
this.node?.port.postMessage({ type: "stop" });
|
|
1059
|
+
}
|
|
1060
|
+
/** Resume the AudioContext if suspended (e.g. after autoplay block). */
|
|
1061
|
+
async resume() {
|
|
1062
|
+
if (this.ctx.state === "suspended") await this.ctx.resume();
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Tear down the worklet node and close the AudioContext, releasing the audio
|
|
1066
|
+
* hardware. The engine cannot be reused afterwards — create a new one.
|
|
1067
|
+
*/
|
|
1068
|
+
async dispose() {
|
|
1069
|
+
this.node?.disconnect();
|
|
1070
|
+
this.node = null;
|
|
1071
|
+
this.bank = null;
|
|
1072
|
+
this.delivered.clear();
|
|
1073
|
+
this.pending.clear();
|
|
1074
|
+
if (this.ctx.state !== "closed") await this.ctx.close();
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Read a phoneme's raw PCM and return it as a Float64Array normalised to
|
|
1078
|
+
* [-1, 1]. Convenience that forwards to the underlying {@link VoiceBank}.
|
|
1079
|
+
* Intended for external analysis such as the WORLD vocoder.
|
|
1080
|
+
*/
|
|
1081
|
+
async getPcm(phoneme) {
|
|
1082
|
+
return this.bank?.getPcm(phoneme) ?? null;
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
// src/engine/worldline.ts
|
|
1087
|
+
var WORLDLINE_SAMPLE_RATE = 48e3;
|
|
1088
|
+
var MIN_WORLDLINE_SAMPLES = 4096;
|
|
1089
|
+
var SYNTH_REQ_SIZE = 120;
|
|
1090
|
+
var WL_FRAME_MS = 10;
|
|
1091
|
+
var sampleCurve = (input, curve, totalMs) => typeof curve === "function" ? curve(input, totalMs) : curve;
|
|
1092
|
+
var samplesToMs = (samples) => samples / WORLDLINE_SAMPLE_RATE * 1e3;
|
|
1093
|
+
function leadInFromEntry(entry) {
|
|
1094
|
+
return {
|
|
1095
|
+
preMs: samplesToMs(entry.pre || 0),
|
|
1096
|
+
consonantMs: samplesToMs(entry.consonant || 0)
|
|
647
1097
|
};
|
|
648
|
-
return { manifest, bin };
|
|
649
1098
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
1099
|
+
var moduleCache = /* @__PURE__ */ new Map();
|
|
1100
|
+
function injectScript(src) {
|
|
1101
|
+
return new Promise((resolve, reject) => {
|
|
1102
|
+
const existing = document.querySelector(
|
|
1103
|
+
`script[data-koe-worldline="${src}"]`
|
|
1104
|
+
);
|
|
1105
|
+
if (existing) {
|
|
1106
|
+
resolve();
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const s = document.createElement("script");
|
|
1110
|
+
s.src = src;
|
|
1111
|
+
s.dataset.koeWorldline = src;
|
|
1112
|
+
s.onload = () => resolve();
|
|
1113
|
+
s.onerror = () => reject(new Error(`worldline: failed to load ${src}`));
|
|
1114
|
+
document.head.appendChild(s);
|
|
1115
|
+
});
|
|
660
1116
|
}
|
|
661
|
-
function
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
|
|
1117
|
+
function loadWasm(scriptUrl) {
|
|
1118
|
+
const cached = moduleCache.get(scriptUrl);
|
|
1119
|
+
if (cached) return cached;
|
|
1120
|
+
const baseUrl = scriptUrl.slice(0, scriptUrl.lastIndexOf("/") + 1);
|
|
1121
|
+
const instantiate = () => {
|
|
1122
|
+
const factory = globalThis.WorldlineModule;
|
|
1123
|
+
if (!factory)
|
|
1124
|
+
throw new Error(
|
|
1125
|
+
"worldline: WorldlineModule global was not defined by the script"
|
|
1126
|
+
);
|
|
1127
|
+
return factory({ locateFile: (f) => baseUrl + f });
|
|
1128
|
+
};
|
|
1129
|
+
let promise;
|
|
1130
|
+
if (typeof document !== "undefined") {
|
|
1131
|
+
promise = injectScript(scriptUrl).then(instantiate);
|
|
1132
|
+
} else if (typeof globalThis.importScripts === "function") {
|
|
1133
|
+
promise = Promise.resolve().then(() => {
|
|
1134
|
+
globalThis.importScripts(scriptUrl);
|
|
1135
|
+
return instantiate();
|
|
1136
|
+
});
|
|
1137
|
+
} else {
|
|
1138
|
+
return Promise.reject(
|
|
1139
|
+
new Error(
|
|
1140
|
+
"Worldline.load requires a DOM or a classic Web Worker (importScripts) to load worldline.js"
|
|
1141
|
+
)
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
moduleCache.set(scriptUrl, promise);
|
|
1145
|
+
return promise;
|
|
666
1146
|
}
|
|
1147
|
+
var Worldline = class _Worldline {
|
|
1148
|
+
constructor(wasm) {
|
|
1149
|
+
this.wasm = wasm;
|
|
1150
|
+
}
|
|
1151
|
+
wasm;
|
|
1152
|
+
sampleRate = WORLDLINE_SAMPLE_RATE;
|
|
1153
|
+
/**
|
|
1154
|
+
* Load + instantiate the worldline WASM module (deduped per scriptUrl).
|
|
1155
|
+
*
|
|
1156
|
+
* Works on the main thread (loads via `<script>`) and inside a classic Web
|
|
1157
|
+
* Worker (loads via `importScripts`), so the heavy synthesis can run
|
|
1158
|
+
* off-thread. The matching `worldline.wasm` is fetched next to scriptUrl.
|
|
1159
|
+
*/
|
|
1160
|
+
static async load(options) {
|
|
1161
|
+
return new _Worldline(await loadWasm(options.scriptUrl));
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Render one note to Float32 PCM at 48 kHz.
|
|
1165
|
+
*
|
|
1166
|
+
* The output buffer is laid out as [lead-in/consonant ≈ preMs][vowel ≈
|
|
1167
|
+
* durationMs], rendered from sample offset 0 (no leading silence). The vowel
|
|
1168
|
+
* onset (the "beat") sits at ≈ preMs into the buffer, so a sequencer should
|
|
1169
|
+
* place the buffer at `beatTime − preMs` and may trim/crossfade the lead-in.
|
|
1170
|
+
*
|
|
1171
|
+
* No internal crossfade is applied — apply fades externally.
|
|
1172
|
+
*
|
|
1173
|
+
* @returns Float32 PCM, or null when `pcm` is shorter than
|
|
1174
|
+
* {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).
|
|
1175
|
+
*/
|
|
1176
|
+
renderNote(params) {
|
|
1177
|
+
const {
|
|
1178
|
+
pcm,
|
|
1179
|
+
pitch,
|
|
1180
|
+
durationMs,
|
|
1181
|
+
preMs,
|
|
1182
|
+
consonantMs,
|
|
1183
|
+
tempo = 120,
|
|
1184
|
+
gender = 0.5,
|
|
1185
|
+
tension = 0.5,
|
|
1186
|
+
breathiness = 0.5,
|
|
1187
|
+
voicing = 1
|
|
1188
|
+
} = params;
|
|
1189
|
+
if (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;
|
|
1190
|
+
const WL = this.wasm;
|
|
1191
|
+
const FS = WORLDLINE_SAMPLE_RATE;
|
|
1192
|
+
const basePitch = sampleCurve(preMs + durationMs / 2, pitch, preMs + durationMs);
|
|
1193
|
+
const midiNote = Math.round(69 + 12 * Math.log2(basePitch / 440));
|
|
1194
|
+
const posMs = 0;
|
|
1195
|
+
const reqLen = preMs + durationMs;
|
|
1196
|
+
const cutMs = WL_FRAME_MS * 2;
|
|
1197
|
+
const ps = WL._PhraseSynthNew();
|
|
1198
|
+
if (!ps) return null;
|
|
1199
|
+
const reqPtr = WL._malloc(SYNTH_REQ_SIZE);
|
|
1200
|
+
if (!reqPtr) {
|
|
1201
|
+
WL._PhraseSynthDelete(ps);
|
|
1202
|
+
return null;
|
|
1203
|
+
}
|
|
1204
|
+
const samplePtr = WL._malloc(pcm.length * 8);
|
|
1205
|
+
if (!samplePtr) {
|
|
1206
|
+
WL._free(reqPtr);
|
|
1207
|
+
WL._PhraseSynthDelete(ps);
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
WL.HEAPF64.set(pcm, samplePtr >> 3);
|
|
1211
|
+
const sv = (off, val, type) => WL.setValue(reqPtr + off, val, type);
|
|
1212
|
+
sv(0, FS, "i32");
|
|
1213
|
+
sv(4, pcm.length, "i32");
|
|
1214
|
+
sv(8, samplePtr, "*");
|
|
1215
|
+
sv(12, 0, "i32");
|
|
1216
|
+
sv(16, 0, "*");
|
|
1217
|
+
sv(20, midiNote, "i32");
|
|
1218
|
+
sv(24, 100, "double");
|
|
1219
|
+
sv(32, 0, "double");
|
|
1220
|
+
sv(40, reqLen, "double");
|
|
1221
|
+
sv(48, consonantMs, "double");
|
|
1222
|
+
sv(56, cutMs, "double");
|
|
1223
|
+
sv(64, 100, "double");
|
|
1224
|
+
sv(72, 0, "double");
|
|
1225
|
+
sv(80, tempo, "double");
|
|
1226
|
+
sv(88, 0, "i32");
|
|
1227
|
+
sv(92, 0, "*");
|
|
1228
|
+
sv(96, 0, "i32");
|
|
1229
|
+
sv(100, 0, "i32");
|
|
1230
|
+
sv(104, 100, "i32");
|
|
1231
|
+
sv(108, 0, "i32");
|
|
1232
|
+
sv(112, 0, "i32");
|
|
1233
|
+
sv(116, 100, "i32");
|
|
1234
|
+
WL._PhraseSynthAddRequest(ps, reqPtr, posMs, 0, reqLen, 0, 0, 0);
|
|
1235
|
+
WL._free(samplePtr);
|
|
1236
|
+
WL._free(reqPtr);
|
|
1237
|
+
const totalMs = posMs + reqLen + WL_FRAME_MS * 2;
|
|
1238
|
+
const nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;
|
|
1239
|
+
const f0Arr = new Float64Array(nFrames);
|
|
1240
|
+
const gArr = new Float64Array(nFrames);
|
|
1241
|
+
const tArr = new Float64Array(nFrames);
|
|
1242
|
+
const bArr = new Float64Array(nFrames);
|
|
1243
|
+
const vArr = new Float64Array(nFrames);
|
|
1244
|
+
for (let i = 0; i < nFrames; i++) {
|
|
1245
|
+
const tMs = i * WL_FRAME_MS;
|
|
1246
|
+
f0Arr[i] = sampleCurve(tMs, pitch, totalMs);
|
|
1247
|
+
gArr[i] = sampleCurve(tMs, gender, totalMs);
|
|
1248
|
+
tArr[i] = sampleCurve(tMs, tension, totalMs);
|
|
1249
|
+
bArr[i] = sampleCurve(tMs, breathiness, totalMs);
|
|
1250
|
+
vArr[i] = sampleCurve(tMs, voicing, totalMs);
|
|
1251
|
+
}
|
|
1252
|
+
const f0Ptr = WL._malloc(nFrames * 8);
|
|
1253
|
+
const gPtr = WL._malloc(nFrames * 8);
|
|
1254
|
+
const tPtr = WL._malloc(nFrames * 8);
|
|
1255
|
+
const bPtr = WL._malloc(nFrames * 8);
|
|
1256
|
+
const vPtr = WL._malloc(nFrames * 8);
|
|
1257
|
+
if (!f0Ptr || !gPtr || !tPtr || !bPtr || !vPtr) {
|
|
1258
|
+
if (f0Ptr) WL._free(f0Ptr);
|
|
1259
|
+
if (gPtr) WL._free(gPtr);
|
|
1260
|
+
if (tPtr) WL._free(tPtr);
|
|
1261
|
+
if (bPtr) WL._free(bPtr);
|
|
1262
|
+
if (vPtr) WL._free(vPtr);
|
|
1263
|
+
WL._PhraseSynthDelete(ps);
|
|
1264
|
+
return null;
|
|
1265
|
+
}
|
|
1266
|
+
WL.HEAPF64.set(f0Arr, f0Ptr >> 3);
|
|
1267
|
+
WL.HEAPF64.set(gArr, gPtr >> 3);
|
|
1268
|
+
WL.HEAPF64.set(tArr, tPtr >> 3);
|
|
1269
|
+
WL.HEAPF64.set(bArr, bPtr >> 3);
|
|
1270
|
+
WL.HEAPF64.set(vArr, vPtr >> 3);
|
|
1271
|
+
WL._PhraseSynthSetCurves(
|
|
1272
|
+
ps,
|
|
1273
|
+
f0Ptr,
|
|
1274
|
+
gPtr,
|
|
1275
|
+
tPtr,
|
|
1276
|
+
bPtr,
|
|
1277
|
+
vPtr,
|
|
1278
|
+
nFrames,
|
|
1279
|
+
WL_FRAME_MS
|
|
1280
|
+
);
|
|
1281
|
+
WL._free(f0Ptr);
|
|
1282
|
+
WL._free(gPtr);
|
|
1283
|
+
WL._free(tPtr);
|
|
1284
|
+
WL._free(bPtr);
|
|
1285
|
+
WL._free(vPtr);
|
|
1286
|
+
const yPtrPtr = WL._malloc(4);
|
|
1287
|
+
if (!yPtrPtr) {
|
|
1288
|
+
WL._PhraseSynthDelete(ps);
|
|
1289
|
+
return null;
|
|
1290
|
+
}
|
|
1291
|
+
const outLen = WL._PhraseSynthSynth(ps, yPtrPtr, 0);
|
|
1292
|
+
const yPtr = WL.getValue(yPtrPtr, "*");
|
|
1293
|
+
const audio = outLen > 0 && yPtr ? new Float32Array(WL.HEAPF32.buffer, yPtr, outLen).slice() : null;
|
|
1294
|
+
if (yPtr) WL._free(yPtr);
|
|
1295
|
+
WL._free(yPtrPtr);
|
|
1296
|
+
WL._PhraseSynthDelete(ps);
|
|
1297
|
+
return audio;
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
667
1300
|
export {
|
|
668
1301
|
KoeEngine,
|
|
669
1302
|
MIN_WORLDLINE_SAMPLES,
|
|
@@ -687,6 +1320,7 @@ export {
|
|
|
687
1320
|
samplesToMs,
|
|
688
1321
|
toInt16,
|
|
689
1322
|
toMono,
|
|
690
|
-
trimToOto
|
|
1323
|
+
trimToOto,
|
|
1324
|
+
unzipToFileMap
|
|
691
1325
|
};
|
|
692
1326
|
//# sourceMappingURL=index.js.map
|