@onjmin/koe 1.0.3 → 1.0.4
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 +115 -109
- package/dist/index.js +482 -393
- package/dist/index.js.map +1 -1
- package/dist/koe-convert.js +182 -154
- package/dist/koe-worklet.js +1 -1
- package/package.json +1 -1
package/dist/koe-convert.js
CHANGED
|
@@ -2,133 +2,35 @@
|
|
|
2
2
|
#!/usr/bin/env node
|
|
3
3
|
|
|
4
4
|
// src/converter/cli.ts
|
|
5
|
-
import { readdir, readFile, writeFile
|
|
6
|
-
import {
|
|
5
|
+
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
6
|
+
import { basename, dirname, join, resolve, sep } from "path";
|
|
7
7
|
|
|
8
|
-
// src/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const parts = line.slice(eq + 1).split(",");
|
|
18
|
-
if (parts.length < 6) continue;
|
|
19
|
-
const [alias, offsetStr, consonantStr, cutoffStr, preStr, overlapStr] = parts;
|
|
20
|
-
const aliasStr = alias.trim() || wav.replace(/\.[^.]+$/, "");
|
|
21
|
-
const entry = {
|
|
22
|
-
wav,
|
|
23
|
-
alias: aliasStr,
|
|
24
|
-
offset: parseFloat(offsetStr) || 0,
|
|
25
|
-
consonant: parseFloat(consonantStr) || 0,
|
|
26
|
-
cutoff: parseFloat(cutoffStr) || 0,
|
|
27
|
-
pre: parseFloat(preStr) || 0,
|
|
28
|
-
overlap: parseFloat(overlapStr) || 0
|
|
29
|
-
};
|
|
30
|
-
if (!entry.alias) continue;
|
|
31
|
-
entries.push(entry);
|
|
32
|
-
}
|
|
33
|
-
return entries;
|
|
8
|
+
// src/koe.ts
|
|
9
|
+
var MAGIC = 1263486208;
|
|
10
|
+
function packKoe(manifest, pcmParts) {
|
|
11
|
+
const json = new TextEncoder().encode(JSON.stringify(manifest));
|
|
12
|
+
const header = new ArrayBuffer(8);
|
|
13
|
+
const view = new DataView(header);
|
|
14
|
+
view.setUint32(0, MAGIC, false);
|
|
15
|
+
view.setUint32(4, json.byteLength, true);
|
|
16
|
+
return new Blob([header, json, ...pcmParts]);
|
|
34
17
|
}
|
|
35
18
|
|
|
36
|
-
// src/converter/
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
let
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
let dataOffset = 0;
|
|
46
|
-
let dataLength = 0;
|
|
47
|
-
let pos = 12;
|
|
48
|
-
while (pos < view.byteLength - 8) {
|
|
49
|
-
const id = readFourCC(view, pos);
|
|
50
|
-
const size = view.getUint32(pos + 4, true);
|
|
51
|
-
pos += 8;
|
|
52
|
-
if (id === "fmt ") {
|
|
53
|
-
audioFormat = view.getUint16(pos, true);
|
|
54
|
-
channels = view.getUint16(pos + 2, true);
|
|
55
|
-
sampleRate = view.getUint32(pos + 4, true);
|
|
56
|
-
bitsPerSample = view.getUint16(pos + 14, true);
|
|
57
|
-
} else if (id === "data") {
|
|
58
|
-
dataOffset = pos;
|
|
59
|
-
dataLength = size;
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
pos += size + (size & 1);
|
|
63
|
-
}
|
|
64
|
-
if (!dataOffset) throw new Error("WAV has no data chunk");
|
|
65
|
-
if (!channels || !sampleRate) throw new Error("WAV fmt chunk missing");
|
|
66
|
-
const bytesPerSample = bitsPerSample >> 3;
|
|
67
|
-
const totalSamples = Math.floor(dataLength / bytesPerSample);
|
|
68
|
-
const samples = new Float32Array(totalSamples);
|
|
69
|
-
for (let i = 0; i < totalSamples; i++) {
|
|
70
|
-
const p = dataOffset + i * bytesPerSample;
|
|
71
|
-
if (audioFormat === 3) {
|
|
72
|
-
samples[i] = view.getFloat32(p, true);
|
|
73
|
-
} else if (bitsPerSample === 8) {
|
|
74
|
-
samples[i] = (view.getUint8(p) - 128) / 128;
|
|
75
|
-
} else if (bitsPerSample === 16) {
|
|
76
|
-
samples[i] = view.getInt16(p, true) / 32768;
|
|
77
|
-
} else if (bitsPerSample === 24) {
|
|
78
|
-
const lo = view.getUint8(p) | view.getUint8(p + 1) << 8;
|
|
79
|
-
let hi = view.getUint8(p + 2);
|
|
80
|
-
if (hi & 128) hi = hi | 4294967040;
|
|
81
|
-
samples[i] = (hi << 16 | lo) / 8388608;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return { sampleRate, channels, samples };
|
|
85
|
-
}
|
|
86
|
-
function toMono(wav) {
|
|
87
|
-
if (wav.channels === 1) return wav;
|
|
88
|
-
const len = wav.samples.length / wav.channels;
|
|
89
|
-
const out = new Float32Array(len);
|
|
90
|
-
for (let i = 0; i < len; i++) {
|
|
91
|
-
let sum = 0;
|
|
92
|
-
for (let c = 0; c < wav.channels; c++)
|
|
93
|
-
sum += wav.samples[i * wav.channels + c];
|
|
94
|
-
out[i] = sum / wav.channels;
|
|
95
|
-
}
|
|
96
|
-
return { sampleRate: wav.sampleRate, channels: 1, samples: out };
|
|
97
|
-
}
|
|
98
|
-
function resample(wav, targetRate) {
|
|
99
|
-
if (wav.sampleRate === targetRate) return wav;
|
|
100
|
-
const ratio = wav.sampleRate / targetRate;
|
|
101
|
-
const outLen = Math.floor(wav.samples.length / ratio);
|
|
102
|
-
const out = new Float32Array(outLen);
|
|
103
|
-
const src = wav.samples;
|
|
104
|
-
for (let i = 0; i < outLen; i++) {
|
|
105
|
-
const x = i * ratio;
|
|
106
|
-
const xi = Math.floor(x);
|
|
107
|
-
const frac = x - xi;
|
|
108
|
-
out[i] = (src[xi] ?? 0) + ((src[xi + 1] ?? 0) - (src[xi] ?? 0)) * frac;
|
|
109
|
-
}
|
|
110
|
-
return { sampleRate: targetRate, channels: 1, samples: out };
|
|
111
|
-
}
|
|
112
|
-
function toInt16(samples) {
|
|
113
|
-
const out = new Int16Array(samples.length);
|
|
114
|
-
for (let i = 0; i < samples.length; i++) {
|
|
115
|
-
out[i] = Math.round(Math.max(-1, Math.min(1, samples[i])) * 32767);
|
|
116
|
-
}
|
|
117
|
-
return out;
|
|
118
|
-
}
|
|
119
|
-
function normalizePcm(buf) {
|
|
120
|
-
const wav = parseWav(buf);
|
|
121
|
-
const mono = toMono(wav);
|
|
122
|
-
const resampled = resample(mono, 48e3);
|
|
123
|
-
return toInt16(resampled.samples);
|
|
19
|
+
// src/converter/frq.ts
|
|
20
|
+
function parseFrqAverageF0(buffer) {
|
|
21
|
+
if (buffer.byteLength < 20) return null;
|
|
22
|
+
const view = new DataView(buffer);
|
|
23
|
+
let header = "";
|
|
24
|
+
for (let i = 0; i < 8; i++) header += String.fromCharCode(view.getUint8(i));
|
|
25
|
+
if (header !== "FREQ0003") return null;
|
|
26
|
+
const avg = view.getFloat64(12, true);
|
|
27
|
+
return Number.isFinite(avg) && avg > 0 ? avg : null;
|
|
124
28
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
view.getUint8(pos + 3)
|
|
131
|
-
);
|
|
29
|
+
function frqFileName(wavName) {
|
|
30
|
+
const dot = wavName.lastIndexOf(".");
|
|
31
|
+
const base = dot >= 0 ? wavName.slice(0, dot) : wavName;
|
|
32
|
+
const ext = dot >= 0 ? wavName.slice(dot + 1) : "wav";
|
|
33
|
+
return `${base}_${ext}.frq`;
|
|
132
34
|
}
|
|
133
35
|
|
|
134
36
|
// src/converter/pitch.ts
|
|
@@ -258,32 +160,139 @@ function pack(inputs, referencePitch = 220) {
|
|
|
258
160
|
return { manifest, bin };
|
|
259
161
|
}
|
|
260
162
|
|
|
261
|
-
// src/converter/
|
|
262
|
-
function
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
163
|
+
// src/converter/parse-oto.ts
|
|
164
|
+
function parseOto(content) {
|
|
165
|
+
const entries = [];
|
|
166
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
167
|
+
const line = raw.trim();
|
|
168
|
+
if (!line || line.startsWith("#")) continue;
|
|
169
|
+
const eq = line.indexOf("=");
|
|
170
|
+
if (eq === -1) continue;
|
|
171
|
+
const wav = line.slice(0, eq).trim();
|
|
172
|
+
const parts = line.slice(eq + 1).split(",");
|
|
173
|
+
if (parts.length < 6) continue;
|
|
174
|
+
const [alias, offsetStr, consonantStr, cutoffStr, preStr, overlapStr] = parts;
|
|
175
|
+
const aliasStr = alias.trim() || wav.replace(/\.[^.]+$/, "");
|
|
176
|
+
const entry = {
|
|
177
|
+
wav,
|
|
178
|
+
alias: aliasStr,
|
|
179
|
+
offset: parseFloat(offsetStr) || 0,
|
|
180
|
+
consonant: parseFloat(consonantStr) || 0,
|
|
181
|
+
cutoff: parseFloat(cutoffStr) || 0,
|
|
182
|
+
pre: parseFloat(preStr) || 0,
|
|
183
|
+
overlap: parseFloat(overlapStr) || 0
|
|
184
|
+
};
|
|
185
|
+
if (!entry.alias) continue;
|
|
186
|
+
entries.push(entry);
|
|
187
|
+
}
|
|
188
|
+
return entries;
|
|
276
189
|
}
|
|
277
190
|
|
|
278
|
-
// src/
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
191
|
+
// src/converter/wav.ts
|
|
192
|
+
function parseWav(buf) {
|
|
193
|
+
const view = new DataView(buf);
|
|
194
|
+
const riff = readFourCC(view, 0);
|
|
195
|
+
if (riff !== "RIFF") throw new Error(`Not a RIFF file (got "${riff}")`);
|
|
196
|
+
let sampleRate = 0;
|
|
197
|
+
let channels = 0;
|
|
198
|
+
let bitsPerSample = 0;
|
|
199
|
+
let audioFormat = 1;
|
|
200
|
+
let dataOffset = 0;
|
|
201
|
+
let dataLength = 0;
|
|
202
|
+
let pos = 12;
|
|
203
|
+
while (pos < view.byteLength - 8) {
|
|
204
|
+
const id = readFourCC(view, pos);
|
|
205
|
+
const size = view.getUint32(pos + 4, true);
|
|
206
|
+
pos += 8;
|
|
207
|
+
if (id === "fmt ") {
|
|
208
|
+
audioFormat = view.getUint16(pos, true);
|
|
209
|
+
channels = view.getUint16(pos + 2, true);
|
|
210
|
+
sampleRate = view.getUint32(pos + 4, true);
|
|
211
|
+
bitsPerSample = view.getUint16(pos + 14, true);
|
|
212
|
+
if (audioFormat === 65534 && size >= 40) {
|
|
213
|
+
audioFormat = view.getUint16(pos + 24, true);
|
|
214
|
+
}
|
|
215
|
+
} else if (id === "data") {
|
|
216
|
+
dataOffset = pos;
|
|
217
|
+
dataLength = Math.min(size, view.byteLength - pos);
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
pos += size + (size & 1);
|
|
221
|
+
}
|
|
222
|
+
if (!dataOffset) throw new Error("WAV has no data chunk");
|
|
223
|
+
if (!channels || !sampleRate) throw new Error("WAV fmt chunk missing");
|
|
224
|
+
const supported = audioFormat === 3 && bitsPerSample === 32 || audioFormat === 1 && (bitsPerSample === 8 || bitsPerSample === 16 || bitsPerSample === 24);
|
|
225
|
+
if (!supported) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Unsupported WAV format ${audioFormat} / ${bitsPerSample}-bit (need PCM 8/16/24-bit or IEEE float 32-bit)`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const bytesPerSample = bitsPerSample >> 3;
|
|
231
|
+
const totalSamples = Math.floor(dataLength / bytesPerSample);
|
|
232
|
+
const samples = new Float32Array(totalSamples);
|
|
233
|
+
for (let i = 0; i < totalSamples; i++) {
|
|
234
|
+
const p = dataOffset + i * bytesPerSample;
|
|
235
|
+
if (audioFormat === 3) {
|
|
236
|
+
samples[i] = view.getFloat32(p, true);
|
|
237
|
+
} else if (bitsPerSample === 8) {
|
|
238
|
+
samples[i] = (view.getUint8(p) - 128) / 128;
|
|
239
|
+
} else if (bitsPerSample === 16) {
|
|
240
|
+
samples[i] = view.getInt16(p, true) / 32768;
|
|
241
|
+
} else if (bitsPerSample === 24) {
|
|
242
|
+
const lo = view.getUint8(p) | view.getUint8(p + 1) << 8;
|
|
243
|
+
let hi = view.getUint8(p + 2);
|
|
244
|
+
if (hi & 128) hi = hi | 4294967040;
|
|
245
|
+
samples[i] = (hi << 16 | lo) / 8388608;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return { sampleRate, channels, samples };
|
|
249
|
+
}
|
|
250
|
+
function toMono(wav) {
|
|
251
|
+
if (wav.channels === 1) return wav;
|
|
252
|
+
const len = wav.samples.length / wav.channels;
|
|
253
|
+
const out = new Float32Array(len);
|
|
254
|
+
for (let i = 0; i < len; i++) {
|
|
255
|
+
let sum = 0;
|
|
256
|
+
for (let c = 0; c < wav.channels; c++)
|
|
257
|
+
sum += wav.samples[i * wav.channels + c];
|
|
258
|
+
out[i] = sum / wav.channels;
|
|
259
|
+
}
|
|
260
|
+
return { sampleRate: wav.sampleRate, channels: 1, samples: out };
|
|
261
|
+
}
|
|
262
|
+
function resample(wav, targetRate) {
|
|
263
|
+
if (wav.sampleRate === targetRate) return wav;
|
|
264
|
+
const ratio = wav.sampleRate / targetRate;
|
|
265
|
+
const outLen = Math.floor(wav.samples.length / ratio);
|
|
266
|
+
const out = new Float32Array(outLen);
|
|
267
|
+
const src = wav.samples;
|
|
268
|
+
for (let i = 0; i < outLen; i++) {
|
|
269
|
+
const x = i * ratio;
|
|
270
|
+
const xi = Math.floor(x);
|
|
271
|
+
const frac = x - xi;
|
|
272
|
+
out[i] = (src[xi] ?? 0) + ((src[xi + 1] ?? 0) - (src[xi] ?? 0)) * frac;
|
|
273
|
+
}
|
|
274
|
+
return { sampleRate: targetRate, channels: 1, samples: out };
|
|
275
|
+
}
|
|
276
|
+
function toInt16(samples) {
|
|
277
|
+
const out = new Int16Array(samples.length);
|
|
278
|
+
for (let i = 0; i < samples.length; i++) {
|
|
279
|
+
out[i] = Math.round(Math.max(-1, Math.min(1, samples[i])) * 32767);
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
function normalizePcm(buf) {
|
|
284
|
+
const wav = parseWav(buf);
|
|
285
|
+
const mono = toMono(wav);
|
|
286
|
+
const resampled = resample(mono, 48e3);
|
|
287
|
+
return toInt16(resampled.samples);
|
|
288
|
+
}
|
|
289
|
+
function readFourCC(view, pos) {
|
|
290
|
+
return String.fromCharCode(
|
|
291
|
+
view.getUint8(pos),
|
|
292
|
+
view.getUint8(pos + 1),
|
|
293
|
+
view.getUint8(pos + 2),
|
|
294
|
+
view.getUint8(pos + 3)
|
|
295
|
+
);
|
|
287
296
|
}
|
|
288
297
|
|
|
289
298
|
// src/converter/cli.ts
|
|
@@ -292,6 +301,20 @@ if (!voiceDir) {
|
|
|
292
301
|
process.stderr.write("Usage: koe-convert <voice-dir> [output-dir]\n");
|
|
293
302
|
process.exit(1);
|
|
294
303
|
}
|
|
304
|
+
function resolveInside(dir, name) {
|
|
305
|
+
const root = resolve(dir);
|
|
306
|
+
const path = resolve(root, name);
|
|
307
|
+
if (path !== root && !path.startsWith(root + sep)) {
|
|
308
|
+
throw new Error(`path escapes voice bank directory: ${name}`);
|
|
309
|
+
}
|
|
310
|
+
return path;
|
|
311
|
+
}
|
|
312
|
+
function toArrayBuffer(buf) {
|
|
313
|
+
return buf.buffer.slice(
|
|
314
|
+
buf.byteOffset,
|
|
315
|
+
buf.byteOffset + buf.byteLength
|
|
316
|
+
);
|
|
317
|
+
}
|
|
295
318
|
async function findOtoFiles(dir) {
|
|
296
319
|
const found = [];
|
|
297
320
|
const entries = await readdir(dir, { withFileTypes: true, recursive: true });
|
|
@@ -314,17 +337,22 @@ async function main() {
|
|
|
314
337
|
for (const otoPath of otoFiles) {
|
|
315
338
|
const otoDir = dirname(otoPath);
|
|
316
339
|
const rawBytes = await readFile(otoPath);
|
|
317
|
-
const
|
|
340
|
+
const isUtf8Bom = rawBytes[0] === 239 && rawBytes[1] === 187 && rawBytes[2] === 191;
|
|
341
|
+
const content = new TextDecoder(isUtf8Bom ? "utf-8" : "shift_jis").decode(
|
|
342
|
+
rawBytes
|
|
343
|
+
);
|
|
318
344
|
const entries = parseOto(content);
|
|
319
345
|
for (const oto of entries) {
|
|
320
|
-
const wavPath = join(otoDir, oto.wav);
|
|
321
346
|
try {
|
|
347
|
+
const wavPath = resolveInside(otoDir, oto.wav);
|
|
322
348
|
const wavBytes = await readFile(wavPath);
|
|
323
|
-
const pcm = normalizePcm(wavBytes
|
|
349
|
+
const pcm = normalizePcm(toArrayBuffer(wavBytes));
|
|
324
350
|
let recordedPitch = pitchFromAliasSuffix(oto.alias) ?? 0;
|
|
325
351
|
try {
|
|
326
|
-
const frqBytes = await readFile(
|
|
327
|
-
|
|
352
|
+
const frqBytes = await readFile(
|
|
353
|
+
resolveInside(otoDir, frqFileName(oto.wav))
|
|
354
|
+
);
|
|
355
|
+
const avg = parseFrqAverageF0(toArrayBuffer(frqBytes));
|
|
328
356
|
if (avg) recordedPitch = avg;
|
|
329
357
|
} catch {
|
|
330
358
|
}
|
|
@@ -332,7 +360,7 @@ async function main() {
|
|
|
332
360
|
} catch (err) {
|
|
333
361
|
const msg = err instanceof Error ? err.message : String(err);
|
|
334
362
|
process.stderr.write(
|
|
335
|
-
`[skip] ${oto.alias} (${basename(
|
|
363
|
+
`[skip] ${oto.alias} (${basename(oto.wav)}): ${msg}
|
|
336
364
|
`
|
|
337
365
|
);
|
|
338
366
|
skipped++;
|
|
@@ -344,7 +372,7 @@ async function main() {
|
|
|
344
372
|
process.exit(1);
|
|
345
373
|
}
|
|
346
374
|
const { manifest, bin } = pack(inputs);
|
|
347
|
-
const koeName = basename(resolve(voiceDir))
|
|
375
|
+
const koeName = `${basename(resolve(voiceDir))}.koe`;
|
|
348
376
|
const koe = packKoe(manifest, [bin]);
|
|
349
377
|
const koeBytes = Buffer.from(await koe.arrayBuffer());
|
|
350
378
|
await mkdir(outDir, { recursive: true });
|
package/dist/koe-worklet.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";(()=>{var
|
|
1
|
+
"use strict";(()=>{var f=new Int16Array(0),u=class extends AudioWorkletProcessor{manifest=null;phonemes=new Map;queue=[];current=null;next=null;constructor(e){super(e),this.port.onmessage=t=>this.onMessage(t.data)}onMessage(e){e.type==="init"&&e.manifest?this.manifest=e.manifest:e.type==="phoneme"&&e.name&&e.buffer?this.phonemes.set(e.name,new Int16Array(e.buffer,0,Math.floor(e.buffer.byteLength/2))):e.type==="play"&&e.notes?this.queue.push(...e.notes):e.type==="stop"&&(this.queue=[],this.current=null,this.next=null)}dequeue(){let e=this.queue.shift();if(!e||!this.manifest)return null;if(e.phoneme==="")return{data:f,length:0,consonant:0,pre:0,overlap:0,readPos:0,stepRate:1,remaining:e.duration};let t=Object.hasOwn(this.manifest.phonemes,e.phoneme)?this.manifest.phonemes[e.phoneme]:void 0,n=this.phonemes.get(e.phoneme);if(!t||!n)return null;let i=t.pitch||this.manifest.referencePitch;return{data:n,length:t.length,consonant:t.consonant,pre:t.pre,overlap:t.overlap,readPos:0,stepRate:e.pitch/i,remaining:e.duration}}readSample(e,t){let n=t|0,i=t-n;return((e[n]??0)+((e[n+1]??0)-(e[n]??0))*i)/32768}advance(e){if(!(e.length<=0)&&(e.readPos+=e.stepRate,e.readPos>=e.length)){let t=e.length-e.consonant;e.readPos=t>0?e.consonant+(e.readPos-e.consonant)%t:e.length-1}}process(e,t){let n=t[0]?.[0];if(!n)return!0;for(let i=0;i<n.length;i++){if(!this.current||this.current.remaining<=0){let s=this.next!==null;if(this.current=this.next??this.dequeue(),this.next=null,!this.current){n[i]=0;continue}s||(this.current.readPos=this.current.pre)}let r=this.current;!this.next&&this.queue.length>0&&(this.next=this.dequeue());let o=this.readSample(r.data,r.readPos);if(!this.next&&this.queue.length===0&&r.remaining<=480&&r.length>0&&(o*=r.remaining/480),this.next){let s=this.next,a=Math.max(s.pre/s.stepRate,480);if(r.remaining<=a){let c=Math.min(Math.max(s.overlap/s.stepRate,480),a),l=a-r.remaining,h=Math.min(1,l/c),p=this.readSample(s.data,s.readPos);o=o*(1-h)+p*h,this.advance(s)}}n[i]=o,this.advance(r),r.remaining--}return!0}};registerProcessor("koe-processor",u);})();
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "onjmin",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@onjmin/koe",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.4",
|
|
6
6
|
"description": "UTAU concatenative synthesis engine optimized for browser / mobile via WebAssembly + AudioWorklet",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./dist/index.js",
|