@onjmin/koe 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -5
- package/dist/index.d.ts +421 -11
- package/dist/index.js +1885 -14
- package/dist/index.js.map +1 -1
- package/dist/koe-convert.js +71 -14
- package/dist/koe-oto.js +1399 -0
- package/dist/koe-worklet.js +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,12 +1,50 @@
|
|
|
1
1
|
// src/converter/frq.ts
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
var HEADER_SIZE = 40;
|
|
3
|
+
function parseFrq(buffer) {
|
|
4
|
+
if (buffer.byteLength < HEADER_SIZE) return null;
|
|
4
5
|
const view = new DataView(buffer);
|
|
5
6
|
let header = "";
|
|
6
7
|
for (let i = 0; i < 8; i++) header += String.fromCharCode(view.getUint8(i));
|
|
7
8
|
if (header !== "FREQ0003") return null;
|
|
8
|
-
const
|
|
9
|
-
|
|
9
|
+
const hopSize = view.getInt32(8, true);
|
|
10
|
+
const averageF0 = view.getFloat64(12, true);
|
|
11
|
+
const declared = view.getInt32(36, true);
|
|
12
|
+
const available = Math.floor((buffer.byteLength - HEADER_SIZE) / 16);
|
|
13
|
+
const length = Math.max(0, Math.min(declared, available));
|
|
14
|
+
const f0 = new Float64Array(length);
|
|
15
|
+
const amp = new Float64Array(length);
|
|
16
|
+
for (let i = 0; i < length; i++) {
|
|
17
|
+
const p = HEADER_SIZE + i * 16;
|
|
18
|
+
f0[i] = view.getFloat64(p, true);
|
|
19
|
+
amp[i] = view.getFloat64(p + 8, true);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
hopSize: hopSize > 0 ? hopSize : 256,
|
|
23
|
+
averageF0: Number.isFinite(averageF0) && averageF0 > 0 ? averageF0 : 0,
|
|
24
|
+
f0,
|
|
25
|
+
amp
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function frqAverageF0InRange(frq, startMs, endMs, sourceRate) {
|
|
29
|
+
if (!(sourceRate > 0) || frq.f0.length === 0) return 0;
|
|
30
|
+
const perFrameMs = frq.hopSize / sourceRate * 1e3;
|
|
31
|
+
if (!(perFrameMs > 0)) return 0;
|
|
32
|
+
const first = Math.max(0, Math.floor(startMs / perFrameMs));
|
|
33
|
+
const last = Math.min(frq.f0.length - 1, Math.ceil(endMs / perFrameMs));
|
|
34
|
+
let num2 = 0;
|
|
35
|
+
let den = 0;
|
|
36
|
+
for (let i = first; i <= last; i++) {
|
|
37
|
+
const hz = frq.f0[i];
|
|
38
|
+
if (!(hz > 0)) continue;
|
|
39
|
+
const w = frq.amp[i] > 0 ? frq.amp[i] : 1;
|
|
40
|
+
num2 += hz * w;
|
|
41
|
+
den += w;
|
|
42
|
+
}
|
|
43
|
+
return den > 0 ? num2 / den : 0;
|
|
44
|
+
}
|
|
45
|
+
function parseFrqAverageF0(buffer) {
|
|
46
|
+
const frq = parseFrq(buffer);
|
|
47
|
+
return frq && frq.averageF0 > 0 ? frq.averageF0 : null;
|
|
10
48
|
}
|
|
11
49
|
function frqFileName(wavName) {
|
|
12
50
|
const dot = wavName.lastIndexOf(".");
|
|
@@ -94,11 +132,25 @@ function msToSamples(ms) {
|
|
|
94
132
|
return Math.round(ms / 1e3 * TARGET_RATE);
|
|
95
133
|
}
|
|
96
134
|
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
135
|
+
function otoRegion(pcmLength, oto) {
|
|
136
|
+
const start = clamp(msToSamples(oto.offset), 0, pcmLength);
|
|
137
|
+
const end = oto.cutoff < 0 ? clamp(start + msToSamples(-oto.cutoff), start, pcmLength) : clamp(pcmLength - msToSamples(oto.cutoff), start, pcmLength);
|
|
138
|
+
return { start, end };
|
|
139
|
+
}
|
|
140
|
+
function centerSlice(slice) {
|
|
141
|
+
const out = new Int16Array(slice.length);
|
|
142
|
+
if (slice.length === 0) return out;
|
|
143
|
+
let sum = 0;
|
|
144
|
+
for (let i = 0; i < slice.length; i++) sum += slice[i];
|
|
145
|
+
const dc = Math.round(sum / slice.length);
|
|
146
|
+
for (let i = 0; i < slice.length; i++) {
|
|
147
|
+
out[i] = clamp(slice[i] - dc, -32768, 32767);
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
97
151
|
function trimToOto(pcm, oto, recordedPitch = 0) {
|
|
98
|
-
const
|
|
99
|
-
const
|
|
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);
|
|
152
|
+
const { start, end } = otoRegion(pcm.length, oto);
|
|
153
|
+
const slice = centerSlice(pcm.subarray(start, end));
|
|
102
154
|
const length = slice.length;
|
|
103
155
|
const pre = clamp(msToSamples(oto.pre), 0, length);
|
|
104
156
|
const overlap = clamp(msToSamples(oto.overlap), 0, length);
|
|
@@ -262,11 +314,14 @@ function toInt16(samples) {
|
|
|
262
314
|
}
|
|
263
315
|
return out;
|
|
264
316
|
}
|
|
265
|
-
function
|
|
317
|
+
function readWavPcm48k(buf) {
|
|
266
318
|
const wav = parseWav(buf);
|
|
267
319
|
const mono = toMono(wav);
|
|
268
320
|
const resampled = resample(mono, 48e3);
|
|
269
|
-
return toInt16(resampled.samples);
|
|
321
|
+
return { pcm: toInt16(resampled.samples), sourceRate: wav.sampleRate };
|
|
322
|
+
}
|
|
323
|
+
function normalizePcm(buf) {
|
|
324
|
+
return readWavPcm48k(buf).pcm;
|
|
270
325
|
}
|
|
271
326
|
function readFourCC(view, pos) {
|
|
272
327
|
return String.fromCharCode(
|
|
@@ -435,7 +490,9 @@ var fdt = new u8(32);
|
|
|
435
490
|
for (i = 0; i < 32; ++i)
|
|
436
491
|
fdt[i] = 5;
|
|
437
492
|
var i;
|
|
493
|
+
var flm = /* @__PURE__ */ hMap(flt, 9, 0);
|
|
438
494
|
var flrm = /* @__PURE__ */ hMap(flt, 9, 1);
|
|
495
|
+
var fdm = /* @__PURE__ */ hMap(fdt, 5, 0);
|
|
439
496
|
var fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
|
|
440
497
|
var max = function(a) {
|
|
441
498
|
var m = a[0];
|
|
@@ -631,7 +688,353 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
631
688
|
} while (!final);
|
|
632
689
|
return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
|
|
633
690
|
};
|
|
691
|
+
var wbits = function(d, p, v) {
|
|
692
|
+
v <<= p & 7;
|
|
693
|
+
var o = p / 8 | 0;
|
|
694
|
+
d[o] |= v;
|
|
695
|
+
d[o + 1] |= v >> 8;
|
|
696
|
+
};
|
|
697
|
+
var wbits16 = function(d, p, v) {
|
|
698
|
+
v <<= p & 7;
|
|
699
|
+
var o = p / 8 | 0;
|
|
700
|
+
d[o] |= v;
|
|
701
|
+
d[o + 1] |= v >> 8;
|
|
702
|
+
d[o + 2] |= v >> 16;
|
|
703
|
+
};
|
|
704
|
+
var hTree = function(d, mb) {
|
|
705
|
+
var t = [];
|
|
706
|
+
for (var i = 0; i < d.length; ++i) {
|
|
707
|
+
if (d[i])
|
|
708
|
+
t.push({ s: i, f: d[i] });
|
|
709
|
+
}
|
|
710
|
+
var s = t.length;
|
|
711
|
+
var t2 = t.slice();
|
|
712
|
+
if (!s)
|
|
713
|
+
return { t: et, l: 0 };
|
|
714
|
+
if (s == 1) {
|
|
715
|
+
var v = new u8(t[0].s + 1);
|
|
716
|
+
v[t[0].s] = 1;
|
|
717
|
+
return { t: v, l: 1 };
|
|
718
|
+
}
|
|
719
|
+
t.sort(function(a, b) {
|
|
720
|
+
return a.f - b.f;
|
|
721
|
+
});
|
|
722
|
+
t.push({ s: -1, f: 25001 });
|
|
723
|
+
var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;
|
|
724
|
+
t[0] = { s: -1, f: l.f + r.f, l, r };
|
|
725
|
+
while (i1 != s - 1) {
|
|
726
|
+
l = t[t[i0].f < t[i2].f ? i0++ : i2++];
|
|
727
|
+
r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];
|
|
728
|
+
t[i1++] = { s: -1, f: l.f + r.f, l, r };
|
|
729
|
+
}
|
|
730
|
+
var maxSym = t2[0].s;
|
|
731
|
+
for (var i = 1; i < s; ++i) {
|
|
732
|
+
if (t2[i].s > maxSym)
|
|
733
|
+
maxSym = t2[i].s;
|
|
734
|
+
}
|
|
735
|
+
var tr = new u16(maxSym + 1);
|
|
736
|
+
var mbt = ln(t[i1 - 1], tr, 0);
|
|
737
|
+
if (mbt > mb) {
|
|
738
|
+
var i = 0, dt = 0;
|
|
739
|
+
var lft = mbt - mb, cst = 1 << lft;
|
|
740
|
+
t2.sort(function(a, b) {
|
|
741
|
+
return tr[b.s] - tr[a.s] || a.f - b.f;
|
|
742
|
+
});
|
|
743
|
+
for (; i < s; ++i) {
|
|
744
|
+
var i2_1 = t2[i].s;
|
|
745
|
+
if (tr[i2_1] > mb) {
|
|
746
|
+
dt += cst - (1 << mbt - tr[i2_1]);
|
|
747
|
+
tr[i2_1] = mb;
|
|
748
|
+
} else
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
dt >>= lft;
|
|
752
|
+
while (dt > 0) {
|
|
753
|
+
var i2_2 = t2[i].s;
|
|
754
|
+
if (tr[i2_2] < mb)
|
|
755
|
+
dt -= 1 << mb - tr[i2_2]++ - 1;
|
|
756
|
+
else
|
|
757
|
+
++i;
|
|
758
|
+
}
|
|
759
|
+
for (; i >= 0 && dt; --i) {
|
|
760
|
+
var i2_3 = t2[i].s;
|
|
761
|
+
if (tr[i2_3] == mb) {
|
|
762
|
+
--tr[i2_3];
|
|
763
|
+
++dt;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
mbt = mb;
|
|
767
|
+
}
|
|
768
|
+
return { t: new u8(tr), l: mbt };
|
|
769
|
+
};
|
|
770
|
+
var ln = function(n, l, d) {
|
|
771
|
+
return n.s == -1 ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) : l[n.s] = d;
|
|
772
|
+
};
|
|
773
|
+
var lc = function(c) {
|
|
774
|
+
var s = c.length;
|
|
775
|
+
while (s && !c[--s])
|
|
776
|
+
;
|
|
777
|
+
var cl = new u16(++s);
|
|
778
|
+
var cli = 0, cln = c[0], cls = 1;
|
|
779
|
+
var w = function(v) {
|
|
780
|
+
cl[cli++] = v;
|
|
781
|
+
};
|
|
782
|
+
for (var i = 1; i <= s; ++i) {
|
|
783
|
+
if (c[i] == cln && i != s)
|
|
784
|
+
++cls;
|
|
785
|
+
else {
|
|
786
|
+
if (!cln && cls > 2) {
|
|
787
|
+
for (; cls > 138; cls -= 138)
|
|
788
|
+
w(32754);
|
|
789
|
+
if (cls > 2) {
|
|
790
|
+
w(cls > 10 ? cls - 11 << 5 | 28690 : cls - 3 << 5 | 12305);
|
|
791
|
+
cls = 0;
|
|
792
|
+
}
|
|
793
|
+
} else if (cls > 3) {
|
|
794
|
+
w(cln), --cls;
|
|
795
|
+
for (; cls > 6; cls -= 6)
|
|
796
|
+
w(8304);
|
|
797
|
+
if (cls > 2)
|
|
798
|
+
w(cls - 3 << 5 | 8208), cls = 0;
|
|
799
|
+
}
|
|
800
|
+
while (cls--)
|
|
801
|
+
w(cln);
|
|
802
|
+
cls = 1;
|
|
803
|
+
cln = c[i];
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return { c: cl.subarray(0, cli), n: s };
|
|
807
|
+
};
|
|
808
|
+
var clen = function(cf, cl) {
|
|
809
|
+
var l = 0;
|
|
810
|
+
for (var i = 0; i < cl.length; ++i)
|
|
811
|
+
l += cf[i] * cl[i];
|
|
812
|
+
return l;
|
|
813
|
+
};
|
|
814
|
+
var wfblk = function(out, pos, dat) {
|
|
815
|
+
var s = dat.length;
|
|
816
|
+
var o = shft(pos + 2);
|
|
817
|
+
out[o] = s & 255;
|
|
818
|
+
out[o + 1] = s >> 8;
|
|
819
|
+
out[o + 2] = out[o] ^ 255;
|
|
820
|
+
out[o + 3] = out[o + 1] ^ 255;
|
|
821
|
+
for (var i = 0; i < s; ++i)
|
|
822
|
+
out[o + i + 4] = dat[i];
|
|
823
|
+
return (o + 4 + s) * 8;
|
|
824
|
+
};
|
|
825
|
+
var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
|
|
826
|
+
wbits(out, p++, final);
|
|
827
|
+
++lf[256];
|
|
828
|
+
var _a2 = hTree(lf, 15), dlt = _a2.t, mlb = _a2.l;
|
|
829
|
+
var _b2 = hTree(df, 15), ddt = _b2.t, mdb = _b2.l;
|
|
830
|
+
var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
|
|
831
|
+
var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
|
|
832
|
+
var lcfreq = new u16(19);
|
|
833
|
+
for (var i = 0; i < lclt.length; ++i)
|
|
834
|
+
++lcfreq[lclt[i] & 31];
|
|
835
|
+
for (var i = 0; i < lcdt.length; ++i)
|
|
836
|
+
++lcfreq[lcdt[i] & 31];
|
|
837
|
+
var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
|
|
838
|
+
var nlcc = 19;
|
|
839
|
+
for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
|
|
840
|
+
;
|
|
841
|
+
var flen = bl + 5 << 3;
|
|
842
|
+
var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
|
|
843
|
+
var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];
|
|
844
|
+
if (bs >= 0 && flen <= ftlen && flen <= dtlen)
|
|
845
|
+
return wfblk(out, p, dat.subarray(bs, bs + bl));
|
|
846
|
+
var lm, ll, dm, dl;
|
|
847
|
+
wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
|
|
848
|
+
if (dtlen < ftlen) {
|
|
849
|
+
lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
|
|
850
|
+
var llm = hMap(lct, mlcb, 0);
|
|
851
|
+
wbits(out, p, nlc - 257);
|
|
852
|
+
wbits(out, p + 5, ndc - 1);
|
|
853
|
+
wbits(out, p + 10, nlcc - 4);
|
|
854
|
+
p += 14;
|
|
855
|
+
for (var i = 0; i < nlcc; ++i)
|
|
856
|
+
wbits(out, p + 3 * i, lct[clim[i]]);
|
|
857
|
+
p += 3 * nlcc;
|
|
858
|
+
var lcts = [lclt, lcdt];
|
|
859
|
+
for (var it = 0; it < 2; ++it) {
|
|
860
|
+
var clct = lcts[it];
|
|
861
|
+
for (var i = 0; i < clct.length; ++i) {
|
|
862
|
+
var len = clct[i] & 31;
|
|
863
|
+
wbits(out, p, llm[len]), p += lct[len];
|
|
864
|
+
if (len > 15)
|
|
865
|
+
wbits(out, p, clct[i] >> 5 & 127), p += clct[i] >> 12;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
} else {
|
|
869
|
+
lm = flm, ll = flt, dm = fdm, dl = fdt;
|
|
870
|
+
}
|
|
871
|
+
for (var i = 0; i < li; ++i) {
|
|
872
|
+
var sym = syms[i];
|
|
873
|
+
if (sym > 255) {
|
|
874
|
+
var len = sym >> 18 & 31;
|
|
875
|
+
wbits16(out, p, lm[len + 257]), p += ll[len + 257];
|
|
876
|
+
if (len > 7)
|
|
877
|
+
wbits(out, p, sym >> 23 & 31), p += fleb[len];
|
|
878
|
+
var dst = sym & 31;
|
|
879
|
+
wbits16(out, p, dm[dst]), p += dl[dst];
|
|
880
|
+
if (dst > 3)
|
|
881
|
+
wbits16(out, p, sym >> 5 & 8191), p += fdeb[dst];
|
|
882
|
+
} else {
|
|
883
|
+
wbits16(out, p, lm[sym]), p += ll[sym];
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
wbits16(out, p, lm[256]);
|
|
887
|
+
return p + ll[256];
|
|
888
|
+
};
|
|
889
|
+
var deo = /* @__PURE__ */ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
|
|
634
890
|
var et = /* @__PURE__ */ new u8(0);
|
|
891
|
+
var dflt = function(dat, lvl, plvl, pre, post, st) {
|
|
892
|
+
var s = st.z || dat.length;
|
|
893
|
+
var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7e3)) + post);
|
|
894
|
+
var w = o.subarray(pre, o.length - post);
|
|
895
|
+
var lst = st.l;
|
|
896
|
+
var pos = (st.r || 0) & 7;
|
|
897
|
+
if (lvl) {
|
|
898
|
+
if (pos)
|
|
899
|
+
w[0] = st.r >> 3;
|
|
900
|
+
var opt = deo[lvl - 1];
|
|
901
|
+
var n = opt >> 13, c = opt & 8191;
|
|
902
|
+
var msk_1 = (1 << plvl) - 1;
|
|
903
|
+
var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
|
|
904
|
+
var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
|
|
905
|
+
var hsh = function(i2) {
|
|
906
|
+
return (dat[i2] ^ dat[i2 + 1] << bs1_1 ^ dat[i2 + 2] << bs2_1) & msk_1;
|
|
907
|
+
};
|
|
908
|
+
var syms = new i32(25e3);
|
|
909
|
+
var lf = new u16(288), df = new u16(32);
|
|
910
|
+
var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
|
|
911
|
+
for (; i + 2 < s; ++i) {
|
|
912
|
+
var hv = hsh(i);
|
|
913
|
+
var imod = i & 32767, pimod = head[hv];
|
|
914
|
+
prev[imod] = pimod;
|
|
915
|
+
head[hv] = imod;
|
|
916
|
+
if (wi <= i) {
|
|
917
|
+
var rem = s - i;
|
|
918
|
+
if ((lc_1 > 7e3 || li > 24576) && (rem > 423 || !lst)) {
|
|
919
|
+
pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
|
|
920
|
+
li = lc_1 = eb = 0, bs = i;
|
|
921
|
+
for (var j = 0; j < 286; ++j)
|
|
922
|
+
lf[j] = 0;
|
|
923
|
+
for (var j = 0; j < 30; ++j)
|
|
924
|
+
df[j] = 0;
|
|
925
|
+
}
|
|
926
|
+
var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
|
|
927
|
+
if (rem > 2 && hv == hsh(i - dif)) {
|
|
928
|
+
var maxn = Math.min(n, rem) - 1;
|
|
929
|
+
var maxd = Math.min(32767, i);
|
|
930
|
+
var ml = Math.min(258, rem);
|
|
931
|
+
while (dif <= maxd && --ch_1 && imod != pimod) {
|
|
932
|
+
if (dat[i + l] == dat[i + l - dif]) {
|
|
933
|
+
var nl = 0;
|
|
934
|
+
for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
|
|
935
|
+
;
|
|
936
|
+
if (nl > l) {
|
|
937
|
+
l = nl, d = dif;
|
|
938
|
+
if (nl > maxn)
|
|
939
|
+
break;
|
|
940
|
+
var mmd = Math.min(dif, nl - 2);
|
|
941
|
+
var md = 0;
|
|
942
|
+
for (var j = 0; j < mmd; ++j) {
|
|
943
|
+
var ti = i - dif + j & 32767;
|
|
944
|
+
var pti = prev[ti];
|
|
945
|
+
var cd = ti - pti & 32767;
|
|
946
|
+
if (cd > md)
|
|
947
|
+
md = cd, pimod = ti;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
imod = pimod, pimod = prev[imod];
|
|
952
|
+
dif += imod - pimod & 32767;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (d) {
|
|
956
|
+
syms[li++] = 268435456 | revfl[l] << 18 | revfd[d];
|
|
957
|
+
var lin = revfl[l] & 31, din = revfd[d] & 31;
|
|
958
|
+
eb += fleb[lin] + fdeb[din];
|
|
959
|
+
++lf[257 + lin];
|
|
960
|
+
++df[din];
|
|
961
|
+
wi = i + l;
|
|
962
|
+
++lc_1;
|
|
963
|
+
} else {
|
|
964
|
+
syms[li++] = dat[i];
|
|
965
|
+
++lf[dat[i]];
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
for (i = Math.max(i, wi); i < s; ++i) {
|
|
970
|
+
syms[li++] = dat[i];
|
|
971
|
+
++lf[dat[i]];
|
|
972
|
+
}
|
|
973
|
+
pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
|
|
974
|
+
if (!lst) {
|
|
975
|
+
st.r = pos & 7 | w[pos / 8 | 0] << 3;
|
|
976
|
+
pos -= 7;
|
|
977
|
+
st.h = head, st.p = prev, st.i = i, st.w = wi;
|
|
978
|
+
}
|
|
979
|
+
} else {
|
|
980
|
+
for (var i = st.w || 0; i < s + lst; i += 65535) {
|
|
981
|
+
var e = i + 65535;
|
|
982
|
+
if (e >= s) {
|
|
983
|
+
w[pos / 8 | 0] = lst;
|
|
984
|
+
e = s;
|
|
985
|
+
}
|
|
986
|
+
pos = wfblk(w, pos + 1, dat.subarray(i, e));
|
|
987
|
+
}
|
|
988
|
+
st.i = s;
|
|
989
|
+
}
|
|
990
|
+
return slc(o, 0, pre + shft(pos) + post);
|
|
991
|
+
};
|
|
992
|
+
var crct = /* @__PURE__ */ (function() {
|
|
993
|
+
var t = new Int32Array(256);
|
|
994
|
+
for (var i = 0; i < 256; ++i) {
|
|
995
|
+
var c = i, k = 9;
|
|
996
|
+
while (--k)
|
|
997
|
+
c = (c & 1 && -306674912) ^ c >>> 1;
|
|
998
|
+
t[i] = c;
|
|
999
|
+
}
|
|
1000
|
+
return t;
|
|
1001
|
+
})();
|
|
1002
|
+
var crc = function() {
|
|
1003
|
+
var c = -1;
|
|
1004
|
+
return {
|
|
1005
|
+
p: function(d) {
|
|
1006
|
+
var cr = c;
|
|
1007
|
+
for (var i = 0; i < d.length; ++i)
|
|
1008
|
+
cr = crct[cr & 255 ^ d[i]] ^ cr >>> 8;
|
|
1009
|
+
c = cr;
|
|
1010
|
+
},
|
|
1011
|
+
d: function() {
|
|
1012
|
+
return ~c;
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
};
|
|
1016
|
+
var dopt = function(dat, opt, pre, post, st) {
|
|
1017
|
+
if (!st) {
|
|
1018
|
+
st = { l: 1 };
|
|
1019
|
+
if (opt.dictionary) {
|
|
1020
|
+
var dict = opt.dictionary.subarray(-32768);
|
|
1021
|
+
var newDat = new u8(dict.length + dat.length);
|
|
1022
|
+
newDat.set(dict);
|
|
1023
|
+
newDat.set(dat, dict.length);
|
|
1024
|
+
dat = newDat;
|
|
1025
|
+
st.w = dict.length;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20 : 12 + opt.mem, pre, post, st);
|
|
1029
|
+
};
|
|
1030
|
+
var mrg = function(a, b) {
|
|
1031
|
+
var o = {};
|
|
1032
|
+
for (var k in a)
|
|
1033
|
+
o[k] = a[k];
|
|
1034
|
+
for (var k in b)
|
|
1035
|
+
o[k] = b[k];
|
|
1036
|
+
return o;
|
|
1037
|
+
};
|
|
635
1038
|
var b2 = function(d, b) {
|
|
636
1039
|
return d[b] | d[b + 1] << 8;
|
|
637
1040
|
};
|
|
@@ -641,9 +1044,30 @@ var b4 = function(d, b) {
|
|
|
641
1044
|
var b8 = function(d, b) {
|
|
642
1045
|
return b4(d, b) + b4(d, b + 4) * 4294967296;
|
|
643
1046
|
};
|
|
1047
|
+
var wbytes = function(d, b, v) {
|
|
1048
|
+
for (; v; ++b)
|
|
1049
|
+
d[b] = v, v >>>= 8;
|
|
1050
|
+
};
|
|
1051
|
+
function deflateSync(data, opts) {
|
|
1052
|
+
return dopt(data, opts || {}, 0, 0);
|
|
1053
|
+
}
|
|
644
1054
|
function inflateSync(data, opts) {
|
|
645
1055
|
return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);
|
|
646
1056
|
}
|
|
1057
|
+
var fltn = function(d, p, t, o) {
|
|
1058
|
+
for (var k in d) {
|
|
1059
|
+
var val = d[k], n = p + k, op = o;
|
|
1060
|
+
if (Array.isArray(val))
|
|
1061
|
+
op = mrg(o, val[1]), val = val[0];
|
|
1062
|
+
if (ArrayBuffer.isView(val))
|
|
1063
|
+
t[n] = [val, op];
|
|
1064
|
+
else {
|
|
1065
|
+
t[n += "/"] = [new u8(0), op];
|
|
1066
|
+
fltn(val, n, t, o);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
var te = typeof TextEncoder != "undefined" && /* @__PURE__ */ new TextEncoder();
|
|
647
1071
|
var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
|
|
648
1072
|
var tds = 0;
|
|
649
1073
|
try {
|
|
@@ -667,6 +1091,39 @@ var dutf8 = function(d) {
|
|
|
667
1091
|
r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | d[i++] & 63);
|
|
668
1092
|
}
|
|
669
1093
|
};
|
|
1094
|
+
function strToU8(str, latin1) {
|
|
1095
|
+
if (latin1) {
|
|
1096
|
+
var ar_1 = new u8(str.length);
|
|
1097
|
+
for (var i = 0; i < str.length; ++i)
|
|
1098
|
+
ar_1[i] = str.charCodeAt(i);
|
|
1099
|
+
return ar_1;
|
|
1100
|
+
}
|
|
1101
|
+
if (te)
|
|
1102
|
+
return te.encode(str);
|
|
1103
|
+
var l = str.length;
|
|
1104
|
+
var ar = new u8(str.length + (str.length >> 1));
|
|
1105
|
+
var ai = 0;
|
|
1106
|
+
var w = function(v) {
|
|
1107
|
+
ar[ai++] = v;
|
|
1108
|
+
};
|
|
1109
|
+
for (var i = 0; i < l; ++i) {
|
|
1110
|
+
if (ai + 5 > ar.length) {
|
|
1111
|
+
var n = new u8(ai + 8 + (l - i << 1));
|
|
1112
|
+
n.set(ar);
|
|
1113
|
+
ar = n;
|
|
1114
|
+
}
|
|
1115
|
+
var c = str.charCodeAt(i);
|
|
1116
|
+
if (c < 128 || latin1)
|
|
1117
|
+
w(c);
|
|
1118
|
+
else if (c < 2048)
|
|
1119
|
+
w(192 | c >> 6), w(128 | c & 63);
|
|
1120
|
+
else if (c > 55295 && c < 57344)
|
|
1121
|
+
c = 65536 + (c & 1023 << 10) | str.charCodeAt(++i) & 1023, w(240 | c >> 18), w(128 | c >> 12 & 63), w(128 | c >> 6 & 63), w(128 | c & 63);
|
|
1122
|
+
else
|
|
1123
|
+
w(224 | c >> 12), w(128 | c >> 6 & 63), w(128 | c & 63);
|
|
1124
|
+
}
|
|
1125
|
+
return slc(ar, 0, ai);
|
|
1126
|
+
}
|
|
670
1127
|
function strFromU8(dat, latin1) {
|
|
671
1128
|
if (latin1) {
|
|
672
1129
|
var r = "";
|
|
@@ -709,6 +1166,107 @@ var z64hs = function(d, b, l, z, sc, su, off) {
|
|
|
709
1166
|
}
|
|
710
1167
|
return [sc, su, off, 0];
|
|
711
1168
|
};
|
|
1169
|
+
var exfl = function(ex) {
|
|
1170
|
+
var le = 0;
|
|
1171
|
+
if (ex) {
|
|
1172
|
+
for (var k in ex) {
|
|
1173
|
+
var l = ex[k].length;
|
|
1174
|
+
if (l > 65535)
|
|
1175
|
+
err(9);
|
|
1176
|
+
le += l + 4;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return le;
|
|
1180
|
+
};
|
|
1181
|
+
var wzh = function(d, b, f, fn, u, c, ce, co) {
|
|
1182
|
+
var fl2 = fn.length, ex = f.extra, col = co && co.length;
|
|
1183
|
+
var exl = exfl(ex);
|
|
1184
|
+
wbytes(d, b, ce != null ? 33639248 : 67324752), b += 4;
|
|
1185
|
+
if (ce != null)
|
|
1186
|
+
d[b++] = 20, d[b++] = f.os;
|
|
1187
|
+
d[b] = 20, b += 2;
|
|
1188
|
+
d[b++] = f.flag << 1 | (c < 0 && 8), d[b++] = u && 8;
|
|
1189
|
+
d[b++] = f.compression & 255, d[b++] = f.compression >> 8;
|
|
1190
|
+
var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980;
|
|
1191
|
+
if (y < 0 || y > 119)
|
|
1192
|
+
err(10);
|
|
1193
|
+
wbytes(d, b, y << 25 | dt.getMonth() + 1 << 21 | dt.getDate() << 16 | dt.getHours() << 11 | dt.getMinutes() << 5 | dt.getSeconds() >> 1), b += 4;
|
|
1194
|
+
if (c != -1) {
|
|
1195
|
+
wbytes(d, b, f.crc);
|
|
1196
|
+
wbytes(d, b + 4, c < 0 ? -c - 2 : c);
|
|
1197
|
+
wbytes(d, b + 8, f.size);
|
|
1198
|
+
}
|
|
1199
|
+
wbytes(d, b + 12, fl2);
|
|
1200
|
+
wbytes(d, b + 14, exl), b += 16;
|
|
1201
|
+
if (ce != null) {
|
|
1202
|
+
wbytes(d, b, col);
|
|
1203
|
+
wbytes(d, b + 6, f.attrs);
|
|
1204
|
+
wbytes(d, b + 10, ce), b += 14;
|
|
1205
|
+
}
|
|
1206
|
+
d.set(fn, b);
|
|
1207
|
+
b += fl2;
|
|
1208
|
+
if (exl) {
|
|
1209
|
+
for (var k in ex) {
|
|
1210
|
+
var exf = ex[k], l = exf.length;
|
|
1211
|
+
wbytes(d, b, +k);
|
|
1212
|
+
wbytes(d, b + 2, l);
|
|
1213
|
+
d.set(exf, b + 4), b += 4 + l;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (col)
|
|
1217
|
+
d.set(co, b), b += col;
|
|
1218
|
+
return b;
|
|
1219
|
+
};
|
|
1220
|
+
var wzf = function(o, b, c, d, e) {
|
|
1221
|
+
wbytes(o, b, 101010256);
|
|
1222
|
+
wbytes(o, b + 8, c);
|
|
1223
|
+
wbytes(o, b + 10, c);
|
|
1224
|
+
wbytes(o, b + 12, d);
|
|
1225
|
+
wbytes(o, b + 16, e);
|
|
1226
|
+
};
|
|
1227
|
+
function zipSync(data, opts) {
|
|
1228
|
+
if (!opts)
|
|
1229
|
+
opts = {};
|
|
1230
|
+
var r = {};
|
|
1231
|
+
var files = [];
|
|
1232
|
+
fltn(data, "", r, opts);
|
|
1233
|
+
var o = 0;
|
|
1234
|
+
var tot = 0;
|
|
1235
|
+
for (var fn in r) {
|
|
1236
|
+
var _a2 = r[fn], file = _a2[0], p = _a2[1];
|
|
1237
|
+
var compression = p.level == 0 ? 0 : 8;
|
|
1238
|
+
var f = strToU8(fn), s = f.length;
|
|
1239
|
+
var com = p.comment, m = com && strToU8(com), ms = m && m.length;
|
|
1240
|
+
var exl = exfl(p.extra);
|
|
1241
|
+
if (s > 65535)
|
|
1242
|
+
err(11);
|
|
1243
|
+
var d = compression ? deflateSync(file, p) : file, l = d.length;
|
|
1244
|
+
var c = crc();
|
|
1245
|
+
c.p(file);
|
|
1246
|
+
files.push(mrg(p, {
|
|
1247
|
+
size: file.length,
|
|
1248
|
+
crc: c.d(),
|
|
1249
|
+
c: d,
|
|
1250
|
+
f,
|
|
1251
|
+
m,
|
|
1252
|
+
u: s != fn.length || m && com.length != ms,
|
|
1253
|
+
o,
|
|
1254
|
+
compression
|
|
1255
|
+
}));
|
|
1256
|
+
o += 30 + s + exl + l;
|
|
1257
|
+
tot += 76 + 2 * (s + exl) + (ms || 0) + l;
|
|
1258
|
+
}
|
|
1259
|
+
var out = new u8(tot + 22), oe = o, cdl = tot - o;
|
|
1260
|
+
for (var i = 0; i < files.length; ++i) {
|
|
1261
|
+
var f = files[i];
|
|
1262
|
+
wzh(out, f.o, f, f.f, f.u, f.c.length);
|
|
1263
|
+
var badd = 30 + f.f.length + exfl(f.extra);
|
|
1264
|
+
out.set(f.c, f.o + badd);
|
|
1265
|
+
wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0);
|
|
1266
|
+
}
|
|
1267
|
+
wzf(out, o, files.length, cdl, oe);
|
|
1268
|
+
return out;
|
|
1269
|
+
}
|
|
712
1270
|
function unzipSync(data, opts) {
|
|
713
1271
|
var files = {};
|
|
714
1272
|
var e = data.length - 22;
|
|
@@ -799,6 +1357,12 @@ function readUtf8Flags(buf) {
|
|
|
799
1357
|
}
|
|
800
1358
|
return flags;
|
|
801
1359
|
}
|
|
1360
|
+
function zipFiles(files) {
|
|
1361
|
+
const packed = zipSync(files, { level: 6 });
|
|
1362
|
+
const bytes = new Uint8Array(packed.length);
|
|
1363
|
+
bytes.set(packed);
|
|
1364
|
+
return new Blob([bytes], { type: "application/zip" });
|
|
1365
|
+
}
|
|
802
1366
|
|
|
803
1367
|
// src/koe.ts
|
|
804
1368
|
var MAGIC = 1263486208;
|
|
@@ -1045,13 +1609,34 @@ var KoeEngine = class {
|
|
|
1045
1609
|
this.pending.set(name, load);
|
|
1046
1610
|
return load;
|
|
1047
1611
|
}
|
|
1048
|
-
/**
|
|
1049
|
-
|
|
1612
|
+
/**
|
|
1613
|
+
* Stop current playback, preload the phonemes for `notes`, then queue them.
|
|
1614
|
+
*
|
|
1615
|
+
* @returns the lead-in offset in samples — how far the first note's audio
|
|
1616
|
+
* starts ahead of its beat. 0 unless {@link PlayOptions.leadIn}.
|
|
1617
|
+
*/
|
|
1618
|
+
async play(notes, options = {}) {
|
|
1050
1619
|
if (!this.node) throw new Error("KoeEngine: call load() before play()");
|
|
1051
1620
|
this.node.port.postMessage({ type: "stop" });
|
|
1052
1621
|
const names = [...new Set(notes.map((n) => n.phoneme))].filter(Boolean);
|
|
1053
1622
|
await Promise.all(names.map((n) => this.ensurePhoneme(n)));
|
|
1054
|
-
|
|
1623
|
+
const leadIn = options.leadIn === true;
|
|
1624
|
+
this.node.port.postMessage({ type: "play", notes, leadIn });
|
|
1625
|
+
return leadIn ? this.leadInSamples(notes) : 0;
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Output samples the first note's lead-in occupies ahead of its beat —
|
|
1629
|
+
* mirrors what the worklet does with `leadIn`, so callers can compensate.
|
|
1630
|
+
*/
|
|
1631
|
+
leadInSamples(notes) {
|
|
1632
|
+
const first = notes[0];
|
|
1633
|
+
const m = this.bank?.manifest;
|
|
1634
|
+
if (!first || !m || first.phoneme === "") return 0;
|
|
1635
|
+
const entry = Object.hasOwn(m.phonemes, first.phoneme) ? m.phonemes[first.phoneme] : void 0;
|
|
1636
|
+
if (!entry || entry.length <= 0) return 0;
|
|
1637
|
+
const recorded = entry.pitch || m.referencePitch || first.pitch || 1;
|
|
1638
|
+
const stepRate = first.pitch / recorded;
|
|
1639
|
+
return stepRate > 0 ? entry.pre / stepRate : 0;
|
|
1055
1640
|
}
|
|
1056
1641
|
/** Stop playback and clear the queue. */
|
|
1057
1642
|
stop() {
|
|
@@ -1173,6 +1758,104 @@ var Worldline = class _Worldline {
|
|
|
1173
1758
|
* @returns Float32 PCM, or null when `pcm` is shorter than
|
|
1174
1759
|
* {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).
|
|
1175
1760
|
*/
|
|
1761
|
+
renderPhrase(params) {
|
|
1762
|
+
const { units, pitch, gender = 0.5, tension = 0.5, breathiness = 0.5, voicing = 1, tempo = 120 } = params;
|
|
1763
|
+
if (units.length === 0) return null;
|
|
1764
|
+
const WL = this.wasm;
|
|
1765
|
+
const FS = WORLDLINE_SAMPLE_RATE;
|
|
1766
|
+
let totalMs = 0;
|
|
1767
|
+
for (const u of units) {
|
|
1768
|
+
const endMs = u.posMs + u.lengthMs;
|
|
1769
|
+
if (endMs > totalMs) totalMs = endMs;
|
|
1770
|
+
}
|
|
1771
|
+
const ps = WL._PhraseSynthNew();
|
|
1772
|
+
if (!ps) return null;
|
|
1773
|
+
const pointersToFree = [];
|
|
1774
|
+
for (const u of units) {
|
|
1775
|
+
if (!u.pcm || u.pcm.length < MIN_WORLDLINE_SAMPLES) continue;
|
|
1776
|
+
const reqPtr = WL._malloc(SYNTH_REQ_SIZE);
|
|
1777
|
+
if (!reqPtr) continue;
|
|
1778
|
+
pointersToFree.push(reqPtr);
|
|
1779
|
+
const samplePtr = WL._malloc(u.pcm.length * 8);
|
|
1780
|
+
if (!samplePtr) continue;
|
|
1781
|
+
pointersToFree.push(samplePtr);
|
|
1782
|
+
WL.HEAPF64.set(u.pcm, samplePtr >> 3);
|
|
1783
|
+
const sv = (off, val, type) => WL.setValue(reqPtr + off, val, type);
|
|
1784
|
+
sv(0, FS, "i32");
|
|
1785
|
+
sv(4, u.pcm.length, "i32");
|
|
1786
|
+
sv(8, samplePtr, "*");
|
|
1787
|
+
sv(12, 0, "i32");
|
|
1788
|
+
sv(16, 0, "*");
|
|
1789
|
+
sv(20, 69, "i32");
|
|
1790
|
+
sv(24, 100, "double");
|
|
1791
|
+
sv(32, 0, "double");
|
|
1792
|
+
sv(40, u.lengthMs, "double");
|
|
1793
|
+
sv(48, u.consonantMs, "double");
|
|
1794
|
+
const cutMs = u.cutMs ?? WL_FRAME_MS * 2;
|
|
1795
|
+
sv(56, cutMs, "double");
|
|
1796
|
+
sv(64, 100, "double");
|
|
1797
|
+
sv(72, 0, "double");
|
|
1798
|
+
sv(80, tempo, "double");
|
|
1799
|
+
sv(88, 0, "i32");
|
|
1800
|
+
sv(92, 0, "*");
|
|
1801
|
+
sv(96, 0, "i32");
|
|
1802
|
+
sv(100, 0, "i32");
|
|
1803
|
+
sv(104, 100, "i32");
|
|
1804
|
+
sv(108, 0, "i32");
|
|
1805
|
+
sv(112, 0, "i32");
|
|
1806
|
+
sv(116, 100, "i32");
|
|
1807
|
+
WL._PhraseSynthAddRequest(ps, reqPtr, u.posMs, u.skipMs, u.lengthMs, u.fadeInMs, u.fadeOutMs, 0);
|
|
1808
|
+
}
|
|
1809
|
+
totalMs += WL_FRAME_MS * 2;
|
|
1810
|
+
const nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;
|
|
1811
|
+
const f0Arr = new Float64Array(nFrames);
|
|
1812
|
+
const gArr = new Float64Array(nFrames);
|
|
1813
|
+
const tArr = new Float64Array(nFrames);
|
|
1814
|
+
const bArr = new Float64Array(nFrames);
|
|
1815
|
+
const vArr = new Float64Array(nFrames);
|
|
1816
|
+
for (let i = 0; i < nFrames; i++) {
|
|
1817
|
+
const tMs = i * WL_FRAME_MS;
|
|
1818
|
+
f0Arr[i] = sampleCurve(tMs, pitch, totalMs);
|
|
1819
|
+
gArr[i] = sampleCurve(tMs, gender, totalMs);
|
|
1820
|
+
tArr[i] = sampleCurve(tMs, tension, totalMs);
|
|
1821
|
+
bArr[i] = sampleCurve(tMs, breathiness, totalMs);
|
|
1822
|
+
vArr[i] = sampleCurve(tMs, voicing, totalMs);
|
|
1823
|
+
}
|
|
1824
|
+
const f0Ptr = WL._malloc(nFrames * 8);
|
|
1825
|
+
const gPtr = WL._malloc(nFrames * 8);
|
|
1826
|
+
const tPtr = WL._malloc(nFrames * 8);
|
|
1827
|
+
const bPtr = WL._malloc(nFrames * 8);
|
|
1828
|
+
const vPtr = WL._malloc(nFrames * 8);
|
|
1829
|
+
if (f0Ptr && gPtr && tPtr && bPtr && vPtr) {
|
|
1830
|
+
WL.HEAPF64.set(f0Arr, f0Ptr >> 3);
|
|
1831
|
+
WL.HEAPF64.set(gArr, gPtr >> 3);
|
|
1832
|
+
WL.HEAPF64.set(tArr, tPtr >> 3);
|
|
1833
|
+
WL.HEAPF64.set(bArr, bPtr >> 3);
|
|
1834
|
+
WL.HEAPF64.set(vArr, vPtr >> 3);
|
|
1835
|
+
WL._PhraseSynthSetCurves(ps, f0Ptr, gPtr, tPtr, bPtr, vPtr, nFrames, WL_FRAME_MS);
|
|
1836
|
+
}
|
|
1837
|
+
if (f0Ptr) WL._free(f0Ptr);
|
|
1838
|
+
if (gPtr) WL._free(gPtr);
|
|
1839
|
+
if (tPtr) WL._free(tPtr);
|
|
1840
|
+
if (bPtr) WL._free(bPtr);
|
|
1841
|
+
if (vPtr) WL._free(vPtr);
|
|
1842
|
+
const yPtrPtr = WL._malloc(4);
|
|
1843
|
+
let audio = null;
|
|
1844
|
+
if (yPtrPtr) {
|
|
1845
|
+
const outLen = WL._PhraseSynthSynth(ps, yPtrPtr, 0);
|
|
1846
|
+
const yPtr = WL.getValue(yPtrPtr, "*");
|
|
1847
|
+
if (outLen > 0 && yPtr) {
|
|
1848
|
+
audio = new Float32Array(WL.HEAPF32.buffer, yPtr, outLen).slice();
|
|
1849
|
+
WL._free(yPtr);
|
|
1850
|
+
}
|
|
1851
|
+
WL._free(yPtrPtr);
|
|
1852
|
+
}
|
|
1853
|
+
for (const ptr of pointersToFree) {
|
|
1854
|
+
WL._free(ptr);
|
|
1855
|
+
}
|
|
1856
|
+
WL._PhraseSynthDelete(ps);
|
|
1857
|
+
return audio;
|
|
1858
|
+
}
|
|
1176
1859
|
renderNote(params) {
|
|
1177
1860
|
const {
|
|
1178
1861
|
pcm,
|
|
@@ -1297,30 +1980,1218 @@ var Worldline = class _Worldline {
|
|
|
1297
1980
|
return audio;
|
|
1298
1981
|
}
|
|
1299
1982
|
};
|
|
1983
|
+
|
|
1984
|
+
// src/oto/frames.ts
|
|
1985
|
+
var RATE = 16e3;
|
|
1986
|
+
var HOP_MS = 2;
|
|
1987
|
+
var HOP = RATE * HOP_MS / 1e3;
|
|
1988
|
+
var FFT_SIZE = 512;
|
|
1989
|
+
var BINS = FFT_SIZE / 2;
|
|
1990
|
+
var PITCH_DECIM = 2;
|
|
1991
|
+
var PITCH_RATE = RATE / PITCH_DECIM;
|
|
1992
|
+
var PITCH_WIN = 512;
|
|
1993
|
+
var MIN_F0 = 70;
|
|
1994
|
+
var MAX_F0 = 600;
|
|
1995
|
+
var WINDOW_CENTRE_MS = FFT_SIZE / 2 / RATE * 1e3;
|
|
1996
|
+
function msToFrame(ms) {
|
|
1997
|
+
return Math.round(ms / HOP_MS);
|
|
1998
|
+
}
|
|
1999
|
+
function framesToMs(frames) {
|
|
2000
|
+
return frames * HOP_MS;
|
|
2001
|
+
}
|
|
2002
|
+
function frameTimeMs(frame) {
|
|
2003
|
+
return frame * HOP_MS + WINDOW_CENTRE_MS;
|
|
2004
|
+
}
|
|
2005
|
+
function fft(re, im) {
|
|
2006
|
+
const n = re.length;
|
|
2007
|
+
for (let i = 1, j = 0; i < n; i++) {
|
|
2008
|
+
let bit = n >> 1;
|
|
2009
|
+
for (; j & bit; bit >>= 1) j ^= bit;
|
|
2010
|
+
j ^= bit;
|
|
2011
|
+
if (i < j) {
|
|
2012
|
+
[re[i], re[j]] = [re[j], re[i]];
|
|
2013
|
+
[im[i], im[j]] = [im[j], im[i]];
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
for (let len = 2; len <= n; len <<= 1) {
|
|
2017
|
+
const ang = -2 * Math.PI / len;
|
|
2018
|
+
const wRe = Math.cos(ang);
|
|
2019
|
+
const wIm = Math.sin(ang);
|
|
2020
|
+
for (let i = 0; i < n; i += len) {
|
|
2021
|
+
let curRe = 1;
|
|
2022
|
+
let curIm = 0;
|
|
2023
|
+
for (let k = 0; k < len >> 1; k++) {
|
|
2024
|
+
const aRe = re[i + k];
|
|
2025
|
+
const aIm = im[i + k];
|
|
2026
|
+
const bRe = re[i + k + (len >> 1)] * curRe - im[i + k + (len >> 1)] * curIm;
|
|
2027
|
+
const bIm = re[i + k + (len >> 1)] * curIm + im[i + k + (len >> 1)] * curRe;
|
|
2028
|
+
re[i + k] = aRe + bRe;
|
|
2029
|
+
im[i + k] = aIm + bIm;
|
|
2030
|
+
re[i + k + (len >> 1)] = aRe - bRe;
|
|
2031
|
+
im[i + k + (len >> 1)] = aIm - bIm;
|
|
2032
|
+
const nextRe = curRe * wRe - curIm * wIm;
|
|
2033
|
+
curIm = curRe * wIm + curIm * wRe;
|
|
2034
|
+
curRe = nextRe;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
function quietestWindow(db) {
|
|
2040
|
+
const averaged = smooth(db, Math.round(50 / HOP_MS));
|
|
2041
|
+
let min = Infinity;
|
|
2042
|
+
for (let i = 0; i < averaged.length; i++) {
|
|
2043
|
+
if (averaged[i] < min) min = averaged[i];
|
|
2044
|
+
}
|
|
2045
|
+
return Number.isFinite(min) ? min : -120;
|
|
2046
|
+
}
|
|
2047
|
+
function percentile(values, p) {
|
|
2048
|
+
const sorted = Float32Array.from(values).sort();
|
|
2049
|
+
const i = Math.min(
|
|
2050
|
+
sorted.length - 1,
|
|
2051
|
+
Math.max(0, Math.round(p * (sorted.length - 1)))
|
|
2052
|
+
);
|
|
2053
|
+
return sorted[i];
|
|
2054
|
+
}
|
|
2055
|
+
function ncc(buf, at, win, lag) {
|
|
2056
|
+
let r = 0;
|
|
2057
|
+
let e0 = 0;
|
|
2058
|
+
let e1 = 0;
|
|
2059
|
+
for (let i = 0; i < win; i++) {
|
|
2060
|
+
const a = buf[at + i];
|
|
2061
|
+
const b = buf[at + i + lag];
|
|
2062
|
+
r += a * b;
|
|
2063
|
+
e0 += a * a;
|
|
2064
|
+
e1 += b * b;
|
|
2065
|
+
}
|
|
2066
|
+
const denom = Math.sqrt(e0 * e1);
|
|
2067
|
+
return denom > 1e-12 ? r / denom : 0;
|
|
2068
|
+
}
|
|
2069
|
+
function analyze(wav) {
|
|
2070
|
+
const mono = resample(toMono(wav), RATE);
|
|
2071
|
+
const x = mono.samples;
|
|
2072
|
+
const n = Math.max(1, Math.floor((x.length - FFT_SIZE) / HOP) + 1);
|
|
2073
|
+
const rmsDb = new Float32Array(n);
|
|
2074
|
+
const highRatio = new Float32Array(n);
|
|
2075
|
+
const highDb = new Float32Array(n);
|
|
2076
|
+
const flux = new Float32Array(n);
|
|
2077
|
+
const voiced = new Float32Array(n);
|
|
2078
|
+
const window = new Float32Array(FFT_SIZE);
|
|
2079
|
+
for (let i = 0; i < FFT_SIZE; i++) {
|
|
2080
|
+
window[i] = 0.5 - 0.5 * Math.cos(2 * Math.PI * i / FFT_SIZE);
|
|
2081
|
+
}
|
|
2082
|
+
const re = new Float32Array(FFT_SIZE);
|
|
2083
|
+
const im = new Float32Array(FFT_SIZE);
|
|
2084
|
+
const mag = new Float32Array(BINS);
|
|
2085
|
+
const prevMag = new Float32Array(BINS);
|
|
2086
|
+
const highBin = Math.floor(4e3 / (RATE / 2) * BINS);
|
|
2087
|
+
for (let t = 0; t < n; t++) {
|
|
2088
|
+
const off = t * HOP;
|
|
2089
|
+
let energy = 0;
|
|
2090
|
+
for (let i = 0; i < FFT_SIZE; i++) {
|
|
2091
|
+
const s = x[off + i] ?? 0;
|
|
2092
|
+
energy += s * s;
|
|
2093
|
+
re[i] = s * window[i];
|
|
2094
|
+
im[i] = 0;
|
|
2095
|
+
}
|
|
2096
|
+
rmsDb[t] = 10 * Math.log10(energy / FFT_SIZE + 1e-12);
|
|
2097
|
+
fft(re, im);
|
|
2098
|
+
let lowSum = 0;
|
|
2099
|
+
let highSum = 0;
|
|
2100
|
+
let fluxSum = 0;
|
|
2101
|
+
for (let b = 1; b < BINS; b++) {
|
|
2102
|
+
const m = Math.sqrt(re[b] * re[b] + im[b] * im[b]);
|
|
2103
|
+
mag[b] = m;
|
|
2104
|
+
if (b < highBin) lowSum += m;
|
|
2105
|
+
else highSum += m;
|
|
2106
|
+
const d = Math.log10(m + 1e-6) - Math.log10(prevMag[b] + 1e-6);
|
|
2107
|
+
if (d > 0) fluxSum += d;
|
|
2108
|
+
}
|
|
2109
|
+
prevMag.set(mag);
|
|
2110
|
+
highRatio[t] = highSum / (lowSum + highSum + 1e-12);
|
|
2111
|
+
highDb[t] = 20 * Math.log10(highSum / BINS + 1e-9);
|
|
2112
|
+
flux[t] = t === 0 ? 0 : fluxSum;
|
|
2113
|
+
}
|
|
2114
|
+
const smoothDb = smooth(rmsDb, Math.round(30 / HOP_MS));
|
|
2115
|
+
const smoothHighDb = smooth(highDb, Math.round(20 / HOP_MS));
|
|
2116
|
+
const highFloorDb = quietestWindow(smoothHighDb);
|
|
2117
|
+
const floorDb = percentile(smoothDb, 0.1);
|
|
2118
|
+
const quietDb = quietestWindow(smoothDb);
|
|
2119
|
+
let peakDb = -120;
|
|
2120
|
+
for (let t = 0; t < n; t++) if (smoothDb[t] > peakDb) peakDb = smoothDb[t];
|
|
2121
|
+
const pitchBuf = new Float32Array(Math.floor(x.length / PITCH_DECIM));
|
|
2122
|
+
for (let i = 0; i < pitchBuf.length; i++) {
|
|
2123
|
+
pitchBuf[i] = (x[i * PITCH_DECIM] + x[i * PITCH_DECIM + 1]) * 0.5;
|
|
2124
|
+
}
|
|
2125
|
+
const minLag = Math.floor(PITCH_RATE / MAX_F0);
|
|
2126
|
+
const maxLag = Math.floor(PITCH_RATE / MIN_F0);
|
|
2127
|
+
const loud = [];
|
|
2128
|
+
for (let t = 0; t < n; t++) {
|
|
2129
|
+
if (smoothDb[t] > peakDb - 10) loud.push(t);
|
|
2130
|
+
}
|
|
2131
|
+
const lags = [];
|
|
2132
|
+
const step = Math.max(1, Math.floor(loud.length / 24));
|
|
2133
|
+
for (let k = 0; k < loud.length; k += step) {
|
|
2134
|
+
const at = Math.floor(loud[k] * HOP / PITCH_DECIM);
|
|
2135
|
+
if (at + PITCH_WIN + maxLag >= pitchBuf.length) continue;
|
|
2136
|
+
let best = 0;
|
|
2137
|
+
let bestLag = 0;
|
|
2138
|
+
for (let lag = minLag; lag <= maxLag; lag++) {
|
|
2139
|
+
const v = ncc(pitchBuf, at, PITCH_WIN, lag);
|
|
2140
|
+
if (v > best) {
|
|
2141
|
+
best = v;
|
|
2142
|
+
bestLag = lag;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
if (best > 0.5 && bestLag > 0) lags.push(bestLag);
|
|
2146
|
+
}
|
|
2147
|
+
lags.sort((a, b) => a - b);
|
|
2148
|
+
const centreLag = lags.length ? lags[lags.length >> 1] : 0;
|
|
2149
|
+
const f0 = centreLag ? PITCH_RATE / centreLag : 0;
|
|
2150
|
+
if (centreLag) {
|
|
2151
|
+
const lo = Math.max(minLag, Math.floor(centreLag / 1.7));
|
|
2152
|
+
const hi = Math.min(maxLag, Math.ceil(centreLag * 1.7));
|
|
2153
|
+
for (let t = 0; t < n; t++) {
|
|
2154
|
+
if (smoothDb[t] < floorDb + 6) continue;
|
|
2155
|
+
const at = Math.floor(t * HOP / PITCH_DECIM);
|
|
2156
|
+
if (at + PITCH_WIN + hi >= pitchBuf.length) continue;
|
|
2157
|
+
let best = 0;
|
|
2158
|
+
for (let lag = lo; lag <= hi; lag++) {
|
|
2159
|
+
const v = ncc(pitchBuf, at, PITCH_WIN, lag);
|
|
2160
|
+
if (v > best) best = v;
|
|
2161
|
+
}
|
|
2162
|
+
voiced[t] = best;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
const medianFlux = percentile(flux, 0.5) || 1;
|
|
2166
|
+
for (let t = 0; t < n; t++) flux[t] /= medianFlux;
|
|
2167
|
+
return {
|
|
2168
|
+
n,
|
|
2169
|
+
durationMs: x.length / RATE * 1e3,
|
|
2170
|
+
rmsDb,
|
|
2171
|
+
smoothDb,
|
|
2172
|
+
voiced,
|
|
2173
|
+
highRatio,
|
|
2174
|
+
highDb: smoothHighDb,
|
|
2175
|
+
highFloorDb,
|
|
2176
|
+
flux: smooth(flux, 3),
|
|
2177
|
+
f0,
|
|
2178
|
+
floorDb,
|
|
2179
|
+
quietDb,
|
|
2180
|
+
peakDb
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
function smooth(src, width) {
|
|
2184
|
+
if (width <= 1) return Float32Array.from(src);
|
|
2185
|
+
const prefix = new Float64Array(src.length + 1);
|
|
2186
|
+
for (let i = 0; i < src.length; i++) prefix[i + 1] = prefix[i] + src[i];
|
|
2187
|
+
const out = new Float32Array(src.length);
|
|
2188
|
+
const half = width >> 1;
|
|
2189
|
+
for (let i = 0; i < src.length; i++) {
|
|
2190
|
+
const lo = Math.max(0, i - half);
|
|
2191
|
+
const hi = Math.min(src.length, i + half + 1);
|
|
2192
|
+
out[i] = (prefix[hi] - prefix[lo]) / (hi - lo);
|
|
2193
|
+
}
|
|
2194
|
+
return out;
|
|
2195
|
+
}
|
|
2196
|
+
function analyzeWav(buf) {
|
|
2197
|
+
return analyze(parseWav(buf));
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
// src/oto/estimate.ts
|
|
2201
|
+
var ARTICULATION = {
|
|
2202
|
+
vowel: {
|
|
2203
|
+
maxConsonantMs: 40,
|
|
2204
|
+
leadInMs: 3,
|
|
2205
|
+
overlapRatio: 0,
|
|
2206
|
+
fixedOverlapMs: 20,
|
|
2207
|
+
voicedConsonant: true,
|
|
2208
|
+
fricationOnset: false
|
|
2209
|
+
},
|
|
2210
|
+
nasalN: {
|
|
2211
|
+
maxConsonantMs: 40,
|
|
2212
|
+
leadInMs: 3,
|
|
2213
|
+
overlapRatio: 0,
|
|
2214
|
+
fixedOverlapMs: 20,
|
|
2215
|
+
voicedConsonant: true,
|
|
2216
|
+
fricationOnset: false
|
|
2217
|
+
},
|
|
2218
|
+
nasal: {
|
|
2219
|
+
maxConsonantMs: 110,
|
|
2220
|
+
leadInMs: 5,
|
|
2221
|
+
overlapRatio: 0.6,
|
|
2222
|
+
voicedConsonant: true,
|
|
2223
|
+
fricationOnset: false
|
|
2224
|
+
},
|
|
2225
|
+
liquid: {
|
|
2226
|
+
maxConsonantMs: 80,
|
|
2227
|
+
leadInMs: 5,
|
|
2228
|
+
overlapRatio: 0.6,
|
|
2229
|
+
voicedConsonant: true,
|
|
2230
|
+
fricationOnset: false
|
|
2231
|
+
},
|
|
2232
|
+
semivowel: {
|
|
2233
|
+
maxConsonantMs: 75,
|
|
2234
|
+
leadInMs: 5,
|
|
2235
|
+
overlapRatio: 0.6,
|
|
2236
|
+
voicedConsonant: true,
|
|
2237
|
+
fricationOnset: false
|
|
2238
|
+
},
|
|
2239
|
+
fricativeVoiceless: {
|
|
2240
|
+
maxConsonantMs: 140,
|
|
2241
|
+
leadInMs: 5,
|
|
2242
|
+
overlapRatio: 0.3,
|
|
2243
|
+
voicedConsonant: false,
|
|
2244
|
+
fricationOnset: true
|
|
2245
|
+
},
|
|
2246
|
+
fricativeVoiced: {
|
|
2247
|
+
maxConsonantMs: 130,
|
|
2248
|
+
leadInMs: 5,
|
|
2249
|
+
overlapRatio: 0.4,
|
|
2250
|
+
voicedConsonant: true,
|
|
2251
|
+
fricationOnset: true
|
|
2252
|
+
},
|
|
2253
|
+
affricate: {
|
|
2254
|
+
maxConsonantMs: 120,
|
|
2255
|
+
leadInMs: 5,
|
|
2256
|
+
overlapRatio: 0.3,
|
|
2257
|
+
voicedConsonant: false,
|
|
2258
|
+
fricationOnset: true
|
|
2259
|
+
},
|
|
2260
|
+
plosiveVoiceless: {
|
|
2261
|
+
maxConsonantMs: 90,
|
|
2262
|
+
leadInMs: 8,
|
|
2263
|
+
overlapRatio: 0,
|
|
2264
|
+
// か/た/ぱ行 need a gap, not a crossfade: the wiki asks for a negative
|
|
2265
|
+
// overlap so the closure that precedes the burst survives synthesis.
|
|
2266
|
+
fixedOverlapMs: -10,
|
|
2267
|
+
voicedConsonant: false,
|
|
2268
|
+
fricationOnset: false
|
|
2269
|
+
},
|
|
2270
|
+
plosiveVoiced: {
|
|
2271
|
+
maxConsonantMs: 90,
|
|
2272
|
+
leadInMs: 8,
|
|
2273
|
+
overlapRatio: 0.55,
|
|
2274
|
+
voicedConsonant: true,
|
|
2275
|
+
fricationOnset: false
|
|
2276
|
+
}
|
|
2277
|
+
};
|
|
2278
|
+
var MIN_OVERLAP_MS = 12;
|
|
2279
|
+
var MAX_OVERLAP_MS = 40;
|
|
2280
|
+
var VOICED_THRESHOLD = 0.55;
|
|
2281
|
+
var VOICED_RUN_MS = 24;
|
|
2282
|
+
var MIN_VOICED_CONSONANT_MS = 22;
|
|
2283
|
+
var SEQUENCE_ONSET_BIAS = 12;
|
|
2284
|
+
var MAX_SEQUENCE_PRE_MS = 250;
|
|
2285
|
+
var DECAY_DROP_DB = 4;
|
|
2286
|
+
var RELEASE_GUARD_FRAMES = 10;
|
|
2287
|
+
function clamp2(v, lo, hi) {
|
|
2288
|
+
return Math.min(hi, Math.max(lo, v));
|
|
2289
|
+
}
|
|
2290
|
+
var SOUND_OVER_FLOOR_DB = 8;
|
|
2291
|
+
var SOUND_UNDER_PEAK_DB = 15;
|
|
2292
|
+
var ATTACK_SLOPE_DB = 1;
|
|
2293
|
+
var FRICATION_HIGH_RATIO = 0.45;
|
|
2294
|
+
var FRICATION_UNDER_PEAK_DB = 30;
|
|
2295
|
+
function soundThreshold(f) {
|
|
2296
|
+
return Math.min(
|
|
2297
|
+
Math.max(f.floorDb + SOUND_OVER_FLOOR_DB, f.peakDb - 34),
|
|
2298
|
+
f.peakDb - SOUND_UNDER_PEAK_DB
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
function findSoundStart(f, from, to) {
|
|
2302
|
+
const th = soundThreshold(f);
|
|
2303
|
+
for (let t = Math.max(0, from); t < Math.min(f.n, to); t++) {
|
|
2304
|
+
if (f.smoothDb[t] >= th) return t;
|
|
2305
|
+
}
|
|
2306
|
+
return Math.max(0, from);
|
|
2307
|
+
}
|
|
2308
|
+
function findSoundEnd(f, from, to) {
|
|
2309
|
+
const th = soundThreshold(f);
|
|
2310
|
+
for (let t = Math.min(f.n, to) - 1; t > from; t--) {
|
|
2311
|
+
if (f.smoothDb[t] >= th) return t;
|
|
2312
|
+
}
|
|
2313
|
+
return Math.min(f.n - 1, to);
|
|
2314
|
+
}
|
|
2315
|
+
function findConsonantStart(f, from, to, frication) {
|
|
2316
|
+
const lo = Math.max(0, from);
|
|
2317
|
+
let start = findSoundStart(f, from, to);
|
|
2318
|
+
if (frication) {
|
|
2319
|
+
const level = f.peakDb - FRICATION_UNDER_PEAK_DB;
|
|
2320
|
+
const absolute = f.highFloorDb + 8;
|
|
2321
|
+
for (let t = lo; t < start; t++) {
|
|
2322
|
+
const byRatio = f.highRatio[t] >= FRICATION_HIGH_RATIO && f.smoothDb[t] >= level;
|
|
2323
|
+
if (byRatio || f.highDb[t] >= absolute) {
|
|
2324
|
+
start = t;
|
|
2325
|
+
break;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
const foot = f.quietDb + 6;
|
|
2330
|
+
const k = Math.max(1, msToFrame(6));
|
|
2331
|
+
while (start > lo) {
|
|
2332
|
+
const back = Math.max(lo, start - k);
|
|
2333
|
+
const stillFalling = f.smoothDb[start] - f.smoothDb[back] >= ATTACK_SLOPE_DB;
|
|
2334
|
+
if (!stillFalling && f.smoothDb[start - 1] <= foot) break;
|
|
2335
|
+
start--;
|
|
2336
|
+
}
|
|
2337
|
+
return start;
|
|
2338
|
+
}
|
|
2339
|
+
function findVoiceOnset(f, from, to) {
|
|
2340
|
+
const need = Math.ceil(VOICED_RUN_MS / HOP_MS);
|
|
2341
|
+
const hi = Math.min(f.n, to);
|
|
2342
|
+
for (let t = Math.max(0, from); t < hi; t++) {
|
|
2343
|
+
if (f.voiced[t] < VOICED_THRESHOLD) continue;
|
|
2344
|
+
let run = 0;
|
|
2345
|
+
while (t + run < hi && f.voiced[t + run] >= VOICED_THRESHOLD) run++;
|
|
2346
|
+
if (run >= need) return t;
|
|
2347
|
+
t += run;
|
|
2348
|
+
}
|
|
2349
|
+
return -1;
|
|
2350
|
+
}
|
|
2351
|
+
function findVoicedRelease(f, from, to) {
|
|
2352
|
+
const lo = Math.max(1, from);
|
|
2353
|
+
const hi = Math.min(f.n - 1, to);
|
|
2354
|
+
if (hi <= lo) return lo;
|
|
2355
|
+
const k = Math.max(1, msToFrame(10));
|
|
2356
|
+
let best = lo;
|
|
2357
|
+
let bestScore = -Infinity;
|
|
2358
|
+
for (let t = lo; t < hi; t++) {
|
|
2359
|
+
const rise = f.smoothDb[Math.min(f.n - 1, t + k)] - f.smoothDb[Math.max(0, t - k)];
|
|
2360
|
+
const deSibilance = (f.highRatio[Math.max(0, t - k)] - f.highRatio[Math.min(f.n - 1, t + k)]) * 30;
|
|
2361
|
+
const score = rise + deSibilance + f.flux[t];
|
|
2362
|
+
if (score > bestScore) {
|
|
2363
|
+
bestScore = score;
|
|
2364
|
+
best = t;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
return best;
|
|
2368
|
+
}
|
|
2369
|
+
function findVowelStable(f, vowelOnset, limit) {
|
|
2370
|
+
const minMs = 45;
|
|
2371
|
+
const maxMs = 110;
|
|
2372
|
+
const from = vowelOnset + msToFrame(minMs);
|
|
2373
|
+
const to = Math.min(limit, vowelOnset + msToFrame(maxMs));
|
|
2374
|
+
if (to <= from) return Math.min(limit, vowelOnset + msToFrame(minMs));
|
|
2375
|
+
const calm = smooth(f.flux, Math.max(1, msToFrame(12)));
|
|
2376
|
+
const need = Math.ceil(20 / HOP_MS);
|
|
2377
|
+
for (let t = from; t < to - need; t++) {
|
|
2378
|
+
let steady = true;
|
|
2379
|
+
for (let k = 0; k < need; k++) {
|
|
2380
|
+
if (calm[t + k] > 1.05) {
|
|
2381
|
+
steady = false;
|
|
2382
|
+
break;
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
if (steady) return t;
|
|
2386
|
+
}
|
|
2387
|
+
return to;
|
|
2388
|
+
}
|
|
2389
|
+
function findDecayStart(f, from, soundEnd) {
|
|
2390
|
+
let peak = from;
|
|
2391
|
+
for (let t = from; t <= soundEnd; t++) {
|
|
2392
|
+
if (f.smoothDb[t] > f.smoothDb[peak]) peak = t;
|
|
2393
|
+
}
|
|
2394
|
+
const th = f.smoothDb[peak] - DECAY_DROP_DB;
|
|
2395
|
+
const need = Math.ceil(24 / HOP_MS);
|
|
2396
|
+
for (let t = peak; t <= soundEnd - need; t++) {
|
|
2397
|
+
let falling = true;
|
|
2398
|
+
for (let k = 0; k < need; k++) {
|
|
2399
|
+
if (f.smoothDb[t + k] >= th) {
|
|
2400
|
+
falling = false;
|
|
2401
|
+
break;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
if (falling) return Math.max(from, t - RELEASE_GUARD_FRAMES);
|
|
2405
|
+
}
|
|
2406
|
+
return Math.max(from, soundEnd - RELEASE_GUARD_FRAMES);
|
|
2407
|
+
}
|
|
2408
|
+
function hasPrevoicing(f, consStart, vowelOnset) {
|
|
2409
|
+
if (vowelOnset - consStart < msToFrame(25)) return false;
|
|
2410
|
+
let voicedFrames = 0;
|
|
2411
|
+
for (let t = consStart; t < vowelOnset; t++) {
|
|
2412
|
+
if (f.voiced[t] >= VOICED_THRESHOLD) voicedFrames++;
|
|
2413
|
+
}
|
|
2414
|
+
return voicedFrames >= (vowelOnset - consStart) * 0.6;
|
|
2415
|
+
}
|
|
2416
|
+
function locateMora(f, cls, from, to) {
|
|
2417
|
+
const art = ARTICULATION[cls];
|
|
2418
|
+
const consStart = findConsonantStart(f, from, to, art.fricationOnset);
|
|
2419
|
+
const searchEnd = Math.min(
|
|
2420
|
+
to,
|
|
2421
|
+
consStart + msToFrame(art.maxConsonantMs + 60)
|
|
2422
|
+
);
|
|
2423
|
+
let vowelOnset;
|
|
2424
|
+
if (!art.voicedConsonant) {
|
|
2425
|
+
const v = findVoiceOnset(f, consStart, searchEnd);
|
|
2426
|
+
vowelOnset = v >= 0 ? v : consStart + msToFrame(30);
|
|
2427
|
+
} else if (cls === "vowel" || cls === "nasalN") {
|
|
2428
|
+
const v = findVoiceOnset(f, consStart, searchEnd);
|
|
2429
|
+
vowelOnset = v >= 0 ? v : consStart;
|
|
2430
|
+
} else {
|
|
2431
|
+
const voiceStart = findVoiceOnset(f, consStart, searchEnd);
|
|
2432
|
+
const base = voiceStart >= 0 ? voiceStart : consStart;
|
|
2433
|
+
vowelOnset = findVoicedRelease(
|
|
2434
|
+
f,
|
|
2435
|
+
base + msToFrame(MIN_VOICED_CONSONANT_MS),
|
|
2436
|
+
Math.min(to, base + msToFrame(art.maxConsonantMs))
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
vowelOnset = clamp2(vowelOnset, consStart, Math.max(consStart, to - 1));
|
|
2440
|
+
return {
|
|
2441
|
+
consStart,
|
|
2442
|
+
vowelOnset,
|
|
2443
|
+
stable: findVowelStable(f, vowelOnset, to),
|
|
2444
|
+
prevoiced: cls === "plosiveVoiced" && hasPrevoicing(f, consStart, vowelOnset)
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
function round(ms) {
|
|
2448
|
+
return Math.round(ms * 1e3) / 1e3;
|
|
2449
|
+
}
|
|
2450
|
+
function buildEntry(wav, alias, f, cls, pos, endFrame) {
|
|
2451
|
+
const art = ARTICULATION[cls];
|
|
2452
|
+
let offsetMs = frameTimeMs(pos.consStart) - art.leadInMs;
|
|
2453
|
+
const vowelMs = frameTimeMs(pos.vowelOnset);
|
|
2454
|
+
offsetMs = Math.max(offsetMs, vowelMs - art.maxConsonantMs);
|
|
2455
|
+
offsetMs = clamp2(offsetMs, 0, Math.max(0, vowelMs - 1));
|
|
2456
|
+
const pre = Math.max(1, vowelMs - offsetMs);
|
|
2457
|
+
let overlap;
|
|
2458
|
+
if (art.fixedOverlapMs !== void 0) {
|
|
2459
|
+
overlap = art.fixedOverlapMs;
|
|
2460
|
+
} else {
|
|
2461
|
+
overlap = clamp2(
|
|
2462
|
+
art.overlapRatio * pre,
|
|
2463
|
+
Math.min(MIN_OVERLAP_MS, pre),
|
|
2464
|
+
Math.min(MAX_OVERLAP_MS, pre)
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
const consonant = Math.max(
|
|
2468
|
+
pre + 10,
|
|
2469
|
+
Math.abs(overlap) + 10,
|
|
2470
|
+
frameTimeMs(pos.stable) - offsetMs
|
|
2471
|
+
);
|
|
2472
|
+
const endMs = Math.max(frameTimeMs(endFrame), offsetMs + consonant + 30);
|
|
2473
|
+
const cutoff = -(Math.min(endMs, f.durationMs) - offsetMs);
|
|
2474
|
+
return {
|
|
2475
|
+
wav,
|
|
2476
|
+
alias,
|
|
2477
|
+
offset: round(offsetMs),
|
|
2478
|
+
consonant: round(consonant),
|
|
2479
|
+
cutoff: round(cutoff),
|
|
2480
|
+
pre: round(pre),
|
|
2481
|
+
overlap: round(overlap)
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
function estimateSolo(wav, f, syl, aliases) {
|
|
2485
|
+
const soundStart = findSoundStart(f, 0, f.n);
|
|
2486
|
+
const soundEnd = findSoundEnd(f, soundStart, f.n);
|
|
2487
|
+
const pos = locateMora(f, syl.cls, 0, soundEnd);
|
|
2488
|
+
const end = findDecayStart(f, pos.stable, soundEnd);
|
|
2489
|
+
return aliases.map((alias) => buildEntry(wav, alias, f, syl.cls, pos, end));
|
|
2490
|
+
}
|
|
2491
|
+
function estimateVowelJoin(wav, f, syl, alias) {
|
|
2492
|
+
const soundStart = findSoundStart(f, 0, f.n);
|
|
2493
|
+
const soundEnd = findSoundEnd(f, soundStart, f.n);
|
|
2494
|
+
const pos = locateMora(f, syl.cls, 0, soundEnd);
|
|
2495
|
+
const end = findDecayStart(f, pos.stable, soundEnd);
|
|
2496
|
+
const pre = 50;
|
|
2497
|
+
const overlap = 100;
|
|
2498
|
+
const consonant = 110;
|
|
2499
|
+
const offsetMs = frameTimeMs(pos.stable);
|
|
2500
|
+
const endMs = frameTimeMs(end);
|
|
2501
|
+
if (offsetMs < 0 || endMs - offsetMs < consonant + 40) return null;
|
|
2502
|
+
return {
|
|
2503
|
+
wav,
|
|
2504
|
+
alias,
|
|
2505
|
+
offset: round(offsetMs),
|
|
2506
|
+
consonant,
|
|
2507
|
+
cutoff: round(-(endMs - offsetMs)),
|
|
2508
|
+
pre,
|
|
2509
|
+
overlap
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
var GRID_SNAP_WEIGHT = 0.6;
|
|
2513
|
+
function fitLine(values) {
|
|
2514
|
+
const n = values.length;
|
|
2515
|
+
if (n < 2) return { intercept: values[0] ?? 0, slope: 0 };
|
|
2516
|
+
let sx = 0;
|
|
2517
|
+
let sy = 0;
|
|
2518
|
+
let sxx = 0;
|
|
2519
|
+
let sxy = 0;
|
|
2520
|
+
for (let i = 0; i < n; i++) {
|
|
2521
|
+
sx += i;
|
|
2522
|
+
sy += values[i];
|
|
2523
|
+
sxx += i * i;
|
|
2524
|
+
sxy += i * values[i];
|
|
2525
|
+
}
|
|
2526
|
+
const denom = n * sxx - sx * sx;
|
|
2527
|
+
const slope = denom !== 0 ? (n * sxy - sx * sy) / denom : 0;
|
|
2528
|
+
return { intercept: (sy - slope * sx) / n, slope };
|
|
2529
|
+
}
|
|
2530
|
+
function onsetStrength(f) {
|
|
2531
|
+
const k = Math.max(1, msToFrame(12));
|
|
2532
|
+
const out = new Float32Array(f.n);
|
|
2533
|
+
for (let t = 0; t < f.n; t++) {
|
|
2534
|
+
const rise = f.smoothDb[Math.min(f.n - 1, t + k)] - f.smoothDb[Math.max(0, t - k)];
|
|
2535
|
+
out[t] = f.flux[t] + Math.max(0, rise) / 3;
|
|
2536
|
+
}
|
|
2537
|
+
return smooth(out, Math.max(1, msToFrame(8)));
|
|
2538
|
+
}
|
|
2539
|
+
function detectGrid(f, count) {
|
|
2540
|
+
if (count < 1) return null;
|
|
2541
|
+
const strength = onsetStrength(f);
|
|
2542
|
+
const uttStart = findSoundStart(f, 0, f.n);
|
|
2543
|
+
const uttEnd = findSoundEnd(f, uttStart, f.n);
|
|
2544
|
+
const span = uttEnd - uttStart;
|
|
2545
|
+
if (span < msToFrame(80)) return null;
|
|
2546
|
+
if (count === 1) {
|
|
2547
|
+
return { start: uttStart, interval: span, onsets: [uttStart] };
|
|
2548
|
+
}
|
|
2549
|
+
const candidates = intervalCandidates(
|
|
2550
|
+
strength,
|
|
2551
|
+
uttStart,
|
|
2552
|
+
uttEnd,
|
|
2553
|
+
span / count
|
|
2554
|
+
);
|
|
2555
|
+
const best = { score: -Infinity, start: uttStart, interval: span / count };
|
|
2556
|
+
const search = (requireFit) => {
|
|
2557
|
+
for (const candidate of candidates) {
|
|
2558
|
+
for (let iv = candidate * 0.85; iv <= candidate * 1.18; iv += candidate * 0.02) {
|
|
2559
|
+
const near2 = windowArgMax(strength, Math.round(iv * 0.22));
|
|
2560
|
+
const tol = iv * 0.22;
|
|
2561
|
+
const hardEnd = uttEnd + iv * 0.4;
|
|
2562
|
+
for (let phase = uttStart - iv * 0.25; phase <= uttStart + iv * 0.5; phase += 1) {
|
|
2563
|
+
if (requireFit && phase + (count - 1) * iv > hardEnd) break;
|
|
2564
|
+
let total = 0;
|
|
2565
|
+
for (let i = 0; i < count; i++) {
|
|
2566
|
+
const centre = phase + i * iv;
|
|
2567
|
+
const at = near2[clamp2(Math.round(centre), 0, f.n - 1)];
|
|
2568
|
+
total += strength[at] - Math.abs(at - centre) / tol;
|
|
2569
|
+
}
|
|
2570
|
+
if (total > best.score) {
|
|
2571
|
+
best.score = total;
|
|
2572
|
+
best.start = phase;
|
|
2573
|
+
best.interval = iv;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
};
|
|
2579
|
+
search(true);
|
|
2580
|
+
if (!Number.isFinite(best.score)) search(false);
|
|
2581
|
+
if (!Number.isFinite(best.score)) return null;
|
|
2582
|
+
const near = windowArgMax(strength, Math.round(best.interval * 0.25));
|
|
2583
|
+
const snapped = [];
|
|
2584
|
+
for (let i = 0; i < count; i++) {
|
|
2585
|
+
snapped.push(
|
|
2586
|
+
near[clamp2(Math.round(best.start + i * best.interval), 0, f.n - 1)]
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2589
|
+
const { intercept, slope } = fitLine(snapped);
|
|
2590
|
+
const onsets = [];
|
|
2591
|
+
for (let i = 0; i < count; i++) {
|
|
2592
|
+
const fitted = intercept + slope * i;
|
|
2593
|
+
const blended = Math.round(
|
|
2594
|
+
fitted + (snapped[i] - fitted) * GRID_SNAP_WEIGHT
|
|
2595
|
+
);
|
|
2596
|
+
onsets.push(i > 0 ? Math.max(blended, onsets[i - 1] + 4) : blended);
|
|
2597
|
+
}
|
|
2598
|
+
return {
|
|
2599
|
+
start: onsets[0],
|
|
2600
|
+
interval: slope > 0 ? slope : best.interval,
|
|
2601
|
+
onsets
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
function windowArgMax(src, half) {
|
|
2605
|
+
const n = src.length;
|
|
2606
|
+
const out = new Int32Array(n);
|
|
2607
|
+
const deque = new Int32Array(n);
|
|
2608
|
+
let head = 0;
|
|
2609
|
+
let tail = 0;
|
|
2610
|
+
let next = 0;
|
|
2611
|
+
for (let i = 0; i < n; i++) {
|
|
2612
|
+
const limit = Math.min(n - 1, i + half);
|
|
2613
|
+
while (next <= limit) {
|
|
2614
|
+
while (tail > head && src[deque[tail - 1]] <= src[next]) tail--;
|
|
2615
|
+
deque[tail++] = next++;
|
|
2616
|
+
}
|
|
2617
|
+
while (deque[head] < i - half) head++;
|
|
2618
|
+
out[i] = deque[head];
|
|
2619
|
+
}
|
|
2620
|
+
return out;
|
|
2621
|
+
}
|
|
2622
|
+
function intervalCandidates(strength, from, to, fromSpan) {
|
|
2623
|
+
const lo = msToFrame(180);
|
|
2624
|
+
const hi = Math.min(msToFrame(1300), Math.floor((to - from) / 2));
|
|
2625
|
+
const out = [];
|
|
2626
|
+
if (hi > lo) {
|
|
2627
|
+
let mean = 0;
|
|
2628
|
+
for (let t = from; t < to; t++) mean += strength[t];
|
|
2629
|
+
mean /= Math.max(1, to - from);
|
|
2630
|
+
const r = new Float64Array(hi + 1);
|
|
2631
|
+
let peak = 0;
|
|
2632
|
+
for (let lag = lo; lag <= hi; lag++) {
|
|
2633
|
+
let sum = 0;
|
|
2634
|
+
let n = 0;
|
|
2635
|
+
for (let t = from; t + lag < to; t++, n++) {
|
|
2636
|
+
sum += (strength[t] - mean) * (strength[t + lag] - mean);
|
|
2637
|
+
}
|
|
2638
|
+
r[lag] = n > 0 ? sum / n : 0;
|
|
2639
|
+
if (r[lag] > peak) peak = r[lag];
|
|
2640
|
+
}
|
|
2641
|
+
if (peak > 0) {
|
|
2642
|
+
for (let lag = lo + 1; lag < hi; lag++) {
|
|
2643
|
+
if (r[lag] < peak * 0.7) continue;
|
|
2644
|
+
if (r[lag] < r[lag - 1] || r[lag] < r[lag + 1]) continue;
|
|
2645
|
+
out.push(lag);
|
|
2646
|
+
if (out.length >= 2) break;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
out.push(fromSpan);
|
|
2651
|
+
const withSubmultiples = [];
|
|
2652
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2653
|
+
const shortest = msToFrame(150);
|
|
2654
|
+
for (const c of out) {
|
|
2655
|
+
for (const divisor of [1, 2, 3]) {
|
|
2656
|
+
const key = Math.round(c / divisor);
|
|
2657
|
+
if (key < shortest || seen.has(key)) continue;
|
|
2658
|
+
seen.add(key);
|
|
2659
|
+
withSubmultiples.push(key);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
if (withSubmultiples.length === 0) withSubmultiples.push(shortest);
|
|
2663
|
+
return withSubmultiples;
|
|
2664
|
+
}
|
|
2665
|
+
function sequenceVowelOnset(f, cls, onset, next) {
|
|
2666
|
+
if (ARTICULATION[cls].voicedConsonant) return onset + SEQUENCE_ONSET_BIAS;
|
|
2667
|
+
const lo = Math.max(0, onset - msToFrame(100));
|
|
2668
|
+
const hi = Math.min(next, onset + msToFrame(150));
|
|
2669
|
+
let quietest = onset;
|
|
2670
|
+
for (let t = lo; t < hi; t++) {
|
|
2671
|
+
if (f.voiced[t] < f.voiced[quietest]) quietest = t;
|
|
2672
|
+
}
|
|
2673
|
+
if (f.voiced[quietest] >= VOICED_THRESHOLD) return onset;
|
|
2674
|
+
const resumed = findVoiceOnset(
|
|
2675
|
+
f,
|
|
2676
|
+
quietest,
|
|
2677
|
+
Math.min(next, hi + msToFrame(80))
|
|
2678
|
+
);
|
|
2679
|
+
return resumed >= 0 ? resumed : onset;
|
|
2680
|
+
}
|
|
2681
|
+
function estimateSequence(wav, f, syllables, opts = {}) {
|
|
2682
|
+
const grid = detectGrid(f, syllables.length);
|
|
2683
|
+
if (!grid) return [];
|
|
2684
|
+
const intervalMs = framesToMs(grid.interval);
|
|
2685
|
+
const pre = clamp2(intervalMs / 2, 60, MAX_SEQUENCE_PRE_MS);
|
|
2686
|
+
const overlap = round(pre / 3);
|
|
2687
|
+
const consonant = round(pre * 1.5);
|
|
2688
|
+
const suffix = opts.suffix ?? "";
|
|
2689
|
+
const prefix = opts.prefix ?? "";
|
|
2690
|
+
const entries = [];
|
|
2691
|
+
for (let i = 0; i < syllables.length; i++) {
|
|
2692
|
+
const syl = syllables[i];
|
|
2693
|
+
const onset = grid.onsets[i];
|
|
2694
|
+
const next = i + 1 < syllables.length ? grid.onsets[i + 1] : findSoundEnd(f, onset, f.n);
|
|
2695
|
+
let vowelFrame;
|
|
2696
|
+
if (i === 0) {
|
|
2697
|
+
vowelFrame = locateMora(f, syl.cls, 0, next).vowelOnset;
|
|
2698
|
+
} else {
|
|
2699
|
+
vowelFrame = sequenceVowelOnset(f, syl.cls, onset, next);
|
|
2700
|
+
}
|
|
2701
|
+
const noteMs = frameTimeMs(vowelFrame);
|
|
2702
|
+
const offsetMs = clamp2(noteMs - pre, 0, Math.max(0, noteMs - 1));
|
|
2703
|
+
const actualPre = noteMs - offsetMs;
|
|
2704
|
+
const endMs = Math.min(f.durationMs, noteMs + intervalMs * 2 / 3);
|
|
2705
|
+
const alias = i === 0 ? `- ${prefix}${syl.kana}${suffix}` : `${syllables[i - 1].vowel} ${syl.kana}${suffix}`;
|
|
2706
|
+
entries.push({
|
|
2707
|
+
wav,
|
|
2708
|
+
alias,
|
|
2709
|
+
offset: round(offsetMs),
|
|
2710
|
+
consonant: Math.max(round(consonant), round(actualPre + 20)),
|
|
2711
|
+
cutoff: round(-Math.max(endMs - offsetMs, consonant + 40)),
|
|
2712
|
+
pre: round(actualPre),
|
|
2713
|
+
overlap: i === 0 ? 0 : Math.min(overlap, round(actualPre))
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
if (opts.trailingRest && syllables.length > 0) {
|
|
2717
|
+
const last = syllables[syllables.length - 1];
|
|
2718
|
+
const lastOnset = grid.onsets[syllables.length - 1];
|
|
2719
|
+
const soundEnd = findSoundEnd(f, lastOnset, f.n);
|
|
2720
|
+
const decay = findDecayStart(f, lastOnset, soundEnd);
|
|
2721
|
+
const noteMs = frameTimeMs(decay);
|
|
2722
|
+
const offsetMs = clamp2(noteMs - pre, 0, Math.max(0, noteMs - 1));
|
|
2723
|
+
const actualPre = noteMs - offsetMs;
|
|
2724
|
+
entries.push({
|
|
2725
|
+
wav,
|
|
2726
|
+
alias: `${last.vowel} R${suffix}`,
|
|
2727
|
+
offset: round(offsetMs),
|
|
2728
|
+
consonant: round(consonant),
|
|
2729
|
+
cutoff: round(
|
|
2730
|
+
-(Math.min(f.durationMs, frameTimeMs(soundEnd)) - offsetMs)
|
|
2731
|
+
),
|
|
2732
|
+
pre: round(actualPre),
|
|
2733
|
+
overlap: Math.min(overlap, round(actualPre))
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
return entries;
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
// src/oto/kana.ts
|
|
2740
|
+
var CLASS_OF = {
|
|
2741
|
+
"": "vowel",
|
|
2742
|
+
k: "plosiveVoiceless",
|
|
2743
|
+
ky: "plosiveVoiceless",
|
|
2744
|
+
t: "plosiveVoiceless",
|
|
2745
|
+
ty: "plosiveVoiceless",
|
|
2746
|
+
p: "plosiveVoiceless",
|
|
2747
|
+
py: "plosiveVoiceless",
|
|
2748
|
+
g: "plosiveVoiced",
|
|
2749
|
+
gy: "plosiveVoiced",
|
|
2750
|
+
d: "plosiveVoiced",
|
|
2751
|
+
dy: "plosiveVoiced",
|
|
2752
|
+
b: "plosiveVoiced",
|
|
2753
|
+
by: "plosiveVoiced",
|
|
2754
|
+
s: "fricativeVoiceless",
|
|
2755
|
+
sh: "fricativeVoiceless",
|
|
2756
|
+
h: "fricativeVoiceless",
|
|
2757
|
+
hy: "fricativeVoiceless",
|
|
2758
|
+
f: "fricativeVoiceless",
|
|
2759
|
+
z: "fricativeVoiced",
|
|
2760
|
+
j: "fricativeVoiced",
|
|
2761
|
+
v: "fricativeVoiced",
|
|
2762
|
+
ts: "affricate",
|
|
2763
|
+
ch: "affricate",
|
|
2764
|
+
n: "nasal",
|
|
2765
|
+
ny: "nasal",
|
|
2766
|
+
m: "nasal",
|
|
2767
|
+
my: "nasal",
|
|
2768
|
+
r: "liquid",
|
|
2769
|
+
ry: "liquid",
|
|
2770
|
+
y: "semivowel",
|
|
2771
|
+
w: "semivowel"
|
|
2772
|
+
};
|
|
2773
|
+
var KANA = {
|
|
2774
|
+
\u3042: " a",
|
|
2775
|
+
\u3044: " i",
|
|
2776
|
+
\u3046: " u",
|
|
2777
|
+
\u3048: " e",
|
|
2778
|
+
\u304A: " o",
|
|
2779
|
+
\u3093: " n",
|
|
2780
|
+
\u304B: "k a",
|
|
2781
|
+
\u304D: "k i",
|
|
2782
|
+
\u304F: "k u",
|
|
2783
|
+
\u3051: "k e",
|
|
2784
|
+
\u3053: "k o",
|
|
2785
|
+
\u304C: "g a",
|
|
2786
|
+
\u304E: "g i",
|
|
2787
|
+
\u3050: "g u",
|
|
2788
|
+
\u3052: "g e",
|
|
2789
|
+
\u3054: "g o",
|
|
2790
|
+
\u3055: "s a",
|
|
2791
|
+
\u3057: "sh i",
|
|
2792
|
+
\u3059: "s u",
|
|
2793
|
+
\u305B: "s e",
|
|
2794
|
+
\u305D: "s o",
|
|
2795
|
+
\u3056: "z a",
|
|
2796
|
+
\u3058: "j i",
|
|
2797
|
+
\u305A: "z u",
|
|
2798
|
+
\u305C: "z e",
|
|
2799
|
+
\u305E: "z o",
|
|
2800
|
+
\u305F: "t a",
|
|
2801
|
+
\u3061: "ch i",
|
|
2802
|
+
\u3064: "ts u",
|
|
2803
|
+
\u3066: "t e",
|
|
2804
|
+
\u3068: "t o",
|
|
2805
|
+
\u3060: "d a",
|
|
2806
|
+
\u3062: "j i",
|
|
2807
|
+
\u3065: "z u",
|
|
2808
|
+
\u3067: "d e",
|
|
2809
|
+
\u3069: "d o",
|
|
2810
|
+
\u306A: "n a",
|
|
2811
|
+
\u306B: "n i",
|
|
2812
|
+
\u306C: "n u",
|
|
2813
|
+
\u306D: "n e",
|
|
2814
|
+
\u306E: "n o",
|
|
2815
|
+
\u306F: "h a",
|
|
2816
|
+
\u3072: "h i",
|
|
2817
|
+
\u3075: "f u",
|
|
2818
|
+
\u3078: "h e",
|
|
2819
|
+
\u307B: "h o",
|
|
2820
|
+
\u3070: "b a",
|
|
2821
|
+
\u3073: "b i",
|
|
2822
|
+
\u3076: "b u",
|
|
2823
|
+
\u3079: "b e",
|
|
2824
|
+
\u307C: "b o",
|
|
2825
|
+
\u3071: "p a",
|
|
2826
|
+
\u3074: "p i",
|
|
2827
|
+
\u3077: "p u",
|
|
2828
|
+
\u307A: "p e",
|
|
2829
|
+
\u307D: "p o",
|
|
2830
|
+
\u307E: "m a",
|
|
2831
|
+
\u307F: "m i",
|
|
2832
|
+
\u3080: "m u",
|
|
2833
|
+
\u3081: "m e",
|
|
2834
|
+
\u3082: "m o",
|
|
2835
|
+
\u3084: "y a",
|
|
2836
|
+
\u3086: "y u",
|
|
2837
|
+
\u3088: "y o",
|
|
2838
|
+
\u3089: "r a",
|
|
2839
|
+
\u308A: "r i",
|
|
2840
|
+
\u308B: "r u",
|
|
2841
|
+
\u308C: "r e",
|
|
2842
|
+
\u308D: "r o",
|
|
2843
|
+
\u308F: "w a",
|
|
2844
|
+
\u3090: "w i",
|
|
2845
|
+
\u3091: "w e",
|
|
2846
|
+
\u3092: "w o",
|
|
2847
|
+
\u3094: "v u"
|
|
2848
|
+
};
|
|
2849
|
+
var SMALL = {
|
|
2850
|
+
\u3041: "a",
|
|
2851
|
+
\u3043: "i",
|
|
2852
|
+
\u3045: "u",
|
|
2853
|
+
\u3047: "e",
|
|
2854
|
+
\u3049: "o",
|
|
2855
|
+
\u3083: "a",
|
|
2856
|
+
\u3085: "u",
|
|
2857
|
+
\u3087: "o",
|
|
2858
|
+
\u308E: "a"
|
|
2859
|
+
};
|
|
2860
|
+
var DIGRAPH = {
|
|
2861
|
+
\u3057\u3083: "sh a",
|
|
2862
|
+
\u3057\u3085: "sh u",
|
|
2863
|
+
\u3057\u3087: "sh o",
|
|
2864
|
+
\u3057\u3047: "sh e",
|
|
2865
|
+
\u3058\u3083: "j a",
|
|
2866
|
+
\u3058\u3085: "j u",
|
|
2867
|
+
\u3058\u3087: "j o",
|
|
2868
|
+
\u3058\u3047: "j e",
|
|
2869
|
+
\u3061\u3083: "ch a",
|
|
2870
|
+
\u3061\u3085: "ch u",
|
|
2871
|
+
\u3061\u3087: "ch o",
|
|
2872
|
+
\u3061\u3047: "ch e",
|
|
2873
|
+
\u3062\u3083: "j a",
|
|
2874
|
+
\u3062\u3085: "j u",
|
|
2875
|
+
\u3062\u3087: "j o",
|
|
2876
|
+
\u3064\u3041: "ts a",
|
|
2877
|
+
\u3064\u3043: "ts i",
|
|
2878
|
+
\u3064\u3047: "ts e",
|
|
2879
|
+
\u3064\u3049: "ts o",
|
|
2880
|
+
\u3066\u3043: "t i",
|
|
2881
|
+
\u3066\u3083: "ty a",
|
|
2882
|
+
\u3066\u3085: "ty u",
|
|
2883
|
+
\u3066\u3087: "ty o",
|
|
2884
|
+
\u3067\u3043: "d i",
|
|
2885
|
+
\u3067\u3083: "dy a",
|
|
2886
|
+
\u3067\u3085: "dy u",
|
|
2887
|
+
\u3067\u3087: "dy o",
|
|
2888
|
+
\u3068\u3045: "t u",
|
|
2889
|
+
\u3069\u3045: "d u",
|
|
2890
|
+
\u3075\u3041: "f a",
|
|
2891
|
+
\u3075\u3043: "f i",
|
|
2892
|
+
\u3075\u3047: "f e",
|
|
2893
|
+
\u3075\u3049: "f o",
|
|
2894
|
+
\u3075\u3085: "f u",
|
|
2895
|
+
\u3094\u3041: "v a",
|
|
2896
|
+
\u3094\u3043: "v i",
|
|
2897
|
+
\u3094\u3047: "v e",
|
|
2898
|
+
\u3094\u3049: "v o",
|
|
2899
|
+
\u3094\u3085: "v u",
|
|
2900
|
+
\u304F\u3041: "k a",
|
|
2901
|
+
\u304F\u3043: "k i",
|
|
2902
|
+
\u304F\u3047: "k e",
|
|
2903
|
+
\u304F\u3049: "k o",
|
|
2904
|
+
\u3050\u3041: "g a",
|
|
2905
|
+
\u3050\u3043: "g i",
|
|
2906
|
+
\u3050\u3047: "g e",
|
|
2907
|
+
\u3050\u3049: "g o",
|
|
2908
|
+
\u3059\u3043: "s i",
|
|
2909
|
+
\u305A\u3043: "z i"
|
|
2910
|
+
};
|
|
2911
|
+
function toHiragana(s) {
|
|
2912
|
+
let out = "";
|
|
2913
|
+
for (const ch of s) {
|
|
2914
|
+
const c = ch.codePointAt(0) ?? 0;
|
|
2915
|
+
out += c >= 12449 && c <= 12534 ? String.fromCodePoint(c - 96) : ch;
|
|
2916
|
+
}
|
|
2917
|
+
return out;
|
|
2918
|
+
}
|
|
2919
|
+
function make(kana, pair) {
|
|
2920
|
+
const sp = pair.indexOf(" ");
|
|
2921
|
+
const consonant = pair.slice(0, sp);
|
|
2922
|
+
const vowel = pair.slice(sp + 1);
|
|
2923
|
+
return { kana, consonant, vowel, cls: CLASS_OF[consonant] ?? "vowel" };
|
|
2924
|
+
}
|
|
2925
|
+
function readSyllable(src, hira, i) {
|
|
2926
|
+
const two = hira.slice(i, i + 2);
|
|
2927
|
+
if (two.length === 2) {
|
|
2928
|
+
const explicit = DIGRAPH[two];
|
|
2929
|
+
if (explicit) return { syl: make(src.slice(i, i + 2), explicit), len: 2 };
|
|
2930
|
+
const small = SMALL[two[1]];
|
|
2931
|
+
const base = KANA[two[0]];
|
|
2932
|
+
if (small && base) {
|
|
2933
|
+
const sp = base.indexOf(" ");
|
|
2934
|
+
const baseConsonant = base.slice(0, sp);
|
|
2935
|
+
const baseVowel = base.slice(sp + 1);
|
|
2936
|
+
if (baseVowel === "i" && CLASS_OF[`${baseConsonant}y`]) {
|
|
2937
|
+
const pair = `${baseConsonant}y ${small}`;
|
|
2938
|
+
return { syl: make(src.slice(i, i + 2), pair), len: 2 };
|
|
2939
|
+
}
|
|
2940
|
+
if (!baseConsonant) {
|
|
2941
|
+
const glide = baseVowel === "i" ? "y" : baseVowel === "u" ? "w" : "";
|
|
2942
|
+
const syl = make(src.slice(i, i + 2), `${glide} ${small}`);
|
|
2943
|
+
return { syl: { ...syl, cls: "semivowel" }, len: 2 };
|
|
2944
|
+
}
|
|
2945
|
+
return {
|
|
2946
|
+
syl: make(src.slice(i, i + 2), `${baseConsonant} ${small}`),
|
|
2947
|
+
len: 2
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
const one = KANA[hira[i]];
|
|
2952
|
+
if (one) return { syl: make(src.slice(i, i + 1), one), len: 1 };
|
|
2953
|
+
return null;
|
|
2954
|
+
}
|
|
2955
|
+
function splitKana(text) {
|
|
2956
|
+
const src = text.normalize("NFC");
|
|
2957
|
+
const hira = toHiragana(src);
|
|
2958
|
+
const out = [];
|
|
2959
|
+
for (let i = 0; i < hira.length; ) {
|
|
2960
|
+
const ch = hira[i];
|
|
2961
|
+
if (ch === "\u3063" || ch === "\u30FC") {
|
|
2962
|
+
i++;
|
|
2963
|
+
continue;
|
|
2964
|
+
}
|
|
2965
|
+
const read = readSyllable(src, hira, i);
|
|
2966
|
+
if (!read) return null;
|
|
2967
|
+
out.push(read.syl);
|
|
2968
|
+
i += read.len;
|
|
2969
|
+
}
|
|
2970
|
+
return out.length > 0 ? out : null;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
// src/oto/generate.ts
|
|
2974
|
+
var REST = /[RrRr]$|息$|吸$/;
|
|
2975
|
+
function transcribe(filename) {
|
|
2976
|
+
let body = filename.replace(/\.[^.]+$/, "");
|
|
2977
|
+
body = body.replace(/^[_\-\s]+/, "");
|
|
2978
|
+
body = body.replace(/_[A-G][#b]?-?\d$/i, "");
|
|
2979
|
+
body = body.trim();
|
|
2980
|
+
if (!body) return null;
|
|
2981
|
+
let mark = "";
|
|
2982
|
+
const arrows = /[↑↓→]+$/.exec(body);
|
|
2983
|
+
if (arrows) {
|
|
2984
|
+
mark = arrows[0];
|
|
2985
|
+
body = body.slice(0, -arrows[0].length);
|
|
2986
|
+
}
|
|
2987
|
+
let trailingRest = false;
|
|
2988
|
+
if (REST.test(body)) {
|
|
2989
|
+
trailingRest = true;
|
|
2990
|
+
body = body.replace(REST, "");
|
|
2991
|
+
}
|
|
2992
|
+
const take = /[A-Za-z0-9'][A-Za-z0-9']?$/.exec(body);
|
|
2993
|
+
if (take && body.length > take[0].length) {
|
|
2994
|
+
mark = take[0] + mark;
|
|
2995
|
+
body = body.slice(0, -take[0].length);
|
|
2996
|
+
}
|
|
2997
|
+
let prefix = "";
|
|
2998
|
+
const head = /^[A-Za-z]{1,2}(?=[ぁ-ゟァ-ヿ])/.exec(body);
|
|
2999
|
+
if (head) {
|
|
3000
|
+
prefix = head[0];
|
|
3001
|
+
body = body.slice(head[0].length);
|
|
3002
|
+
}
|
|
3003
|
+
if (!body) return null;
|
|
3004
|
+
const syllables = splitKana(body);
|
|
3005
|
+
if (!syllables) return null;
|
|
3006
|
+
return { syllables, trailingRest, prefix, mark };
|
|
3007
|
+
}
|
|
3008
|
+
function suffixFromFolderName(folder) {
|
|
3009
|
+
if (/^[A-G][#b]?-?\d$/.test(folder)) return `_${folder}`;
|
|
3010
|
+
const tagged = /_([A-G][#b]?-?\d)(?![0-9A-Za-z])/.exec(folder);
|
|
3011
|
+
return tagged ? `_${tagged[1]}` : "";
|
|
3012
|
+
}
|
|
3013
|
+
function aliasesFor(name, headAliases) {
|
|
3014
|
+
return headAliases ? [name, `- ${name}`] : [name];
|
|
3015
|
+
}
|
|
3016
|
+
function generateOtoForFile(file, options = {}) {
|
|
3017
|
+
const suffix = options.suffix ?? "";
|
|
3018
|
+
const headAliases = options.headAliases ?? true;
|
|
3019
|
+
const vowelJoinAliases = options.vowelJoinAliases ?? true;
|
|
3020
|
+
const transcript = transcribe(file.name);
|
|
3021
|
+
if (!transcript) {
|
|
3022
|
+
return {
|
|
3023
|
+
entries: [],
|
|
3024
|
+
skipped: { wav: file.name, reason: "filename is not kana" },
|
|
3025
|
+
style: null
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
let frames;
|
|
3029
|
+
try {
|
|
3030
|
+
frames = analyze(parseWav(file.data));
|
|
3031
|
+
} catch (err2) {
|
|
3032
|
+
return {
|
|
3033
|
+
entries: [],
|
|
3034
|
+
skipped: {
|
|
3035
|
+
wav: file.name,
|
|
3036
|
+
reason: err2 instanceof Error ? err2.message : String(err2)
|
|
3037
|
+
},
|
|
3038
|
+
style: null
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
if (frames.n < 8) {
|
|
3042
|
+
return {
|
|
3043
|
+
entries: [],
|
|
3044
|
+
skipped: { wav: file.name, reason: "too short to analyse" },
|
|
3045
|
+
style: null
|
|
3046
|
+
};
|
|
3047
|
+
}
|
|
3048
|
+
const { syllables, trailingRest, prefix, mark } = transcript;
|
|
3049
|
+
if (syllables.length === 1 && !trailingRest) {
|
|
3050
|
+
const syl = syllables[0];
|
|
3051
|
+
const name = `${prefix}${syl.kana}${mark}${suffix}`;
|
|
3052
|
+
const entries2 = estimateSolo(
|
|
3053
|
+
file.name,
|
|
3054
|
+
frames,
|
|
3055
|
+
syl,
|
|
3056
|
+
aliasesFor(name, headAliases)
|
|
3057
|
+
);
|
|
3058
|
+
if (vowelJoinAliases && (syl.cls === "vowel" || syl.cls === "nasalN")) {
|
|
3059
|
+
const join = estimateVowelJoin(file.name, frames, syl, `* ${name}`);
|
|
3060
|
+
if (join) entries2.push(join);
|
|
3061
|
+
}
|
|
3062
|
+
return { entries: entries2, skipped: null, style: "solo" };
|
|
3063
|
+
}
|
|
3064
|
+
const entries = estimateSequence(file.name, frames, syllables, {
|
|
3065
|
+
suffix: `${mark}${suffix}`,
|
|
3066
|
+
prefix,
|
|
3067
|
+
trailingRest
|
|
3068
|
+
});
|
|
3069
|
+
if (entries.length === 0) {
|
|
3070
|
+
return {
|
|
3071
|
+
entries: [],
|
|
3072
|
+
skipped: { wav: file.name, reason: "could not segment the phrase" },
|
|
3073
|
+
style: null
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
return { entries, skipped: null, style: "sequence" };
|
|
3077
|
+
}
|
|
3078
|
+
function generateOto(files, options = {}) {
|
|
3079
|
+
const entries = [];
|
|
3080
|
+
const skipped = [];
|
|
3081
|
+
let solo = 0;
|
|
3082
|
+
let sequence = 0;
|
|
3083
|
+
for (const file of files) {
|
|
3084
|
+
const result = generateOtoForFile(file, options);
|
|
3085
|
+
entries.push(...result.entries);
|
|
3086
|
+
if (result.skipped) skipped.push(result.skipped);
|
|
3087
|
+
if (result.style === "solo") solo++;
|
|
3088
|
+
if (result.style === "sequence") sequence++;
|
|
3089
|
+
}
|
|
3090
|
+
return { entries, skipped, style: summarise(solo, sequence) };
|
|
3091
|
+
}
|
|
3092
|
+
function summarise(solo, sequence) {
|
|
3093
|
+
if (solo && sequence) return "mixed";
|
|
3094
|
+
if (sequence) return "sequence";
|
|
3095
|
+
if (solo) return "solo";
|
|
3096
|
+
return "empty";
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
// src/oto/write.ts
|
|
3100
|
+
var reverseTable = null;
|
|
3101
|
+
function shiftJisTable() {
|
|
3102
|
+
if (reverseTable) return reverseTable;
|
|
3103
|
+
const table = /* @__PURE__ */ new Map();
|
|
3104
|
+
const decoder = new TextDecoder("shift_jis", { fatal: false });
|
|
3105
|
+
for (let b = 0; b < 128; b++) {
|
|
3106
|
+
table.set(decoder.decode(new Uint8Array([b])), b);
|
|
3107
|
+
}
|
|
3108
|
+
for (let b = 161; b <= 223; b++) {
|
|
3109
|
+
table.set(decoder.decode(new Uint8Array([b])), b);
|
|
3110
|
+
}
|
|
3111
|
+
const pair = new Uint8Array(2);
|
|
3112
|
+
for (let lead = 129; lead <= 252; lead++) {
|
|
3113
|
+
if (lead > 159 && lead < 224) continue;
|
|
3114
|
+
pair[0] = lead;
|
|
3115
|
+
for (let trail = 64; trail <= 252; trail++) {
|
|
3116
|
+
if (trail === 127) continue;
|
|
3117
|
+
pair[1] = trail;
|
|
3118
|
+
const ch = decoder.decode(pair);
|
|
3119
|
+
if (ch.length !== 1 || ch === "\uFFFD") continue;
|
|
3120
|
+
if (!table.has(ch)) table.set(ch, lead << 8 | trail);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
reverseTable = table;
|
|
3124
|
+
return table;
|
|
3125
|
+
}
|
|
3126
|
+
function encodeShiftJis(text) {
|
|
3127
|
+
const table = shiftJisTable();
|
|
3128
|
+
const out = [];
|
|
3129
|
+
for (const ch of text) {
|
|
3130
|
+
const code = table.get(ch);
|
|
3131
|
+
if (code === void 0) out.push(63);
|
|
3132
|
+
else if (code > 255) out.push(code >> 8, code & 255);
|
|
3133
|
+
else out.push(code);
|
|
3134
|
+
}
|
|
3135
|
+
return new Uint8Array(out);
|
|
3136
|
+
}
|
|
3137
|
+
function num(v) {
|
|
3138
|
+
const s = v.toFixed(3);
|
|
3139
|
+
return s.replace(/\.?0+$/, "") || "0";
|
|
3140
|
+
}
|
|
3141
|
+
function formatOto(entries) {
|
|
3142
|
+
return entries.map(
|
|
3143
|
+
(e) => `${e.wav}=${e.alias},${num(e.offset)},${num(e.consonant)},${num(e.cutoff)},${num(e.pre)},${num(e.overlap)}`
|
|
3144
|
+
).join("\r\n").concat("\r\n");
|
|
3145
|
+
}
|
|
3146
|
+
function encodeOto(entries) {
|
|
3147
|
+
return encodeShiftJis(formatOto(entries));
|
|
3148
|
+
}
|
|
1300
3149
|
export {
|
|
1301
3150
|
KoeEngine,
|
|
1302
3151
|
MIN_WORLDLINE_SAMPLES,
|
|
1303
3152
|
VoiceBank,
|
|
1304
3153
|
WORLDLINE_SAMPLE_RATE,
|
|
1305
3154
|
Worldline,
|
|
3155
|
+
analyze,
|
|
3156
|
+
analyzeWav,
|
|
1306
3157
|
detectF0,
|
|
3158
|
+
detectGrid,
|
|
3159
|
+
encodeOto,
|
|
3160
|
+
encodeShiftJis,
|
|
3161
|
+
estimateSequence,
|
|
3162
|
+
estimateSolo,
|
|
3163
|
+
estimateVowelJoin,
|
|
3164
|
+
formatOto,
|
|
3165
|
+
frqAverageF0InRange,
|
|
1307
3166
|
frqFileName,
|
|
3167
|
+
generateOto,
|
|
3168
|
+
generateOtoForFile,
|
|
1308
3169
|
leadInFromEntry,
|
|
3170
|
+
locateMora,
|
|
1309
3171
|
normalizePcm,
|
|
1310
3172
|
noteNameToHz,
|
|
3173
|
+
otoRegion,
|
|
1311
3174
|
pack,
|
|
1312
3175
|
packKoe,
|
|
3176
|
+
parseFrq,
|
|
1313
3177
|
parseFrqAverageF0,
|
|
1314
3178
|
parseKoeHeader,
|
|
1315
3179
|
parseOto,
|
|
1316
3180
|
parseWav,
|
|
1317
3181
|
pcmBase,
|
|
1318
3182
|
pitchFromAliasSuffix,
|
|
3183
|
+
readWavPcm48k,
|
|
1319
3184
|
resample,
|
|
1320
3185
|
samplesToMs,
|
|
3186
|
+
splitKana,
|
|
3187
|
+
suffixFromFolderName,
|
|
3188
|
+
summarise,
|
|
3189
|
+
toHiragana,
|
|
1321
3190
|
toInt16,
|
|
1322
3191
|
toMono,
|
|
3192
|
+
transcribe,
|
|
1323
3193
|
trimToOto,
|
|
1324
|
-
unzipToFileMap
|
|
3194
|
+
unzipToFileMap,
|
|
3195
|
+
zipFiles
|
|
1325
3196
|
};
|
|
1326
3197
|
//# sourceMappingURL=index.js.map
|