@audio/sinusoidal-synth 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/package.json +30 -0
- package/synth.js +41 -0
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@audio/sinusoidal-synth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Additive resynthesis from partial trajectories — interpolated freq/amp, integrated phase",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "synth.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./synth.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"synth.js"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"audio",
|
|
17
|
+
"dsp",
|
|
18
|
+
"sinusoidal",
|
|
19
|
+
"sms",
|
|
20
|
+
"synth"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Dmitry Iv. <dfcreative@gmail.com>",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/synth.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Additive resynthesis from partial trajectories — per-sample linear interpolation of
|
|
2
|
+
// frequency/amplitude between frame centers, phase integrated for continuity (MQ 1986).
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {{partials, frames, hop, frameSize, fs}} model — from @audio/sinusoidal-track
|
|
6
|
+
* @param {object} opts — { length (samples; default frames·hop + frameSize) }
|
|
7
|
+
* @returns {Float32Array}
|
|
8
|
+
*/
|
|
9
|
+
export default function synth (model, { length } = {}) {
|
|
10
|
+
let { partials, frames, hop, frameSize, fs } = model
|
|
11
|
+
let n = length ?? frames * hop + frameSize
|
|
12
|
+
let out = new Float32Array(n)
|
|
13
|
+
let center = frameSize / 2
|
|
14
|
+
|
|
15
|
+
for (let p of partials) {
|
|
16
|
+
let phase = 0
|
|
17
|
+
let f0 = p.freqs[0]
|
|
18
|
+
let t0 = p.start * hop + center
|
|
19
|
+
// fade in/out one hop at the ends to avoid clicks
|
|
20
|
+
let total = p.freqs.length
|
|
21
|
+
let sampStart = Math.max(0, t0 - hop)
|
|
22
|
+
let sampEnd = Math.min(n, (p.start + total - 1) * hop + center + hop)
|
|
23
|
+
for (let i = sampStart; i < sampEnd; i++) {
|
|
24
|
+
let pos = (i - t0) / hop // fractional frame index within the partial
|
|
25
|
+
let idx = Math.floor(pos)
|
|
26
|
+
let frac = pos - idx
|
|
27
|
+
let f, a
|
|
28
|
+
if (idx < 0) { f = p.freqs[0]; a = p.amps[0] * (1 + pos) }
|
|
29
|
+
else if (idx >= total - 1) {
|
|
30
|
+
f = p.freqs[total - 1]
|
|
31
|
+
a = p.amps[total - 1] * Math.max(0, 1 - (pos - (total - 1)))
|
|
32
|
+
} else {
|
|
33
|
+
f = p.freqs[idx] + (p.freqs[idx + 1] - p.freqs[idx]) * frac
|
|
34
|
+
a = p.amps[idx] + (p.amps[idx + 1] - p.amps[idx]) * frac
|
|
35
|
+
}
|
|
36
|
+
phase += 2 * Math.PI * f / fs
|
|
37
|
+
out[i] += a * Math.sin(phase)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out
|
|
41
|
+
}
|