@onjmin/koe 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -5
- package/dist/index.d.ts +421 -11
- package/dist/index.js +1885 -14
- package/dist/index.js.map +1 -1
- package/dist/koe-convert.js +71 -14
- package/dist/koe-oto.js +1399 -0
- package/dist/koe-worklet.js +1 -1
- package/package.json +4 -2
package/dist/koe-convert.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
#!/usr/bin/env node
|
|
3
2
|
|
|
4
3
|
// src/converter/cli.ts
|
|
5
4
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
@@ -17,14 +16,48 @@ function packKoe(manifest, pcmParts) {
|
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
// src/converter/frq.ts
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
var HEADER_SIZE = 40;
|
|
20
|
+
function parseFrq(buffer) {
|
|
21
|
+
if (buffer.byteLength < HEADER_SIZE) return null;
|
|
22
22
|
const view = new DataView(buffer);
|
|
23
23
|
let header = "";
|
|
24
24
|
for (let i = 0; i < 8; i++) header += String.fromCharCode(view.getUint8(i));
|
|
25
25
|
if (header !== "FREQ0003") return null;
|
|
26
|
-
const
|
|
27
|
-
|
|
26
|
+
const hopSize = view.getInt32(8, true);
|
|
27
|
+
const averageF0 = view.getFloat64(12, true);
|
|
28
|
+
const declared = view.getInt32(36, true);
|
|
29
|
+
const available = Math.floor((buffer.byteLength - HEADER_SIZE) / 16);
|
|
30
|
+
const length = Math.max(0, Math.min(declared, available));
|
|
31
|
+
const f0 = new Float64Array(length);
|
|
32
|
+
const amp = new Float64Array(length);
|
|
33
|
+
for (let i = 0; i < length; i++) {
|
|
34
|
+
const p = HEADER_SIZE + i * 16;
|
|
35
|
+
f0[i] = view.getFloat64(p, true);
|
|
36
|
+
amp[i] = view.getFloat64(p + 8, true);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
hopSize: hopSize > 0 ? hopSize : 256,
|
|
40
|
+
averageF0: Number.isFinite(averageF0) && averageF0 > 0 ? averageF0 : 0,
|
|
41
|
+
f0,
|
|
42
|
+
amp
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function frqAverageF0InRange(frq, startMs, endMs, sourceRate) {
|
|
46
|
+
if (!(sourceRate > 0) || frq.f0.length === 0) return 0;
|
|
47
|
+
const perFrameMs = frq.hopSize / sourceRate * 1e3;
|
|
48
|
+
if (!(perFrameMs > 0)) return 0;
|
|
49
|
+
const first = Math.max(0, Math.floor(startMs / perFrameMs));
|
|
50
|
+
const last = Math.min(frq.f0.length - 1, Math.ceil(endMs / perFrameMs));
|
|
51
|
+
let num = 0;
|
|
52
|
+
let den = 0;
|
|
53
|
+
for (let i = first; i <= last; i++) {
|
|
54
|
+
const hz = frq.f0[i];
|
|
55
|
+
if (!(hz > 0)) continue;
|
|
56
|
+
const w = frq.amp[i] > 0 ? frq.amp[i] : 1;
|
|
57
|
+
num += hz * w;
|
|
58
|
+
den += w;
|
|
59
|
+
}
|
|
60
|
+
return den > 0 ? num / den : 0;
|
|
28
61
|
}
|
|
29
62
|
function frqFileName(wavName) {
|
|
30
63
|
const dot = wavName.lastIndexOf(".");
|
|
@@ -112,11 +145,25 @@ function msToSamples(ms) {
|
|
|
112
145
|
return Math.round(ms / 1e3 * TARGET_RATE);
|
|
113
146
|
}
|
|
114
147
|
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
148
|
+
function otoRegion(pcmLength, oto) {
|
|
149
|
+
const start = clamp(msToSamples(oto.offset), 0, pcmLength);
|
|
150
|
+
const end = oto.cutoff < 0 ? clamp(start + msToSamples(-oto.cutoff), start, pcmLength) : clamp(pcmLength - msToSamples(oto.cutoff), start, pcmLength);
|
|
151
|
+
return { start, end };
|
|
152
|
+
}
|
|
153
|
+
function centerSlice(slice) {
|
|
154
|
+
const out = new Int16Array(slice.length);
|
|
155
|
+
if (slice.length === 0) return out;
|
|
156
|
+
let sum = 0;
|
|
157
|
+
for (let i = 0; i < slice.length; i++) sum += slice[i];
|
|
158
|
+
const dc = Math.round(sum / slice.length);
|
|
159
|
+
for (let i = 0; i < slice.length; i++) {
|
|
160
|
+
out[i] = clamp(slice[i] - dc, -32768, 32767);
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
115
164
|
function trimToOto(pcm, oto, recordedPitch = 0) {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const end = oto.cutoff < 0 ? clamp(start + msToSamples(-oto.cutoff), start, full) : clamp(full - msToSamples(oto.cutoff), start, full);
|
|
119
|
-
const slice = pcm.subarray(start, end);
|
|
165
|
+
const { start, end } = otoRegion(pcm.length, oto);
|
|
166
|
+
const slice = centerSlice(pcm.subarray(start, end));
|
|
120
167
|
const length = slice.length;
|
|
121
168
|
const pre = clamp(msToSamples(oto.pre), 0, length);
|
|
122
169
|
const overlap = clamp(msToSamples(oto.overlap), 0, length);
|
|
@@ -280,11 +327,11 @@ function toInt16(samples) {
|
|
|
280
327
|
}
|
|
281
328
|
return out;
|
|
282
329
|
}
|
|
283
|
-
function
|
|
330
|
+
function readWavPcm48k(buf) {
|
|
284
331
|
const wav = parseWav(buf);
|
|
285
332
|
const mono = toMono(wav);
|
|
286
333
|
const resampled = resample(mono, 48e3);
|
|
287
|
-
return toInt16(resampled.samples);
|
|
334
|
+
return { pcm: toInt16(resampled.samples), sourceRate: wav.sampleRate };
|
|
288
335
|
}
|
|
289
336
|
function readFourCC(view, pos) {
|
|
290
337
|
return String.fromCharCode(
|
|
@@ -346,14 +393,24 @@ async function main() {
|
|
|
346
393
|
try {
|
|
347
394
|
const wavPath = resolveInside(otoDir, oto.wav);
|
|
348
395
|
const wavBytes = await readFile(wavPath);
|
|
349
|
-
const pcm =
|
|
396
|
+
const { pcm, sourceRate } = readWavPcm48k(toArrayBuffer(wavBytes));
|
|
350
397
|
let recordedPitch = pitchFromAliasSuffix(oto.alias) ?? 0;
|
|
351
398
|
try {
|
|
352
399
|
const frqBytes = await readFile(
|
|
353
400
|
resolveInside(otoDir, frqFileName(oto.wav))
|
|
354
401
|
);
|
|
355
|
-
const
|
|
356
|
-
if (
|
|
402
|
+
const frq = parseFrq(toArrayBuffer(frqBytes));
|
|
403
|
+
if (frq) {
|
|
404
|
+
const { start, end } = otoRegion(pcm.length, oto);
|
|
405
|
+
const toMs = (samples) => samples / 48e3 * 1e3;
|
|
406
|
+
const local = frqAverageF0InRange(
|
|
407
|
+
frq,
|
|
408
|
+
toMs(start) + oto.pre,
|
|
409
|
+
toMs(end),
|
|
410
|
+
sourceRate
|
|
411
|
+
) || frqAverageF0InRange(frq, toMs(start), toMs(end), sourceRate) || frq.averageF0;
|
|
412
|
+
if (local > 0) recordedPitch = local;
|
|
413
|
+
}
|
|
357
414
|
} catch {
|
|
358
415
|
}
|
|
359
416
|
inputs.push({ oto, pcm, recordedPitch });
|