@hraness/dawg 0.6.1 → 0.8.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 +118 -0
- package/DAWG.md +454 -140
- package/README.md +30 -26
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +271 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +184 -104
- package/core/expression.ts +15 -0
- package/core/fx.ts +99 -29
- package/core/ids.ts +52 -0
- package/core/instruments.ts +19 -0
- package/core/keys.ts +3 -3
- package/core/loop.ts +8 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/range.ts +581 -0
- package/core/resonators.ts +16 -2
- package/core/routing.ts +165 -0
- package/core/score.ts +955 -14
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +287 -5
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +2132 -46
- package/core/sections.ts +480 -35
- package/core/sing.ts +815 -0
- 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/guides/agent.md +29 -0
- package/guides/arrange.md +31 -0
- package/guides/audio.md +31 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -7
- package/guides/chords.md +15 -15
- package/guides/effects.md +17 -16
- 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 +15 -7
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/panes.md +32 -0
- package/guides/performance.md +15 -13
- package/guides/play.md +21 -13
- package/guides/project.md +26 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +15 -9
- package/guides/rhythm.md +18 -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 -13
- package/guides/styles.md +31 -0
- package/guides/tape.md +32 -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 +6 -2
- package/src/agent/agent.ts +130 -18
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +378 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +1 -1
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +27 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +25 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +4 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/range-tools.ts +216 -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 +110 -23
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +13 -10
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +49 -5
- package/src/audio/audio-command.ts +218 -0
- 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/devices.ts +264 -0
- package/src/audio/doctor.ts +157 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +10 -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/effects/chain.ts +3 -1
- package/src/audio/effects/common.ts +43 -0
- package/src/audio/effects/convolution.ts +7 -4
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/engine.ts +360 -40
- package/src/audio/fit.ts +35 -3
- package/src/audio/instrument-check.ts +59 -43
- package/src/audio/instruments.ts +4 -0
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +8 -1
- package/src/audio/keys/engine.ts +13 -1
- package/src/audio/keys/piano.ts +22 -2
- package/src/audio/kits.ts +135 -6
- package/src/audio/live.ts +114 -20
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +30 -2
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +2 -1
- package/src/audio/sampler.ts +85 -4
- package/src/audio/samples.ts +20 -2
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/bow.ts +48 -5
- package/src/audio/strings/engine.ts +8 -2
- 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 +500 -77
- package/src/audio/winds/engine.ts +5 -1
- package/src/audio/winds/trim.ts +28 -4
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +15 -2
- 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 +82 -32
- 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 +1 -1
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +101 -32
- package/src/commands/grammar.ts +560 -0
- package/src/commands/help.ts +774 -366
- package/src/commands/history.ts +139 -10
- package/src/commands/keys.ts +25 -8
- package/src/commands/modal.ts +1 -1
- 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 +135 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/range.ts +763 -0
- package/src/commands/resample.ts +13 -10
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +3 -24
- package/src/commands/sample.ts +11 -1
- package/src/commands/sing.ts +474 -0
- package/src/commands/strum.ts +13 -1
- 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 +617 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +2 -2
- package/src/fs/durable.ts +50 -0
- package/src/identity/actor.ts +127 -0
- package/src/lang/glossary.ts +511 -0
- package/src/launch-args.ts +267 -0
- package/src/main.ts +2508 -308
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +21 -2
- 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 +20 -1
- package/src/session/client.ts +107 -3
- package/src/session/clipboard.ts +79 -0
- package/src/session/daemon.ts +199 -5
- package/src/session/live-host.ts +232 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +174 -0
- package/src/session/port.ts +109 -14
- package/src/session/presence.ts +34 -4
- package/src/session/protocol.ts +365 -7
- package/src/session/rebase.ts +63 -12
- package/src/session/receipt.ts +265 -0
- package/src/session/shared-live.ts +131 -0
- package/src/session/store.ts +126 -45
- package/src/tui/arrange-menu.ts +225 -23
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +113 -27
- package/src/tui/fader.ts +282 -42
- package/src/tui/granular-menu.ts +2 -4
- package/src/tui/knob-fields.ts +107 -0
- package/src/tui/knob-map.ts +198 -0
- 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 +987 -179
- package/src/tui/modal-menu.ts +6 -6
- package/src/tui/performance-menu.ts +5 -2
- package/src/tui/play-chords.ts +4 -2
- package/src/tui/play-mode.ts +15 -1
- package/src/tui/play-session.ts +243 -28
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/tape-mode.ts +580 -0
- package/src/tui/tape-view.ts +263 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +3 -3
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +491 -84
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +193 -0
- package/tui/drawer.ts +233 -27
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +143 -65
- package/tui/guide.ts +50 -5
- package/tui/highway.ts +309 -30
- package/tui/hints.ts +197 -0
- package/tui/hits.ts +7 -1
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/knobs.ts +268 -0
- package/tui/play-strip.ts +80 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +151 -18
- package/tui/tape.ts +439 -0
- package/tui/text.ts +35 -2
- package/tui/theme.ts +46 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formant shift at constant pitch (0.7 `fx.formant`): the throat or gender
|
|
3
|
+
* knob. Per STFT frame the cepstral spectral envelope E is taken and each
|
|
4
|
+
* bin is scaled by E(k / a) / E(k), a = 2^(shift / 12), with its phase left
|
|
5
|
+
* untouched, then the frames overlap-add. The pitch never moves, so no
|
|
6
|
+
* phase vocoder is needed (Röbel and Rodet, "Efficient spectral envelope
|
|
7
|
+
* estimation and its application to pitch shifting and envelope
|
|
8
|
+
* preservation", DAFx 2005; Little AlterBoy and VocalSynth's formant knob).
|
|
9
|
+
*
|
|
10
|
+
* The lifter adapts to the voice: 0.75 periods of a 5-frame median
|
|
11
|
+
* autocorrelation f0, clamped to 1-2 ms, 1 ms when unvoiced. A fixed 2 ms
|
|
12
|
+
* lifter resolves single harmonics above about 500 Hz and makes high
|
|
13
|
+
* voices worse than no processing (design formant.md section 2).
|
|
14
|
+
*
|
|
15
|
+
* Frames sit at multiples of the hop in absolute track samples (an
|
|
16
|
+
* arranged window passes its origin as `seedSeconds`), and a window padded
|
|
17
|
+
* back by `FORMANT_PAD` samples and forward by one frame equals the same
|
|
18
|
+
* span of a full render bit for bit.
|
|
19
|
+
*/
|
|
20
|
+
import type { FxValues } from "../../../core/fx.ts";
|
|
21
|
+
import type { Track } from "../../../core/score.ts";
|
|
22
|
+
import { cepstralEnvelope, envelopeScratch } from "../dsp/envelope.ts";
|
|
23
|
+
import { fftInPlace } from "../dsp/fft.ts";
|
|
24
|
+
import { hann, HANN_SQUARED_OLA_QUARTER } from "../dsp/stft.ts";
|
|
25
|
+
import { fxReader, type EffectContext } from "./common.ts";
|
|
26
|
+
|
|
27
|
+
/** Per-bin gain clamp (24 dB), as shift.ts holds formants. */
|
|
28
|
+
const MAX_GAIN = 10 ** (24 / 20);
|
|
29
|
+
/** Lifter periods and clamps (design formant.md section 2). */
|
|
30
|
+
const LIFTER_PERIODS = 0.75;
|
|
31
|
+
const LIFTER_MIN_SECONDS = 0.001;
|
|
32
|
+
const LIFTER_MAX_SECONDS = 0.002;
|
|
33
|
+
/** Frames in the f0 median (this one and the four before it). */
|
|
34
|
+
const F0_MEDIAN = 5;
|
|
35
|
+
const F0_MIN_HZ = 60;
|
|
36
|
+
const F0_MAX_HZ = 1500;
|
|
37
|
+
/** Below this normalized autocorrelation a frame counts as unvoiced. */
|
|
38
|
+
const VOICED = 0.45;
|
|
39
|
+
/** The first peak within this fraction of the best is the period. */
|
|
40
|
+
const FIRST_PEAK = 0.85;
|
|
41
|
+
|
|
42
|
+
/** Frame length: 1024 at or below 24 kHz, 2048 above. */
|
|
43
|
+
export function formantFrame(sampleRate: number): number {
|
|
44
|
+
return sampleRate > 24_000 ? 2048 : 1024;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Samples a window render must start before its first output sample (4
|
|
49
|
+
* hops for the f0 median plus one frame), and it renders one frame past
|
|
50
|
+
* its last.
|
|
51
|
+
*/
|
|
52
|
+
export function formantPad(sampleRate: number): number {
|
|
53
|
+
const n = formantFrame(sampleRate);
|
|
54
|
+
return n + 4 * (n / 4);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type FormantShiftOptions = Readonly<{
|
|
58
|
+
/** Wet amount 0..1 (1: fully shifted). */
|
|
59
|
+
mix?: number | ((index: number) => number);
|
|
60
|
+
/** Absolute track sample of `x[0]`; frames anchor at hop multiples of it. */
|
|
61
|
+
origin?: number;
|
|
62
|
+
}>;
|
|
63
|
+
|
|
64
|
+
type Analysis = {
|
|
65
|
+
n: number;
|
|
66
|
+
hop: number;
|
|
67
|
+
win: Float64Array;
|
|
68
|
+
wac: Float64Array;
|
|
69
|
+
minLag: number;
|
|
70
|
+
maxLag: number;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const analyses = new Map<number, Analysis>();
|
|
74
|
+
|
|
75
|
+
function analysisFor(sampleRate: number): Analysis {
|
|
76
|
+
let a = analyses.get(sampleRate);
|
|
77
|
+
if (a) return a;
|
|
78
|
+
const n = formantFrame(sampleRate);
|
|
79
|
+
const win = hann(n);
|
|
80
|
+
const minLag = Math.floor(sampleRate / F0_MAX_HZ);
|
|
81
|
+
const maxLag = Math.min(n / 2 - 1, Math.ceil(sampleRate / F0_MIN_HZ));
|
|
82
|
+
// The window's own autocorrelation, divided out so long lags are not
|
|
83
|
+
// biased down (Boersma 1993).
|
|
84
|
+
const wac = new Float64Array(maxLag + 1);
|
|
85
|
+
for (let t = 0; t <= maxLag; t += 1) {
|
|
86
|
+
let sum = 0;
|
|
87
|
+
for (let i = 0; i + t < n; i += 1) sum += win[i]! * win[i + t]!;
|
|
88
|
+
wac[t] = sum;
|
|
89
|
+
}
|
|
90
|
+
a = { n, hop: n / 4, win, wac, minLag, maxLag };
|
|
91
|
+
analyses.set(sampleRate, a);
|
|
92
|
+
return a;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** One frame's f0 from its power spectrum (0: unvoiced). */
|
|
96
|
+
function frameF0(
|
|
97
|
+
mag: Float64Array,
|
|
98
|
+
a: Analysis,
|
|
99
|
+
sampleRate: number,
|
|
100
|
+
re: Float64Array,
|
|
101
|
+
im: Float64Array,
|
|
102
|
+
): number {
|
|
103
|
+
const { n, wac, minLag, maxLag } = a;
|
|
104
|
+
const half = n / 2;
|
|
105
|
+
for (let k = 0; k <= half; k += 1) {
|
|
106
|
+
re[k] = mag[k]! * mag[k]!;
|
|
107
|
+
im[k] = 0;
|
|
108
|
+
if (k > 0 && k < half) {
|
|
109
|
+
re[n - k] = re[k]!;
|
|
110
|
+
im[n - k] = 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
fftInPlace(re, im, true);
|
|
114
|
+
const r0 = re[0]!;
|
|
115
|
+
if (r0 <= 1e-12) return 0;
|
|
116
|
+
const w0 = wac[0]! / r0;
|
|
117
|
+
for (let t = minLag - 1; t <= maxLag; t += 1) re[t] = (re[t]! * w0) / wac[t]!;
|
|
118
|
+
let best = 0;
|
|
119
|
+
for (let t = minLag; t <= maxLag; t += 1) best = Math.max(best, re[t]!);
|
|
120
|
+
if (best < VOICED) return 0;
|
|
121
|
+
for (let t = minLag + 1; t < maxLag; t += 1) {
|
|
122
|
+
const v = re[t]!;
|
|
123
|
+
if (v >= FIRST_PEAK * best && v >= re[t - 1]! && v >= re[t + 1]!) {
|
|
124
|
+
const l = re[t - 1]!;
|
|
125
|
+
const r = re[t + 1]!;
|
|
126
|
+
const d = l - 2 * v + r;
|
|
127
|
+
const offset = d < 0 ? (0.5 * (l - r)) / d : 0;
|
|
128
|
+
return sampleRate / (t + offset);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Shift the formants of `x` by `shift` semitones (a number, or a function
|
|
136
|
+
* of the buffer index read once per hop) at constant pitch. Returns a new
|
|
137
|
+
* buffer; `shift` 0 everywhere returns a copy of `x`.
|
|
138
|
+
*/
|
|
139
|
+
export function formantShift(
|
|
140
|
+
x: Float64Array,
|
|
141
|
+
sampleRate: number,
|
|
142
|
+
shift: number | ((index: number) => number),
|
|
143
|
+
options: FormantShiftOptions = {},
|
|
144
|
+
): Float64Array {
|
|
145
|
+
if (shift === 0) return Float64Array.from(x);
|
|
146
|
+
const a = analysisFor(sampleRate);
|
|
147
|
+
const { n, hop, win } = a;
|
|
148
|
+
const half = n / 2;
|
|
149
|
+
const origin = Math.round(options.origin ?? 0);
|
|
150
|
+
const out = new Float64Array(x.length);
|
|
151
|
+
const re = new Float64Array(n);
|
|
152
|
+
const im = new Float64Array(n);
|
|
153
|
+
const acRe = new Float64Array(n);
|
|
154
|
+
const acIm = new Float64Array(n);
|
|
155
|
+
const mag = new Float64Array(half + 1);
|
|
156
|
+
const env = new Float64Array(half + 1);
|
|
157
|
+
const scratch = envelopeScratch(n);
|
|
158
|
+
const history = new Float64Array(F0_MEDIAN);
|
|
159
|
+
const sorted = new Float64Array(F0_MEDIAN);
|
|
160
|
+
let frames = 0;
|
|
161
|
+
const ola = 1 / HANN_SQUARED_OLA_QUARTER;
|
|
162
|
+
const auto = typeof shift === "function";
|
|
163
|
+
// First frame: the latest hop multiple (absolute) whose frame still
|
|
164
|
+
// reaches x[0].
|
|
165
|
+
const firstAbs = Math.floor((origin - n + 1 + (hop - 1)) / hop) * hop;
|
|
166
|
+
for (let start = firstAbs - origin; start < x.length; start += hop) {
|
|
167
|
+
const st = auto ? shift(Math.max(0, start + half)) : shift;
|
|
168
|
+
for (let i = 0; i < n; i += 1) {
|
|
169
|
+
const j = start + i;
|
|
170
|
+
re[i] = j >= 0 && j < x.length ? x[j]! * win[i]! : 0;
|
|
171
|
+
im[i] = 0;
|
|
172
|
+
}
|
|
173
|
+
fftInPlace(re, im);
|
|
174
|
+
for (let k = 0; k <= half; k += 1) mag[k] = Math.hypot(re[k]!, im[k]!);
|
|
175
|
+
// Every frame joins the f0 history, so a window and the full render
|
|
176
|
+
// agree whatever the shift was.
|
|
177
|
+
history[frames % F0_MEDIAN] = frameF0(mag, a, sampleRate, acRe, acIm);
|
|
178
|
+
frames += 1;
|
|
179
|
+
if (st === 0) {
|
|
180
|
+
fftInPlace(re, im, true);
|
|
181
|
+
} else {
|
|
182
|
+
const m = Math.min(frames, F0_MEDIAN);
|
|
183
|
+
for (let i = 0; i < m; i += 1) sorted[i] = history[i]!;
|
|
184
|
+
for (let i = 1; i < m; i += 1)
|
|
185
|
+
for (let j = i; j > 0 && sorted[j - 1]! > sorted[j]!; j -= 1) {
|
|
186
|
+
const tmp = sorted[j]!;
|
|
187
|
+
sorted[j] = sorted[j - 1]!;
|
|
188
|
+
sorted[j - 1] = tmp;
|
|
189
|
+
}
|
|
190
|
+
const f0 = sorted[m >> 1]!;
|
|
191
|
+
const seconds =
|
|
192
|
+
f0 > 0
|
|
193
|
+
? Math.min(
|
|
194
|
+
LIFTER_MAX_SECONDS,
|
|
195
|
+
Math.max(LIFTER_MIN_SECONDS, LIFTER_PERIODS / f0),
|
|
196
|
+
)
|
|
197
|
+
: LIFTER_MIN_SECONDS;
|
|
198
|
+
const lifter = Math.max(4, Math.round(seconds * sampleRate));
|
|
199
|
+
cepstralEnvelope(mag, n, lifter, env, scratch);
|
|
200
|
+
const ratio = 2 ** (-st / 12);
|
|
201
|
+
for (let k = 0; k <= half; k += 1) {
|
|
202
|
+
const source = k * ratio;
|
|
203
|
+
const i0 = Math.floor(source);
|
|
204
|
+
const f = source - i0;
|
|
205
|
+
const e =
|
|
206
|
+
i0 >= half
|
|
207
|
+
? env[half]!
|
|
208
|
+
: env[i0]! + (env[Math.min(half, i0 + 1)]! - env[i0]!) * f;
|
|
209
|
+
const g = Math.min(
|
|
210
|
+
MAX_GAIN,
|
|
211
|
+
Math.max(1 / MAX_GAIN, e / Math.max(env[k]!, 1e-12)),
|
|
212
|
+
);
|
|
213
|
+
re[k] = re[k]! * g;
|
|
214
|
+
im[k] = im[k]! * g;
|
|
215
|
+
if (k > 0 && k < half) {
|
|
216
|
+
re[n - k] = re[k]!;
|
|
217
|
+
im[n - k] = -im[k]!;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
fftInPlace(re, im, true);
|
|
221
|
+
}
|
|
222
|
+
for (let i = 0; i < n; i += 1) {
|
|
223
|
+
const j = start + i;
|
|
224
|
+
if (j >= 0 && j < x.length)
|
|
225
|
+
out[j] = out[j]! + (re[i]! / n) * win[i]! * ola;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const mix = options.mix ?? 1;
|
|
229
|
+
if (typeof mix === "function")
|
|
230
|
+
for (let i = 0; i < x.length; i += 1)
|
|
231
|
+
out[i] = x[i]! + (out[i]! - x[i]!) * mix(i);
|
|
232
|
+
else if (mix < 1)
|
|
233
|
+
for (let i = 0; i < x.length; i += 1)
|
|
234
|
+
out[i] = x[i]! + (out[i]! - x[i]!) * mix;
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** `fx.formant`: formant shift with mix, both automatable. */
|
|
239
|
+
export function applyFormant(
|
|
240
|
+
buffer: Float64Array,
|
|
241
|
+
track: Track,
|
|
242
|
+
values: FxValues,
|
|
243
|
+
context: EffectContext,
|
|
244
|
+
): void {
|
|
245
|
+
const read = fxReader(track, "formant", values, context);
|
|
246
|
+
const shift = read.param("shift");
|
|
247
|
+
const mix = read.param("mix");
|
|
248
|
+
if (!shift.automated && shift.fallback === 0) return;
|
|
249
|
+
if (!mix.automated && mix.fallback <= 0) return;
|
|
250
|
+
const origin = context.seedSeconds
|
|
251
|
+
? Math.round(context.seedSeconds * context.sampleRate)
|
|
252
|
+
: 0;
|
|
253
|
+
const out = formantShift(
|
|
254
|
+
buffer,
|
|
255
|
+
context.sampleRate,
|
|
256
|
+
shift.automated ? (index) => shift.at(index) : shift.fallback,
|
|
257
|
+
{
|
|
258
|
+
origin,
|
|
259
|
+
mix: mix.automated ? (index) => mix.at(index) : mix.fallback,
|
|
260
|
+
},
|
|
261
|
+
);
|
|
262
|
+
buffer.set(out);
|
|
263
|
+
}
|