@audio/shift-pvoc 1.0.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/index.js +39 -0
- package/package.json +40 -0
package/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { makeStftShift } from '@audio/shift-core/stft'
|
|
2
|
+
import { makeFrameRatio, wrapPhase, scatterGated } from '@audio/shift-core'
|
|
3
|
+
|
|
4
|
+
// Canonical phase vocoder pitch shift (Bernsee / SMB method), peak-gated bin scatter.
|
|
5
|
+
// Per frame: compute true instantaneous frequency at each analysis bin eligible under the
|
|
6
|
+
// peak ±1 gate, shift it to the destination bin `ratio` implies, and accumulate synthesis
|
|
7
|
+
// phase there at the shifted frequency (below). Colliding destination bins accumulate in
|
|
8
|
+
// the energy domain and are per-frame renormalized (see scatterGated) — the frequency
|
|
9
|
+
// written to a bin is its loudest contributor's. No time-stretch, no resample.
|
|
10
|
+
function process(mag, phase, state, ctx) {
|
|
11
|
+
if (!state.fr) state.fr = makeFrameRatio(ctx.ratioFn || ctx.ratio || 1)
|
|
12
|
+
let { half, hop } = ctx
|
|
13
|
+
let ratio = state.fr.at(ctx.frameStart, ctx.sampleRate)
|
|
14
|
+
if (!state.prev) {
|
|
15
|
+
state.prev = new Float64Array(half + 1)
|
|
16
|
+
state.syn = new Float64Array(half + 1)
|
|
17
|
+
state.newMag = new Float64Array(half + 1)
|
|
18
|
+
state.newFreq = new Float64Array(half + 1)
|
|
19
|
+
state.peakMag = new Float64Array(half + 1)
|
|
20
|
+
state.first = true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let { prev, syn, newMag, newFreq, peakMag } = state
|
|
24
|
+
newMag.fill(0)
|
|
25
|
+
newFreq.fill(0)
|
|
26
|
+
peakMag.fill(0)
|
|
27
|
+
|
|
28
|
+
scatterGated(mag, phase, state.first ? null : prev, ratio, ctx, newMag, newFreq, peakMag)
|
|
29
|
+
prev.set(phase)
|
|
30
|
+
|
|
31
|
+
for (let k = 0; k <= half; k++) syn[k] = wrapPhase(syn[k] + newFreq[k] * hop)
|
|
32
|
+
|
|
33
|
+
state.first = false
|
|
34
|
+
return { mag: newMag, phase: syn }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Batch and stream share scatterGated's per-frame energy renormalization, so both
|
|
38
|
+
// reconstruct at identical loudness with no whole-signal gain correction needed.
|
|
39
|
+
export default makeStftShift(process, { post: (out) => out })
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@audio/shift-pvoc",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Canonical phase vocoder (Bernsee/SMB) pitch shift",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.js"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@audio/shift-core": "^1.0.0"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "audiojs",
|
|
23
|
+
"homepage": "https://github.com/audiojs/shift/tree/main/packages/shift-pvoc#readme",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/audiojs/shift.git",
|
|
27
|
+
"directory": "packages/shift-pvoc"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"audio",
|
|
31
|
+
"pitch-shift",
|
|
32
|
+
"phase-vocoder",
|
|
33
|
+
"pvoc",
|
|
34
|
+
"stft",
|
|
35
|
+
"dsp"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
}
|
|
40
|
+
}
|