@hraness/dawg 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -0
- package/DAWG.md +655 -141
- package/README.md +27 -25
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +759 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +182 -99
- package/core/expression.ts +158 -6
- package/core/fx.ts +354 -32
- package/core/granular.ts +91 -0
- package/core/instruments.ts +180 -0
- package/core/keys.ts +573 -4
- package/core/loop.ts +5 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/resonators.ts +306 -4
- package/core/routing.ts +165 -0
- package/core/score.ts +1181 -16
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +354 -6
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +3160 -69
- package/core/sections.ts +298 -24
- package/core/sing.ts +815 -0
- package/core/strings.ts +237 -2
- package/core/style-provenance.ts +80 -0
- package/core/styles/africa-mena-southasia.ts +2893 -0
- package/core/styles/americas.ts +3810 -0
- package/core/styles/art.ts +4993 -0
- package/core/styles/base.ts +123 -0
- package/core/styles/cycles.ts +106 -0
- package/core/styles/electronic.ts +2723 -0
- package/core/styles/europe-asia-pacific.ts +2838 -0
- package/core/styles/excerpt.ts +29 -0
- package/core/styles/gamelan.ts +283 -0
- package/core/styles/generate.ts +2199 -0
- package/core/styles/index.ts +515 -0
- package/core/styles/parts.ts +106 -0
- package/core/styles/pop.ts +3189 -0
- package/core/styles/rock.ts +2993 -0
- package/core/styles/roots.ts +4175 -0
- package/core/styles/schema.ts +429 -0
- package/core/styles/taxonomy.ts +940 -0
- package/core/styles/validate.ts +528 -0
- package/core/tempo.ts +32 -2
- package/core/tuning.ts +19 -3
- package/core/vocoder.ts +524 -0
- package/core/winds.ts +652 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +30 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -6
- package/guides/chords.md +16 -14
- package/guides/effects.md +17 -15
- package/guides/faders.md +20 -16
- package/guides/files.md +13 -9
- package/guides/getting-started.md +15 -11
- package/guides/keys.md +19 -15
- package/guides/media.md +17 -12
- package/guides/mix.md +12 -6
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/performance.md +15 -10
- package/guides/play.md +21 -13
- package/guides/project.md +25 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +32 -0
- package/guides/rhythm.md +17 -13
- package/guides/sessions.md +17 -7
- package/guides/show-me.md +31 -0
- package/guides/sound.md +25 -9
- package/guides/sounds.md +15 -12
- package/guides/styles.md +31 -0
- package/guides/tempo.md +15 -10
- package/guides/tracks.md +15 -10
- package/guides/tuning.md +32 -0
- package/guides/voice.md +31 -0
- package/guides/web-search.md +18 -8
- package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
- package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
- package/native/prebuilt/manifest.json +21 -0
- package/package.json +5 -2
- package/src/agent/agent.ts +131 -13
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/chord-tools.ts +153 -1
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +369 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +91 -0
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +15 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +13 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +5 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/resample-tool.ts +131 -0
- package/src/agent/rhythm-tools.ts +1 -1
- package/src/agent/section-tools.ts +1 -1
- package/src/agent/show-me.ts +497 -0
- package/src/agent/steer.ts +15 -0
- package/src/agent/style-tools.ts +217 -0
- package/src/agent/tool-error.ts +12 -0
- package/src/agent/tools.ts +307 -37
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +16 -6
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +58 -3
- package/src/audio/autotune-engine.ts +101 -0
- package/src/audio/autotune.ts +640 -0
- package/src/audio/clips.ts +240 -0
- package/src/audio/doctor.ts +86 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +171 -0
- package/src/audio/dsp/follow.ts +120 -0
- package/src/audio/dsp/formant.ts +427 -0
- package/src/audio/dsp/glottal.ts +243 -0
- package/src/audio/dsp/interp.ts +7 -2
- package/src/audio/dsp/lpc.ts +50 -0
- package/src/audio/dsp/periodicity.ts +59 -0
- package/src/audio/dsp/pitch.ts +995 -0
- package/src/audio/dsp/psola.ts +199 -0
- package/src/audio/dsp/shift.ts +256 -0
- package/src/audio/effects/chain.ts +21 -4
- package/src/audio/effects/common.ts +57 -0
- package/src/audio/effects/convolution.ts +113 -5
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/effects/gaze.ts +354 -0
- package/src/audio/engine.ts +206 -33
- package/src/audio/fit.ts +80 -3
- package/src/audio/granular.ts +352 -21
- package/src/audio/instrument-check.ts +67 -47
- package/src/audio/instruments.ts +36 -3
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +427 -0
- package/src/audio/keys/engine.ts +156 -20
- package/src/audio/keys/organ.ts +1335 -0
- package/src/audio/keys/piano.ts +72 -8
- package/src/audio/keys/sympathetic.ts +127 -0
- package/src/audio/kits.ts +135 -6
- package/src/audio/live-worker.ts +3 -1
- package/src/audio/live.ts +247 -26
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +45 -3
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +286 -0
- package/src/audio/resonators.ts +10 -2
- package/src/audio/sampler.ts +252 -24
- package/src/audio/samples.ts +46 -3
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/body.ts +29 -16
- package/src/audio/strings/bow.ts +699 -0
- package/src/audio/strings/engine.ts +274 -22
- package/src/audio/strings/measure.test-helpers.ts +2 -0
- package/src/audio/synth/oscillators.ts +31 -21
- package/src/audio/synth/voice.ts +34 -1
- package/src/audio/vocoder/bank.ts +314 -0
- package/src/audio/vocoder/carrier.ts +165 -0
- package/src/audio/vocoder/control.ts +68 -0
- package/src/audio/vocoder/detect.ts +50 -0
- package/src/audio/vocoder/index.ts +304 -0
- package/src/audio/vocoder/talkbox.ts +143 -0
- package/src/audio/wav.ts +608 -85
- package/src/audio/winds/engine.ts +306 -0
- package/src/audio/winds/filters.ts +153 -0
- package/src/audio/winds/pitch.ts +104 -0
- package/src/audio/winds/trim.ts +104 -0
- package/src/audio/winds/trims.ts +917 -0
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +492 -0
- package/src/auth/cli.ts +38 -36
- package/src/auth/credentials.ts +30 -1
- package/src/auth/login.ts +15 -9
- package/src/auth/tui.ts +19 -10
- package/src/commands/arrange.ts +44 -29
- package/src/commands/autotune.ts +421 -0
- package/src/commands/calibration.ts +74 -0
- package/src/commands/clips.ts +887 -0
- package/src/commands/drums.ts +3 -2
- package/src/commands/edit.ts +11 -4
- package/src/commands/expression.ts +104 -23
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +105 -33
- package/src/commands/grammar.ts +558 -0
- package/src/commands/granular.ts +7 -2
- package/src/commands/help.ts +652 -339
- package/src/commands/history.ts +18 -0
- package/src/commands/keys.ts +293 -17
- package/src/commands/modal.ts +67 -4
- package/src/commands/music.ts +1 -1
- package/src/commands/nearest.ts +53 -0
- package/src/commands/pack.ts +9 -2
- package/src/commands/param-range.ts +56 -0
- package/src/commands/parses.ts +130 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/resample.ts +281 -0
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +8 -25
- package/src/commands/sample.ts +49 -2
- package/src/commands/shift.ts +119 -0
- package/src/commands/sing.ts +478 -0
- package/src/commands/string.ts +28 -2
- package/src/commands/strum.ts +485 -0
- package/src/commands/style.ts +415 -0
- package/src/commands/time.ts +6 -3
- package/src/commands/tuning.ts +3 -3
- package/src/commands/vocal-pitch.ts +616 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +244 -0
- package/src/fs/durable.ts +50 -0
- package/src/lang/glossary.ts +493 -0
- package/src/launch-args.ts +163 -0
- package/src/main.ts +1521 -255
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +26 -3
- package/src/project/clip-pins.ts +72 -0
- package/src/project/init.ts +23 -8
- package/src/project/sync.ts +418 -86
- package/src/render.ts +36 -1
- package/src/session/daemon.ts +3 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +154 -0
- package/src/session/port.ts +22 -4
- package/src/session/presence.ts +50 -7
- package/src/session/protocol.ts +5 -1
- package/src/session/rebase.ts +18 -5
- package/src/session/receipt.ts +258 -0
- package/src/session/store.ts +65 -43
- package/src/tui/arrange-menu.ts +65 -31
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +18 -13
- package/src/tui/fader.ts +228 -41
- package/src/tui/granular-menu.ts +2 -4
- package/src/tui/menu-clips.ts +297 -0
- package/src/tui/menu-time.ts +20 -13
- package/src/tui/menu-voice.ts +405 -0
- package/src/tui/menu.ts +1377 -193
- package/src/tui/modal-menu.ts +38 -11
- package/src/tui/performance-menu.ts +46 -1
- package/src/tui/play-chords.ts +108 -5
- package/src/tui/play-mode.ts +16 -1
- package/src/tui/play-session.ts +81 -16
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +144 -0
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +274 -40
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +144 -0
- package/tui/drawer.ts +70 -22
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +112 -63
- package/tui/guide.ts +42 -4
- package/tui/highway.ts +269 -25
- package/tui/hints.ts +192 -0
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/play-strip.ts +68 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +144 -14
- package/tui/theme.ts +27 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glottal source: the Liljencrants-Fant (LF) flow-derivative pulse shaped by
|
|
3
|
+
* one voice-quality knob Rd (Fant 1995, "The LF-model revisited"): 0.3 is
|
|
4
|
+
* pressed and bright, 1.0 modal, 2.7 breathy and dark. One period tables are
|
|
5
|
+
* precomputed on an Rd grid and read with linear interpolation in phase and
|
|
6
|
+
* across Rd, so the per-sample cost is two table reads.
|
|
7
|
+
*
|
|
8
|
+
* Fant's Rd relations (all in units of the period T0 = 1):
|
|
9
|
+
* Ra = (-1 + 4.8 Rd) / 100, Rk = (22.4 + 11.8 Rd) / 100,
|
|
10
|
+
* Rg = Rk / (4 (0.11 Rd / (0.5 + 1.2 Rk) - Ra)),
|
|
11
|
+
* tp = 1 / (2 Rg), te = tp (1 + Rk), ta = Ra.
|
|
12
|
+
*/
|
|
13
|
+
import { fftInPlace } from "./fft.ts";
|
|
14
|
+
|
|
15
|
+
export const GLOTTAL_TABLE = 2048;
|
|
16
|
+
export const RD_MIN = 0.3;
|
|
17
|
+
export const RD_MAX = 2.7;
|
|
18
|
+
export const RD_STEP = 0.1;
|
|
19
|
+
const RD_COUNT = Math.round((RD_MAX - RD_MIN) / RD_STEP) + 1;
|
|
20
|
+
|
|
21
|
+
function lfTable(rd: number, n: number): Float64Array {
|
|
22
|
+
const ra = (-1 + 4.8 * rd) / 100;
|
|
23
|
+
const rk = (22.4 + 11.8 * rd) / 100;
|
|
24
|
+
const rg = rk / (4 * ((0.11 * rd) / (0.5 + 1.2 * rk) - ra));
|
|
25
|
+
const tp = 1 / (2 * rg);
|
|
26
|
+
let te = tp * (1 + rk);
|
|
27
|
+
if (te > 0.98) te = 0.98;
|
|
28
|
+
const ta = Math.max(ra, 1e-4);
|
|
29
|
+
const tc = 1;
|
|
30
|
+
// return-phase constant: eps * ta = 1 - exp(-eps (tc - te))
|
|
31
|
+
let eps = 1 / ta;
|
|
32
|
+
for (let i = 0; i < 60; i += 1) eps = (1 - Math.exp(-eps * (tc - te))) / ta;
|
|
33
|
+
const wg = Math.PI / tp;
|
|
34
|
+
const shape = (alpha: number, out: Float64Array): number => {
|
|
35
|
+
const e0 = -1 / (Math.exp(alpha * te) * Math.sin(wg * te));
|
|
36
|
+
let sum = 0;
|
|
37
|
+
for (let i = 0; i < n; i += 1) {
|
|
38
|
+
const t = i / n;
|
|
39
|
+
let v: number;
|
|
40
|
+
if (t <= te) v = e0 * Math.exp(alpha * t) * Math.sin(wg * t);
|
|
41
|
+
else
|
|
42
|
+
v =
|
|
43
|
+
-(1 / (eps * ta)) *
|
|
44
|
+
(Math.exp(-eps * (t - te)) - Math.exp(-eps * (tc - te)));
|
|
45
|
+
out[i] = v;
|
|
46
|
+
sum += v;
|
|
47
|
+
}
|
|
48
|
+
return sum / n;
|
|
49
|
+
};
|
|
50
|
+
// alpha makes the net flow zero (the derivative integrates to 0)
|
|
51
|
+
const out = new Float64Array(n);
|
|
52
|
+
let lo = -50;
|
|
53
|
+
let hi = 200;
|
|
54
|
+
for (let i = 0; i < 80; i += 1) {
|
|
55
|
+
const mid = (lo + hi) / 2;
|
|
56
|
+
if (shape(mid, out) > 0) hi = mid;
|
|
57
|
+
else lo = mid;
|
|
58
|
+
}
|
|
59
|
+
shape((lo + hi) / 2, out);
|
|
60
|
+
// remove residual DC exactly
|
|
61
|
+
let mean = 0;
|
|
62
|
+
for (const v of out) mean += v;
|
|
63
|
+
mean /= n;
|
|
64
|
+
for (let i = 0; i < n; i += 1) out[i] = out[i]! - mean;
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let tables: Float64Array[] | undefined;
|
|
69
|
+
function lfTables(): Float64Array[] {
|
|
70
|
+
if (!tables) {
|
|
71
|
+
tables = [];
|
|
72
|
+
for (let i = 0; i < RD_COUNT; i += 1)
|
|
73
|
+
tables.push(lfTable(RD_MIN + i * RD_STEP, GLOTTAL_TABLE));
|
|
74
|
+
}
|
|
75
|
+
return tables;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Band-limited mip levels (review fix: the single table aliased badly at
|
|
80
|
+
* 22,050 Hz). Each Rd table is truncated by FFT to its first H harmonics,
|
|
81
|
+
* H on a half-octave grid. A voice at f0 reads the two levels below
|
|
82
|
+
* nmax = fs / (2 f0) and crossfades between them by log(nmax), so the
|
|
83
|
+
* output never holds a harmonic above Nyquist and the timbre moves
|
|
84
|
+
* continuously with pitch (no level jumps under vibrato). The build stores
|
|
85
|
+
* Float32 tables of size max(64, 8 H) rounded up to a power of two
|
|
86
|
+
* (about 0.6 MB for 25 Rd x 19 levels).
|
|
87
|
+
*/
|
|
88
|
+
export const MIP_HARMONICS = [
|
|
89
|
+
2, 3, 4, 6, 8, 11, 16, 23, 32, 45, 64, 91, 128, 181, 256, 362, 512, 724, 1023,
|
|
90
|
+
];
|
|
91
|
+
let mips: Float64Array[][] | undefined; // [level][rd]
|
|
92
|
+
function truncate(src: Float64Array, harmonics: number): Float64Array {
|
|
93
|
+
const n = src.length;
|
|
94
|
+
const re = Float64Array.from(src);
|
|
95
|
+
const im = new Float64Array(n);
|
|
96
|
+
fftInPlace(re, im);
|
|
97
|
+
for (let k = 0; k < n; k += 1) {
|
|
98
|
+
const h = k <= n / 2 ? k : n - k;
|
|
99
|
+
if (h > harmonics) {
|
|
100
|
+
re[k] = 0;
|
|
101
|
+
im[k] = 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
fftInPlace(re, im, true);
|
|
105
|
+
for (let i = 0; i < n; i += 1) re[i] = re[i]! / n;
|
|
106
|
+
return re;
|
|
107
|
+
}
|
|
108
|
+
function mipTables(): Float64Array[][] {
|
|
109
|
+
if (!mips) {
|
|
110
|
+
const base = lfTables();
|
|
111
|
+
mips = MIP_HARMONICS.map((h) => base.map((t) => truncate(t, h)));
|
|
112
|
+
}
|
|
113
|
+
return mips;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readLevel(
|
|
117
|
+
level: Float64Array[],
|
|
118
|
+
ri: number,
|
|
119
|
+
rf: number,
|
|
120
|
+
i0: number,
|
|
121
|
+
i1: number,
|
|
122
|
+
pf: number,
|
|
123
|
+
): number {
|
|
124
|
+
const a = level[ri]!;
|
|
125
|
+
const b = level[ri + 1]!;
|
|
126
|
+
const va = a[i0]! + (a[i1]! - a[i0]!) * pf;
|
|
127
|
+
const vb = b[i0]! + (b[i1]! - b[i0]!) * pf;
|
|
128
|
+
return va + (vb - va) * rf;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* LF flow derivative at `phase` in [0,1) for voice quality `rd`; peak
|
|
133
|
+
* excitation -1. `inc` is f0 / sampleRate: when given, the read is
|
|
134
|
+
* band-limited (mip levels); without it, the full table (aliases; kept
|
|
135
|
+
* only to measure the difference).
|
|
136
|
+
*/
|
|
137
|
+
// The mip level choice depends only on `inc`, which changes once per period
|
|
138
|
+
// or control step: memoised so the per-sample cost is the table reads.
|
|
139
|
+
let lastInc = Number.NaN;
|
|
140
|
+
let lastLevel = 0;
|
|
141
|
+
let lastBlend = 0;
|
|
142
|
+
function chooseLevel(inc: number): void {
|
|
143
|
+
lastInc = inc;
|
|
144
|
+
const nmax = 0.5 / Math.max(inc, 1e-9);
|
|
145
|
+
let j = 0;
|
|
146
|
+
while (j + 1 < MIP_HARMONICS.length && MIP_HARMONICS[j + 1]! <= nmax) j += 1;
|
|
147
|
+
if (j === 0 || MIP_HARMONICS[j]! > nmax) {
|
|
148
|
+
lastLevel = 0;
|
|
149
|
+
lastBlend = -1;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
// blend level j-1 (weight 1-f) and level j (weight f): both are below nmax
|
|
153
|
+
const top =
|
|
154
|
+
j + 1 < MIP_HARMONICS.length
|
|
155
|
+
? MIP_HARMONICS[j + 1]!
|
|
156
|
+
: MIP_HARMONICS[j]! * Math.SQRT2;
|
|
157
|
+
lastLevel = j;
|
|
158
|
+
lastBlend = Math.min(
|
|
159
|
+
1,
|
|
160
|
+
Math.log(nmax / MIP_HARMONICS[j]!) / Math.log(top / MIP_HARMONICS[j]!),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Builds the LF tables and their mip levels now (about 50 ms) instead of on
|
|
166
|
+
* the first sounding sample, so a live key never pays for them. Idempotent.
|
|
167
|
+
*/
|
|
168
|
+
export function warmGlottal(): void {
|
|
169
|
+
mipTables();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* LF flow derivative at `phase` in [0,1) for voice quality `rd`; peak
|
|
174
|
+
* excitation -1. `inc` is f0 / sampleRate: when given, the read is
|
|
175
|
+
* band-limited (mip levels); without it, the full table (aliases; kept
|
|
176
|
+
* only to measure the difference).
|
|
177
|
+
*/
|
|
178
|
+
export function glottal(phase: number, rd: number, inc?: number): number {
|
|
179
|
+
const r = (Math.min(RD_MAX, Math.max(RD_MIN, rd)) - RD_MIN) / RD_STEP;
|
|
180
|
+
const ri = Math.min(RD_COUNT - 2, Math.floor(r));
|
|
181
|
+
const rf = r - ri;
|
|
182
|
+
const p = phase * GLOTTAL_TABLE;
|
|
183
|
+
const pi = Math.floor(p);
|
|
184
|
+
const pf = p - pi;
|
|
185
|
+
const i0 = pi % GLOTTAL_TABLE;
|
|
186
|
+
const i1 = (pi + 1) % GLOTTAL_TABLE;
|
|
187
|
+
if (inc === undefined) return readLevel(lfTables(), ri, rf, i0, i1, pf);
|
|
188
|
+
const m = mipTables();
|
|
189
|
+
if (inc !== lastInc) chooseLevel(inc);
|
|
190
|
+
const j = lastLevel;
|
|
191
|
+
if (lastBlend < 0) return readLevel(m[0]!, ri, rf, i0, i1, pf);
|
|
192
|
+
const lo = readLevel(m[j - 1]!, ri, rf, i0, i1, pf);
|
|
193
|
+
const hi = readLevel(m[j]!, ri, rf, i0, i1, pf);
|
|
194
|
+
return lo + (hi - lo) * lastBlend;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Open-phase weight (1 while the glottis is open) for breath-noise gating. */
|
|
198
|
+
let owRd = Number.NaN;
|
|
199
|
+
let owTe = 0;
|
|
200
|
+
export function openWeight(phase: number, rd: number): number {
|
|
201
|
+
if (rd !== owRd) {
|
|
202
|
+
const rk = (22.4 + 11.8 * rd) / 100;
|
|
203
|
+
const ra = (-1 + 4.8 * rd) / 100;
|
|
204
|
+
const rg = rk / (4 * ((0.11 * rd) / (0.5 + 1.2 * rk) - ra));
|
|
205
|
+
owRd = rd;
|
|
206
|
+
owTe = Math.min(0.98, (1 / (2 * rg)) * (1 + rk));
|
|
207
|
+
}
|
|
208
|
+
const te = owTe;
|
|
209
|
+
return phase < te ? 0.5 - 0.5 * Math.cos((2 * Math.PI * phase) / te) : 0.15;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const spectra = new Map<number, Float64Array>();
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Power of harmonics 1..`count` of the LF flow derivative at `rd`
|
|
216
|
+
* (rounded to the table's 0.1 grid), from a DFT of one period. Cached:
|
|
217
|
+
* the sing engine's level normalization reads it at control rate.
|
|
218
|
+
*/
|
|
219
|
+
export function glottalSpectrum(rd: number, count: number): Float64Array {
|
|
220
|
+
const index = Math.round(
|
|
221
|
+
(Math.min(RD_MAX, Math.max(RD_MIN, rd)) - RD_MIN) / RD_STEP,
|
|
222
|
+
);
|
|
223
|
+
const key = index * 1024 + count;
|
|
224
|
+
const hit = spectra.get(key);
|
|
225
|
+
if (hit) return hit;
|
|
226
|
+
const at = RD_MIN + index * RD_STEP;
|
|
227
|
+
const n = 1024;
|
|
228
|
+
const x = new Float64Array(n);
|
|
229
|
+
for (let i = 0; i < n; i += 1) x[i] = glottal(i / n, at);
|
|
230
|
+
const out = new Float64Array(count + 1);
|
|
231
|
+
for (let h = 1; h <= count; h += 1) {
|
|
232
|
+
let re = 0;
|
|
233
|
+
let im = 0;
|
|
234
|
+
for (let i = 0; i < n; i += 1) {
|
|
235
|
+
const a = (2 * Math.PI * h * i) / n;
|
|
236
|
+
re += x[i]! * Math.cos(a);
|
|
237
|
+
im -= x[i]! * Math.sin(a);
|
|
238
|
+
}
|
|
239
|
+
out[h] = (re * re + im * im) / (n * n);
|
|
240
|
+
}
|
|
241
|
+
spectra.set(key, out);
|
|
242
|
+
return out;
|
|
243
|
+
}
|
package/src/audio/dsp/interp.ts
CHANGED
|
@@ -51,11 +51,16 @@ export function allpassDelay(a: number, w: number): number {
|
|
|
51
51
|
export function thiranFor(d: number, w = 0): number {
|
|
52
52
|
let a = (1 - d) / (1 + d);
|
|
53
53
|
if (w < 1e-9) return a;
|
|
54
|
+
const s = Math.sin(w);
|
|
55
|
+
const c = Math.cos(w);
|
|
54
56
|
for (let i = 0; i < 4; i += 1) {
|
|
55
57
|
const f = allpassDelay(a, w) - d;
|
|
56
58
|
if (Math.abs(f) < 1e-10) break;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
// d(delay)/da in closed form: the phase is atan2(-s, a + c) -
|
|
60
|
+
// atan2(-a s, 1 + a c), so no extra atan2 pair per step.
|
|
61
|
+
const p = a + c;
|
|
62
|
+
const q = 1 + a * c;
|
|
63
|
+
const df = -(s / (p * p + s * s) + s / (q * q + a * a * s * s)) / w;
|
|
59
64
|
if (df === 0) break;
|
|
60
65
|
a = clamp(a - f / df, -0.9, 0.9);
|
|
61
66
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear prediction (0.7 talkbox): autocorrelation with a deterministic
|
|
3
|
+
* white-noise floor and a 60 Hz Gaussian lag window, and Levinson-Durbin.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Autocorrelation r[0..order] of `x` into `r`. */
|
|
7
|
+
export function autocorrelate(
|
|
8
|
+
x: Float64Array,
|
|
9
|
+
order: number,
|
|
10
|
+
r: Float64Array,
|
|
11
|
+
sampleRate: number,
|
|
12
|
+
): Float64Array {
|
|
13
|
+
for (let lag = 0; lag <= order; lag += 1) {
|
|
14
|
+
let s = 0;
|
|
15
|
+
for (let i = lag; i < x.length; i += 1) s += x[i]! * x[i - lag]!;
|
|
16
|
+
r[lag] = s;
|
|
17
|
+
}
|
|
18
|
+
r[0] = r[0]! * (1 + 1e-6) + 1e-12;
|
|
19
|
+
for (let lag = 1; lag <= order; lag += 1)
|
|
20
|
+
r[lag] =
|
|
21
|
+
r[lag]! * Math.exp(-0.5 * ((2 * Math.PI * 60 * lag) / sampleRate) ** 2);
|
|
22
|
+
return r;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Levinson-Durbin: predictor `a` (a[0] = 1) from autocorrelation `r`.
|
|
27
|
+
* Returns the prediction error (0 when the recursion goes unstable).
|
|
28
|
+
*/
|
|
29
|
+
export function levinson(
|
|
30
|
+
r: Float64Array,
|
|
31
|
+
order: number,
|
|
32
|
+
a: Float64Array,
|
|
33
|
+
): number {
|
|
34
|
+
a.fill(0);
|
|
35
|
+
a[0] = 1;
|
|
36
|
+
let err = r[0]!;
|
|
37
|
+
if (err <= 0) return 0;
|
|
38
|
+
const tmp = new Float64Array(order + 1);
|
|
39
|
+
for (let i = 1; i <= order; i += 1) {
|
|
40
|
+
let acc = r[i]!;
|
|
41
|
+
for (let j = 1; j < i; j += 1) acc += a[j]! * r[i - j]!;
|
|
42
|
+
const k = -acc / err;
|
|
43
|
+
tmp.set(a);
|
|
44
|
+
for (let j = 1; j < i; j += 1) a[j] = tmp[j]! + k * tmp[i - j]!;
|
|
45
|
+
a[i] = k;
|
|
46
|
+
err *= 1 - k * k;
|
|
47
|
+
if (err <= 0) return 0;
|
|
48
|
+
}
|
|
49
|
+
return err;
|
|
50
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodicity (0.7 vocoder): 0..1 per sample, the best normalised
|
|
3
|
+
* autocorrelation over 2..20 ms lags (a YIN-style aperiodicity cue), on a
|
|
4
|
+
* ~8 kHz block-averaged copy, 30 ms frames every 5 ms. The frame grid sits on
|
|
5
|
+
* the absolute song sample (`origin` + index), so a render window and the
|
|
6
|
+
* full render agree after pre-roll.
|
|
7
|
+
*/
|
|
8
|
+
const posMod = (a: number, m: number): number => ((a % m) + m) % m;
|
|
9
|
+
|
|
10
|
+
export function periodicityCurve(
|
|
11
|
+
x: Float64Array,
|
|
12
|
+
sampleRate: number,
|
|
13
|
+
origin = 0,
|
|
14
|
+
): Float64Array {
|
|
15
|
+
const dec = Math.max(1, Math.round(sampleRate / 8000));
|
|
16
|
+
const dsr = sampleRate / dec;
|
|
17
|
+
const hop = Math.round(0.005 * sampleRate);
|
|
18
|
+
const win = Math.round(0.03 * dsr);
|
|
19
|
+
const lagLo = Math.round(dsr / 500);
|
|
20
|
+
const lagHi = Math.round(dsr / 50);
|
|
21
|
+
const out = new Float64Array(x.length);
|
|
22
|
+
const buf = new Float64Array(win + lagHi);
|
|
23
|
+
for (let s = -posMod(origin, hop); s < x.length; s += hop) {
|
|
24
|
+
for (let i = 0; i < buf.length; i += 1) {
|
|
25
|
+
let acc = 0;
|
|
26
|
+
for (let d = 0; d < dec; d += 1) {
|
|
27
|
+
const j = s + i * dec + d;
|
|
28
|
+
acc += j >= 0 && j < x.length ? x[j]! : 0;
|
|
29
|
+
}
|
|
30
|
+
buf[i] = acc / dec;
|
|
31
|
+
}
|
|
32
|
+
let e0 = 0;
|
|
33
|
+
for (let i = 0; i < win; i += 1) e0 += buf[i]! * buf[i]!;
|
|
34
|
+
let best = 0;
|
|
35
|
+
if (e0 > 1e-12)
|
|
36
|
+
for (let lag = lagLo; lag <= lagHi; lag += 1) {
|
|
37
|
+
let c = 0;
|
|
38
|
+
let el = 0;
|
|
39
|
+
for (let i = 0; i < win; i += 1) {
|
|
40
|
+
c += buf[i]! * buf[i + lag]!;
|
|
41
|
+
el += buf[i + lag]! * buf[i + lag]!;
|
|
42
|
+
}
|
|
43
|
+
const r = c / Math.sqrt(e0 * el + 1e-20);
|
|
44
|
+
if (r > best) best = r;
|
|
45
|
+
}
|
|
46
|
+
// The value labels the frame's middle hop.
|
|
47
|
+
const a = s + Math.round((win * dec) / 2) - (hop >> 1);
|
|
48
|
+
for (let i = Math.max(0, a); i < Math.min(x.length, a + hop); i += 1)
|
|
49
|
+
out[i] = best;
|
|
50
|
+
}
|
|
51
|
+
let first = 0;
|
|
52
|
+
for (let i = 0; i < out.length; i += 1)
|
|
53
|
+
if (out[i] !== 0) {
|
|
54
|
+
first = out[i]!;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
for (let i = 0; i < out.length && out[i] === 0; i += 1) out[i] = first;
|
|
58
|
+
return out;
|
|
59
|
+
}
|