@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.
Files changed (2) hide show
  1. package/package.json +30 -0
  2. 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
+ }