cantor-digitalis 0.0.1

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 (35) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +164 -0
  3. package/dist/index.d.ts +35 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +1486 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/nodes/anti-resonance.d.ts +58 -0
  8. package/dist/nodes/anti-resonance.d.ts.map +1 -0
  9. package/dist/nodes/formant-bank.d.ts +76 -0
  10. package/dist/nodes/formant-bank.d.ts.map +1 -0
  11. package/dist/nodes/formant-resonator.d.ts +67 -0
  12. package/dist/nodes/formant-resonator.d.ts.map +1 -0
  13. package/dist/nodes/gain.d.ts +16 -0
  14. package/dist/nodes/gain.d.ts.map +1 -0
  15. package/dist/nodes/glottal-flow-derivative.d.ts +119 -0
  16. package/dist/nodes/glottal-flow-derivative.d.ts.map +1 -0
  17. package/dist/nodes/glottal-formant.d.ts +64 -0
  18. package/dist/nodes/glottal-formant.d.ts.map +1 -0
  19. package/dist/nodes/noise-source.d.ts +51 -0
  20. package/dist/nodes/noise-source.d.ts.map +1 -0
  21. package/dist/nodes/pulse-train.d.ts +51 -0
  22. package/dist/nodes/pulse-train.d.ts.map +1 -0
  23. package/dist/nodes/spectral-tilt.d.ts +69 -0
  24. package/dist/nodes/spectral-tilt.d.ts.map +1 -0
  25. package/dist/nodes/types.d.ts +10 -0
  26. package/dist/nodes/types.d.ts.map +1 -0
  27. package/dist/nodes/vocal-tract.d.ts +83 -0
  28. package/dist/nodes/vocal-tract.d.ts.map +1 -0
  29. package/dist/nodes/voice.d.ts +102 -0
  30. package/dist/nodes/voice.d.ts.map +1 -0
  31. package/dist/parameters/index.d.ts +111 -0
  32. package/dist/parameters/index.d.ts.map +1 -0
  33. package/dist/parameters/vowels.d.ts +28 -0
  34. package/dist/parameters/vowels.d.ts.map +1 -0
  35. package/package.json +52 -0
package/dist/index.js ADDED
@@ -0,0 +1,1486 @@
1
+ var lt = Object.defineProperty;
2
+ var ht = (a, t, e) => t in a ? lt(a, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : a[t] = e;
3
+ var r = (a, t, e) => ht(a, typeof t != "symbol" ? t + "" : t, e);
4
+ class w {
5
+ constructor(t, e) {
6
+ r(this, "ctx");
7
+ r(this, "gainNode");
8
+ r(this, "in");
9
+ r(this, "out");
10
+ this.ctx = t, this.gainNode = e, this.in = e, this.out = e;
11
+ }
12
+ static async create(t, e) {
13
+ const i = t.createGain();
14
+ return i.gain.setValueAtTime(e.gain, t.currentTime), new w(t, i);
15
+ }
16
+ update(t) {
17
+ this.gainNode.gain.setTargetAtTime(t.gain, this.ctx.currentTime, 0.02);
18
+ }
19
+ destroy() {
20
+ this.gainNode.disconnect();
21
+ }
22
+ get gain() {
23
+ return this.gainNode.gain;
24
+ }
25
+ }
26
+ const dt = `
27
+ class PulseTrainProcessor extends AudioWorkletProcessor {
28
+ static get parameterDescriptors() {
29
+ return [
30
+ { name: "f0", defaultValue: 220, minValue: 20, maxValue: 20000, automationRate: "a-rate" },
31
+ { name: "jitterDepth", defaultValue: 0, minValue: 0, maxValue: 0.3, automationRate: "k-rate" },
32
+ { name: "shimmerDepth", defaultValue: 0, minValue: 0, maxValue: 1, automationRate: "k-rate" }
33
+ ];
34
+ }
35
+
36
+ constructor() {
37
+ super();
38
+ // Phase accumulator (0 to 1)
39
+ this.phase = 0;
40
+
41
+ // Current perturbation values (updated each period)
42
+ this.currentJitter = 1; // Multiplier for f0 (1 = no jitter)
43
+ this.currentShimmer = 1; // Multiplier for amplitude (1 = no shimmer)
44
+
45
+ // Box-Muller spare for Gaussian random
46
+ this.hasSpare = false;
47
+ this.spare = 0;
48
+
49
+ // Cached harmonic count for band-limiting
50
+ this.harmonicCount = 0;
51
+ this.lastF0ForHarmonics = 220;
52
+ this.updateHarmonicCount(220);
53
+ }
54
+
55
+ /**
56
+ * Update the number of harmonics to sum for band-limited impulse.
57
+ * We include harmonics up to Nyquist to avoid aliasing.
58
+ */
59
+ updateHarmonicCount(f0) {
60
+ const nyquist = sampleRate / 2;
61
+ this.harmonicCount = Math.max(1, Math.floor(nyquist / f0));
62
+ this.lastF0ForHarmonics = f0;
63
+ }
64
+
65
+ /**
66
+ * Generate a Gaussian-distributed random number using Box-Muller transform.
67
+ * Returns values with mean 0 and standard deviation 1.
68
+ */
69
+ gaussianRandom() {
70
+ if (this.hasSpare) {
71
+ this.hasSpare = false;
72
+ return this.spare;
73
+ }
74
+
75
+ let u, v, s;
76
+ do {
77
+ u = Math.random() * 2 - 1;
78
+ v = Math.random() * 2 - 1;
79
+ s = u * u + v * v;
80
+ } while (s >= 1 || s === 0);
81
+
82
+ const mul = Math.sqrt(-2 * Math.log(s) / s);
83
+ this.spare = v * mul;
84
+ this.hasSpare = true;
85
+ return u * mul;
86
+ }
87
+
88
+ /**
89
+ * Generate new perturbation values at a period boundary.
90
+ * Jitter affects the next period's frequency.
91
+ * Shimmer affects the current pulse's amplitude.
92
+ */
93
+ generatePerturbations(jitterDepth, shimmerDepth) {
94
+ // Jitter: Gaussian perturbation of f0, clipped to ±jitterDepth
95
+ // Using Gaussian makes small perturbations more likely than large ones
96
+ const jitterRaw = this.gaussianRandom() * jitterDepth * 0.5;
97
+ const jitterClamped = Math.max(-jitterDepth, Math.min(jitterDepth, jitterRaw));
98
+ this.currentJitter = 1 + jitterClamped;
99
+
100
+ // Shimmer: Gaussian perturbation of amplitude, clipped to [0, 2]
101
+ // At shimmerDepth=1, amplitude can vary from 0 to 2 (±100%)
102
+ const shimmerRaw = this.gaussianRandom() * shimmerDepth * 0.5;
103
+ const shimmerClamped = Math.max(-shimmerDepth, Math.min(shimmerDepth, shimmerRaw));
104
+ this.currentShimmer = 1 + shimmerClamped;
105
+
106
+ // Clamp shimmer to non-negative (amplitude can't be negative)
107
+ this.currentShimmer = Math.max(0, this.currentShimmer);
108
+ }
109
+
110
+ /**
111
+ * Compute a band-limited impulse using additive synthesis.
112
+ * Sums cosines at all harmonic frequencies up to Nyquist.
113
+ *
114
+ * The impulse is normalized so that its peak amplitude is approximately 1.
115
+ */
116
+ computeBandLimitedImpulse(phase) {
117
+ // Sum all harmonics: Σ cos(2π * k * phase) for k = 1 to harmonicCount
118
+ // This produces a Dirac comb approximation
119
+ let sum = 0;
120
+ const twoPiPhase = 2 * Math.PI * phase;
121
+ for (let k = 1; k <= this.harmonicCount; k++) {
122
+ sum += Math.cos(k * twoPiPhase);
123
+ }
124
+ // Normalize by harmonic count to keep amplitude reasonable
125
+ return sum / this.harmonicCount;
126
+ }
127
+
128
+ process(inputs, outputs, parameters) {
129
+ const output = outputs[0]?.[0];
130
+ if (!output) {
131
+ return true;
132
+ }
133
+
134
+ const dt = 1 / sampleRate;
135
+ // k-rate parameters (single value per block)
136
+ const jitterDepth = parameters.jitterDepth[0];
137
+ const shimmerDepth = parameters.shimmerDepth[0];
138
+
139
+ for (let i = 0; i < output.length; i++) {
140
+ // a-rate: per-sample value for f0
141
+ const f0 = parameters.f0.length > 1 ? parameters.f0[i] : parameters.f0[0];
142
+
143
+ // Compute effective f0 with current jitter
144
+ const f0Effective = f0 * this.currentJitter;
145
+
146
+ // Advance phase
147
+ this.phase += f0Effective * dt;
148
+
149
+ // Check for period boundary (phase wrapped)
150
+ if (this.phase >= 1) {
151
+ this.phase -= 1;
152
+ // Generate new perturbations for the next period
153
+ this.generatePerturbations(jitterDepth, shimmerDepth);
154
+ // Update harmonic count if f0 changed significantly (>10%)
155
+ if (Math.abs(f0 - this.lastF0ForHarmonics) / this.lastF0ForHarmonics > 0.1) {
156
+ this.updateHarmonicCount(f0);
157
+ }
158
+ }
159
+
160
+ // Generate band-limited impulse and apply shimmer
161
+ output[i] = this.currentShimmer * this.computeBandLimitedImpulse(this.phase);
162
+ }
163
+
164
+ return true;
165
+ }
166
+ }
167
+
168
+ registerProcessor('pulse-train-processor', PulseTrainProcessor);
169
+ `;
170
+ let Q = !1;
171
+ async function pt(a) {
172
+ if (Q)
173
+ return;
174
+ const t = new Blob([dt], { type: "application/javascript" }), e = URL.createObjectURL(t);
175
+ try {
176
+ await a.audioWorklet.addModule(e), Q = !0;
177
+ } finally {
178
+ URL.revokeObjectURL(e);
179
+ }
180
+ }
181
+ class V {
182
+ constructor(t, e, i) {
183
+ r(this, "ctx");
184
+ r(this, "workletNode");
185
+ r(this, "gain");
186
+ r(this, "in");
187
+ r(this, "out");
188
+ this.ctx = t, this.workletNode = e, this.gain = i, this.out = i.out, this.in = null;
189
+ }
190
+ /** Fundamental frequency AudioParam (a-rate, 20-20000 Hz) */
191
+ get f0() {
192
+ return this.workletNode.parameters.get("f0");
193
+ }
194
+ /** Jitter depth AudioParam (k-rate, 0-0.3) */
195
+ get jitterDepth() {
196
+ return this.workletNode.parameters.get("jitterDepth");
197
+ }
198
+ /** Shimmer depth AudioParam (k-rate, 0-1) */
199
+ get shimmerDepth() {
200
+ return this.workletNode.parameters.get("shimmerDepth");
201
+ }
202
+ static async create(t, e) {
203
+ await pt(t);
204
+ const i = new AudioWorkletNode(t, "pulse-train-processor"), s = await w.create(t, { gain: 0 });
205
+ i.connect(s.in);
206
+ const n = new V(t, i, s);
207
+ return n.update(e), n;
208
+ }
209
+ update(t) {
210
+ this.f0.setTargetAtTime(t.f0, this.ctx.currentTime, 0.02), this.jitterDepth.setTargetAtTime(t.jitterDepth, this.ctx.currentTime, 0.02), this.shimmerDepth.setTargetAtTime(t.shimmerDepth, this.ctx.currentTime, 0.02);
211
+ }
212
+ destroy() {
213
+ this.workletNode.disconnect(), this.gain.destroy();
214
+ }
215
+ start() {
216
+ this.gain.update({ gain: 1 });
217
+ }
218
+ stop() {
219
+ this.gain.update({ gain: 0 });
220
+ }
221
+ }
222
+ const mt = `
223
+ class GlottalFormantProcessor extends AudioWorkletProcessor {
224
+ static get parameterDescriptors() {
225
+ return [
226
+ { name: "Fg", defaultValue: 110, minValue: 20, maxValue: 2000, automationRate: "a-rate" },
227
+ { name: "Bg", defaultValue: 50, minValue: 10, maxValue: 500, automationRate: "a-rate" },
228
+ { name: "Ag", defaultValue: 1, minValue: 0, maxValue: 10, automationRate: "a-rate" }
229
+ ];
230
+ }
231
+
232
+ constructor() {
233
+ super();
234
+ // Filter state (delay line)
235
+ this.x1 = 0;
236
+ this.x2 = 0;
237
+ this.y1 = 0;
238
+ this.y2 = 0;
239
+
240
+ // Cached coefficient values to avoid recomputing when unchanged
241
+ this.lastFg = -1;
242
+ this.lastBg = -1;
243
+ this.R = 0;
244
+ this.cosTheta = 0;
245
+ this.a1 = 0;
246
+ this.a2 = 0;
247
+ }
248
+
249
+ process(inputs, outputs, parameters) {
250
+ const input = inputs[0]?.[0];
251
+ const output = outputs[0]?.[0];
252
+
253
+ if (!input || !output) {
254
+ return true;
255
+ }
256
+
257
+ const Ts = 1 / sampleRate;
258
+ // k-rate parameters (single value per block)
259
+ const Fg = parameters.Fg[0];
260
+ const Bg = parameters.Bg[0];
261
+
262
+ // Recompute filter coefficients if Fg or Bg changed
263
+ if (Fg !== this.lastFg || Bg !== this.lastBg) {
264
+ this.R = Math.exp(-Math.PI * Bg * Ts);
265
+ const theta = 2 * Math.PI * Fg * Ts;
266
+ this.cosTheta = Math.cos(theta);
267
+ this.a1 = -2 * this.R * this.cosTheta;
268
+ this.a2 = this.R * this.R;
269
+ this.lastFg = Fg;
270
+ this.lastBg = Bg;
271
+ }
272
+
273
+ for (let i = 0; i < input.length; i++) {
274
+ // a-rate: per-sample value for Ag
275
+ const Ag = parameters.Ag.length > 1 ? parameters.Ag[i] : parameters.Ag[0];
276
+
277
+ const x0 = input[i];
278
+
279
+ // Numerator coefficients (feedforward) depend on Ag
280
+ // From: -Ag * z^{-1} * (1 - z^{-1}) = -Ag*z^{-1} + Ag*z^{-2}
281
+ const b1 = -Ag;
282
+ const b2 = Ag;
283
+
284
+ // Direct Form I biquad (b0 is always 0)
285
+ const y0 = b1 * this.x1 + b2 * this.x2
286
+ - this.a1 * this.y1 - this.a2 * this.y2;
287
+
288
+ // Update delay line
289
+ this.x2 = this.x1;
290
+ this.x1 = x0;
291
+ this.y2 = this.y1;
292
+ this.y1 = y0;
293
+
294
+ output[i] = y0;
295
+ }
296
+
297
+ return true;
298
+ }
299
+ }
300
+
301
+ registerProcessor('glottal-formant-processor', GlottalFormantProcessor);
302
+ `;
303
+ let z = !1;
304
+ async function ft(a) {
305
+ if (z)
306
+ return;
307
+ const t = new Blob([mt], { type: "application/javascript" }), e = URL.createObjectURL(t);
308
+ try {
309
+ await a.audioWorklet.addModule(e), z = !0;
310
+ } finally {
311
+ URL.revokeObjectURL(e);
312
+ }
313
+ }
314
+ class D {
315
+ constructor(t, e) {
316
+ r(this, "ctx");
317
+ r(this, "workletNode");
318
+ r(this, "in");
319
+ r(this, "out");
320
+ this.ctx = t, this.workletNode = e, this.in = e, this.out = e;
321
+ }
322
+ /** Glottal formant centre frequency AudioParam (a-rate, 20-2000 Hz) */
323
+ get Fg() {
324
+ return this.workletNode.parameters.get("Fg");
325
+ }
326
+ /** Glottal formant bandwidth AudioParam (a-rate, 10-500 Hz) */
327
+ get Bg() {
328
+ return this.workletNode.parameters.get("Bg");
329
+ }
330
+ /** Source amplitude AudioParam (a-rate, 0-10) */
331
+ get Ag() {
332
+ return this.workletNode.parameters.get("Ag");
333
+ }
334
+ /**
335
+ * Creates a new GlottalFormant node.
336
+ *
337
+ * The AudioWorklet module is registered automatically on first use.
338
+ */
339
+ static async create(t, e) {
340
+ await ft(t);
341
+ const i = new AudioWorkletNode(t, "glottal-formant-processor"), s = new D(t, i);
342
+ return s.update(e), s;
343
+ }
344
+ /**
345
+ * Updates the glottal formant parameters.
346
+ * Sets AudioParams via setTargetAtTime for smooth transitions.
347
+ */
348
+ update(t) {
349
+ this.Fg.setTargetAtTime(t.Fg, this.ctx.currentTime, 0.02), this.Bg.setTargetAtTime(t.Bg, this.ctx.currentTime, 0.02), this.Ag.setTargetAtTime(t.Ag, this.ctx.currentTime, 0.02);
350
+ }
351
+ destroy() {
352
+ this.workletNode.disconnect();
353
+ }
354
+ }
355
+ const gt = `
356
+ class SpectralTiltProcessor extends AudioWorkletProcessor {
357
+ static get parameterDescriptors() {
358
+ return [
359
+ { name: "Tl1", defaultValue: 0, minValue: 0, maxValue: 50, automationRate: "k-rate" },
360
+ { name: "Tl2", defaultValue: 0, minValue: 0, maxValue: 30, automationRate: "k-rate" }
361
+ ];
362
+ }
363
+
364
+ constructor() {
365
+ super();
366
+ // Filter coefficients for two stages
367
+ this.a1 = 0; // First stage pole
368
+ this.a2 = 0; // Second stage pole
369
+ this.g1 = 1; // First stage gain (1 - a1)
370
+ this.g2 = 1; // Second stage gain (1 - a2)
371
+
372
+ // Filter state (one delay element per stage)
373
+ this.y1_prev = 0; // Previous output of stage 1
374
+ this.y2_prev = 0; // Previous output of stage 2
375
+
376
+ // Cache last parameter values for change detection
377
+ this.lastTl1 = -1;
378
+ this.lastTl2 = -1;
379
+
380
+ // Pre-compute cosOmega since it's constant for a given sample rate
381
+ const Ts = 1 / sampleRate;
382
+ this.cosOmega = Math.cos(2 * Math.PI * 3000 * Ts);
383
+ }
384
+
385
+ /**
386
+ * Compute the pole coefficient for a single stage.
387
+ * Returns [a, g] where a is the pole and g = 1 - a is the gain.
388
+ */
389
+ computeStageCoefficients(Tl) {
390
+ // Bypass if attenuation is zero or negative
391
+ if (Tl <= 0) {
392
+ return [0, 1];
393
+ }
394
+
395
+ // Power ratio at 3000 Hz (10^(Tl/10) is the attenuation factor)
396
+ const attenuationRatio = Math.pow(10, Tl / 10);
397
+
398
+ // Compute ν from Section 3.2.2
399
+ // νᵢ = 1 + (1 - cos(ω)) / (10^(Tlᵢ/10) - 1)
400
+ const nu = 1 + (1 - this.cosOmega) / (attenuationRatio - 1);
401
+
402
+ // Compute pole coefficient: a = ν - √(ν² - 1)
403
+ // This ensures 0 < a < 1 for stability
404
+ const a = nu - Math.sqrt(nu * nu - 1);
405
+ const g = 1 - a;
406
+
407
+ return [a, g];
408
+ }
409
+
410
+ process(inputs, outputs, parameters) {
411
+ const input = inputs[0]?.[0];
412
+ const output = outputs[0]?.[0];
413
+
414
+ if (!input || !output) {
415
+ return true;
416
+ }
417
+
418
+ // k-rate parameters (single value per block)
419
+ const Tl1 = parameters.Tl1[0];
420
+ const Tl2 = parameters.Tl2[0];
421
+
422
+ // Recompute coefficients if parameters changed
423
+ if (Tl1 !== this.lastTl1) {
424
+ [this.a1, this.g1] = this.computeStageCoefficients(Tl1);
425
+ this.lastTl1 = Tl1;
426
+ }
427
+ if (Tl2 !== this.lastTl2) {
428
+ [this.a2, this.g2] = this.computeStageCoefficients(Tl2);
429
+ this.lastTl2 = Tl2;
430
+ }
431
+
432
+ for (let i = 0; i < input.length; i++) {
433
+ // Stage 1: y1[n] = g1 * x[n] + a1 * y1[n-1]
434
+ const y1 = this.g1 * input[i] + this.a1 * this.y1_prev;
435
+ this.y1_prev = y1;
436
+
437
+ // Stage 2: y2[n] = g2 * y1[n] + a2 * y2[n-1]
438
+ const y2 = this.g2 * y1 + this.a2 * this.y2_prev;
439
+ this.y2_prev = y2;
440
+
441
+ output[i] = y2;
442
+ }
443
+
444
+ return true;
445
+ }
446
+ }
447
+
448
+ registerProcessor('spectral-tilt-processor', SpectralTiltProcessor);
449
+ `;
450
+ let O = !1;
451
+ async function yt(a) {
452
+ if (O)
453
+ return;
454
+ const t = new Blob([gt], { type: "application/javascript" }), e = URL.createObjectURL(t);
455
+ try {
456
+ await a.audioWorklet.addModule(e), O = !0;
457
+ } finally {
458
+ URL.revokeObjectURL(e);
459
+ }
460
+ }
461
+ class N {
462
+ constructor(t, e) {
463
+ r(this, "ctx");
464
+ r(this, "workletNode");
465
+ r(this, "in");
466
+ r(this, "out");
467
+ this.ctx = t, this.workletNode = e, this.in = e, this.out = e;
468
+ }
469
+ /** First stage spectral tilt attenuation AudioParam (k-rate, 0-50 dB at 3kHz) */
470
+ get Tl1() {
471
+ return this.workletNode.parameters.get("Tl1");
472
+ }
473
+ /** Second stage spectral tilt attenuation AudioParam (k-rate, 0-30 dB at 3kHz) */
474
+ get Tl2() {
475
+ return this.workletNode.parameters.get("Tl2");
476
+ }
477
+ /**
478
+ * Creates a new SpectralTilt node.
479
+ *
480
+ * The AudioWorklet module is registered automatically on first use.
481
+ */
482
+ static async create(t, e) {
483
+ await yt(t);
484
+ const i = new AudioWorkletNode(t, "spectral-tilt-processor"), s = new N(t, i);
485
+ return s.update(e), s;
486
+ }
487
+ /**
488
+ * Updates the spectral tilt parameters.
489
+ * Sets AudioParams via setTargetAtTime for smooth transitions.
490
+ */
491
+ update(t) {
492
+ this.Tl1.setTargetAtTime(t.Tl1, this.ctx.currentTime, 0.02), this.Tl2.setTargetAtTime(t.Tl2, this.ctx.currentTime, 0.02);
493
+ }
494
+ destroy() {
495
+ this.workletNode.disconnect();
496
+ }
497
+ }
498
+ const wt = `
499
+ class NoiseSourceProcessor extends AudioWorkletProcessor {
500
+ static get parameterDescriptors() {
501
+ return [
502
+ { name: "An", defaultValue: 0, minValue: 0, maxValue: 1, automationRate: "a-rate" }
503
+ ];
504
+ }
505
+
506
+ constructor() {
507
+ super();
508
+ // Box-Muller spare value
509
+ this.hasSpare = false;
510
+ this.spare = 0;
511
+ }
512
+
513
+ /**
514
+ * Generate a Gaussian-distributed random number using Box-Muller transform.
515
+ * Returns values with mean 0 and standard deviation 1.
516
+ */
517
+ gaussianRandom() {
518
+ if (this.hasSpare) {
519
+ this.hasSpare = false;
520
+ return this.spare;
521
+ }
522
+
523
+ // Generate two uniform random numbers in (0, 1)
524
+ let u, v, s;
525
+ do {
526
+ u = Math.random() * 2 - 1;
527
+ v = Math.random() * 2 - 1;
528
+ s = u * u + v * v;
529
+ } while (s >= 1 || s === 0);
530
+
531
+ const mul = Math.sqrt(-2 * Math.log(s) / s);
532
+ this.spare = v * mul;
533
+ this.hasSpare = true;
534
+ return u * mul;
535
+ }
536
+
537
+ process(inputs, outputs, parameters) {
538
+ const output = outputs[0]?.[0];
539
+
540
+ if (!output) {
541
+ return true;
542
+ }
543
+
544
+ for (let i = 0; i < output.length; i++) {
545
+ // a-rate: per-sample value for An
546
+ const An = parameters.An.length > 1 ? parameters.An[i] : parameters.An[0];
547
+ output[i] = An * this.gaussianRandom();
548
+ }
549
+
550
+ return true;
551
+ }
552
+ }
553
+
554
+ registerProcessor('noise-source-processor', NoiseSourceProcessor);
555
+ `;
556
+ let W = !1;
557
+ async function bt(a) {
558
+ if (W)
559
+ return;
560
+ const t = new Blob([wt], { type: "application/javascript" }), e = URL.createObjectURL(t);
561
+ try {
562
+ await a.audioWorklet.addModule(e), W = !0;
563
+ } finally {
564
+ URL.revokeObjectURL(e);
565
+ }
566
+ }
567
+ class P {
568
+ constructor(t, e, i, s) {
569
+ r(this, "ctx");
570
+ r(this, "workletNode");
571
+ r(this, "highpassFilter");
572
+ r(this, "lowpassFilter");
573
+ r(this, "in", null);
574
+ // Noise source has no input
575
+ r(this, "out");
576
+ this.ctx = t, this.workletNode = e, this.highpassFilter = i, this.lowpassFilter = s, this.out = s;
577
+ }
578
+ /** Noise amplitude AudioParam (a-rate, 0-1) */
579
+ get An() {
580
+ return this.workletNode.parameters.get("An");
581
+ }
582
+ /**
583
+ * Creates a new NoiseSource node.
584
+ *
585
+ * The AudioWorklet module is registered automatically on first use.
586
+ */
587
+ static async create(t, e) {
588
+ await bt(t);
589
+ const i = new AudioWorkletNode(t, "noise-source-processor"), s = t.createBiquadFilter();
590
+ s.type = "highpass", s.frequency.setValueAtTime(1e3, t.currentTime), s.Q.setValueAtTime(Math.SQRT1_2, t.currentTime);
591
+ const n = t.createBiquadFilter();
592
+ n.type = "lowpass", n.frequency.setValueAtTime(6e3, t.currentTime), n.Q.setValueAtTime(Math.SQRT1_2, t.currentTime), i.connect(s), s.connect(n);
593
+ const o = new P(t, i, s, n);
594
+ return o.update(e), o;
595
+ }
596
+ /**
597
+ * Updates the noise source amplitude.
598
+ * Sets AudioParam via setTargetAtTime for smooth transitions.
599
+ */
600
+ update(t) {
601
+ this.An.setTargetAtTime(t.An, this.ctx.currentTime, 0.02);
602
+ }
603
+ destroy() {
604
+ this.workletNode.disconnect(), this.highpassFilter.disconnect(), this.lowpassFilter.disconnect();
605
+ }
606
+ }
607
+ class S {
608
+ constructor(t, e, i, s, n, o) {
609
+ r(this, "pulseTrain");
610
+ r(this, "glottalFormant");
611
+ r(this, "spectralTilt");
612
+ r(this, "noiseSource");
613
+ r(this, "noiseModulator");
614
+ // Multiplies noise by GFD signal
615
+ r(this, "outputGain");
616
+ r(this, "in", null);
617
+ // Source node has no input
618
+ r(this, "out");
619
+ this.pulseTrain = t, this.glottalFormant = e, this.spectralTilt = i, this.noiseSource = s, this.noiseModulator = n, this.outputGain = o, this.out = o.out;
620
+ }
621
+ /**
622
+ * Creates a new GlottalFlowDerivative node.
623
+ *
624
+ * Sets up the voiced path (pulse train → glottal formant → spectral tilt)
625
+ * and the noise modulation path where noise is multiplied by the GFD signal.
626
+ */
627
+ static async create(t, e) {
628
+ const [i, s, n, o, c] = await Promise.all([
629
+ V.create(t, {
630
+ f0: e.f0,
631
+ jitterDepth: e.jitterDepth,
632
+ shimmerDepth: e.shimmerDepth
633
+ }),
634
+ D.create(t, { Fg: e.Fg, Bg: e.Bg, Ag: e.Ag }),
635
+ N.create(t, { Tl1: e.Tl1, Tl2: e.Tl2 }),
636
+ P.create(t, { An: e.An }),
637
+ w.create(t, { gain: 1 })
638
+ ]), l = t.createGain();
639
+ return l.gain.setValueAtTime(0, t.currentTime), i.out.connect(s.in), s.out.connect(n.in), n.out.connect(c.in), n.out.connect(l.gain), o.out.connect(l), l.connect(c.in), new S(
640
+ i,
641
+ s,
642
+ n,
643
+ o,
644
+ l,
645
+ c
646
+ );
647
+ }
648
+ /**
649
+ * Updates all glottal flow derivative parameters.
650
+ * Each sub-node receives its relevant parameters.
651
+ */
652
+ update(t) {
653
+ this.pulseTrain.update({
654
+ f0: t.f0,
655
+ jitterDepth: t.jitterDepth,
656
+ shimmerDepth: t.shimmerDepth
657
+ }), this.glottalFormant.update({ Fg: t.Fg, Bg: t.Bg, Ag: t.Ag }), this.spectralTilt.update({ Tl1: t.Tl1, Tl2: t.Tl2 }), this.noiseSource.update({ An: t.An });
658
+ }
659
+ /**
660
+ * Returns the pulse train node for direct AudioParam access.
661
+ *
662
+ * Example usage:
663
+ * gfd.pulseTrainNode.f0.setValueAtTime(440, ctx.currentTime);
664
+ * gfd.pulseTrainNode.jitterDepth.linearRampToValueAtTime(0.1, ctx.currentTime + 0.5);
665
+ */
666
+ get pulseTrainNode() {
667
+ return this.pulseTrain;
668
+ }
669
+ /**
670
+ * Returns the glottal formant node for direct AudioParam access.
671
+ *
672
+ * Example usage:
673
+ * gfd.glottalFormantNode.Fg.setValueAtTime(220, ctx.currentTime);
674
+ * gfd.glottalFormantNode.Ag.setTargetAtTime(0.5, ctx.currentTime, 0.1);
675
+ */
676
+ get glottalFormantNode() {
677
+ return this.glottalFormant;
678
+ }
679
+ /**
680
+ * Returns the spectral tilt node for direct AudioParam access.
681
+ *
682
+ * Example usage:
683
+ * gfd.spectralTiltNode.Tl1.setValueAtTime(10, ctx.currentTime);
684
+ */
685
+ get spectralTiltNode() {
686
+ return this.spectralTilt;
687
+ }
688
+ /**
689
+ * Returns the noise source node for direct AudioParam access.
690
+ *
691
+ * Example usage:
692
+ * gfd.noiseSourceNode.An.setValueAtTime(0.3, ctx.currentTime);
693
+ */
694
+ get noiseSourceNode() {
695
+ return this.noiseSource;
696
+ }
697
+ /**
698
+ * Starts the voiced excitation (pulse train).
699
+ */
700
+ start() {
701
+ this.pulseTrain.start();
702
+ }
703
+ /**
704
+ * Stops the voiced excitation (pulse train).
705
+ * Note: Noise source continues to produce output based on An parameter.
706
+ */
707
+ stop() {
708
+ this.pulseTrain.stop();
709
+ }
710
+ destroy() {
711
+ this.pulseTrain.destroy(), this.glottalFormant.destroy(), this.spectralTilt.destroy(), this.noiseSource.destroy(), this.noiseModulator.disconnect(), this.outputGain.destroy();
712
+ }
713
+ }
714
+ const Tt = `
715
+ class FormantResonatorProcessor extends AudioWorkletProcessor {
716
+ static get parameterDescriptors() {
717
+ return [
718
+ { name: "F", defaultValue: 500, minValue: 100, maxValue: 8000, automationRate: "a-rate" },
719
+ { name: "B", defaultValue: 100, minValue: 20, maxValue: 1000, automationRate: "a-rate" },
720
+ { name: "A", defaultValue: 1, minValue: 0, maxValue: 10, automationRate: "a-rate" }
721
+ ];
722
+ }
723
+
724
+ constructor() {
725
+ super();
726
+ // Filter state (delay line)
727
+ this.x1 = 0;
728
+ this.x2 = 0;
729
+ this.y1 = 0;
730
+ this.y2 = 0;
731
+
732
+ // Cached parameter values for change detection
733
+ this.lastF = -1;
734
+ this.lastB = -1;
735
+ this.lastA = -1;
736
+
737
+ // Cached coefficients
738
+ this.b0 = 0;
739
+ this.b2 = 0;
740
+ this.a1 = 0;
741
+ this.a2 = 0;
742
+ }
743
+
744
+ process(inputs, outputs, parameters) {
745
+ const input = inputs[0]?.[0];
746
+ const output = outputs[0]?.[0];
747
+
748
+ if (!input || !output) {
749
+ return true;
750
+ }
751
+
752
+ // a-rate parameters (per-sample values)
753
+ const F = parameters.F[0];
754
+ const B = parameters.B[0];
755
+ const A = parameters.A[0];
756
+
757
+ // Recompute coefficients if any parameter changed
758
+ if (F !== this.lastF || B !== this.lastB || A !== this.lastA) {
759
+ const Ts = 1 / sampleRate;
760
+
761
+ // Pole radius and angle
762
+ const R = Math.exp(-Math.PI * B * Ts);
763
+ const theta = 2 * Math.PI * F * Ts;
764
+
765
+ // Gain scaling factor
766
+ const g = 1 - R;
767
+
768
+ // Numerator coefficients (feedforward)
769
+ // From: A * g * (1 - R*z^{-2}) = A*g + 0*z^{-1} - A*g*R*z^{-2}
770
+ this.b0 = A * g;
771
+ this.b2 = -A * g * R;
772
+
773
+ // Denominator coefficients (feedback)
774
+ // From: 1 - 2R*cos(θ)*z^{-1} + R²*z^{-2}
775
+ this.a1 = -2 * R * Math.cos(theta);
776
+ this.a2 = R * R;
777
+
778
+ this.lastF = F;
779
+ this.lastB = B;
780
+ this.lastA = A;
781
+ }
782
+
783
+ for (let i = 0; i < input.length; i++) {
784
+ const x0 = input[i];
785
+
786
+ // Direct Form I biquad (b1 is always 0)
787
+ const y0 = this.b0 * x0 + this.b2 * this.x2
788
+ - this.a1 * this.y1 - this.a2 * this.y2;
789
+
790
+ // Update delay line
791
+ this.x2 = this.x1;
792
+ this.x1 = x0;
793
+ this.y2 = this.y1;
794
+ this.y1 = y0;
795
+
796
+ output[i] = y0;
797
+ }
798
+
799
+ return true;
800
+ }
801
+ }
802
+
803
+ registerProcessor('formant-resonator-processor', FormantResonatorProcessor);
804
+ `;
805
+ let $ = !1;
806
+ async function Ft(a) {
807
+ if ($)
808
+ return;
809
+ const t = new Blob([Tt], { type: "application/javascript" }), e = URL.createObjectURL(t);
810
+ try {
811
+ await a.audioWorklet.addModule(e), $ = !0;
812
+ } finally {
813
+ URL.revokeObjectURL(e);
814
+ }
815
+ }
816
+ class C {
817
+ constructor(t, e) {
818
+ r(this, "ctx");
819
+ r(this, "workletNode");
820
+ r(this, "in");
821
+ r(this, "out");
822
+ this.ctx = t, this.workletNode = e, this.in = e, this.out = e;
823
+ }
824
+ /** Formant centre frequency AudioParam (a-rate, 100-8000 Hz) */
825
+ get F() {
826
+ return this.workletNode.parameters.get("F");
827
+ }
828
+ /** Formant bandwidth AudioParam (a-rate, 20-1000 Hz) */
829
+ get B() {
830
+ return this.workletNode.parameters.get("B");
831
+ }
832
+ /** Formant amplitude AudioParam (a-rate, 0-10) */
833
+ get A() {
834
+ return this.workletNode.parameters.get("A");
835
+ }
836
+ /**
837
+ * Creates a new FormantResonator node.
838
+ *
839
+ * The AudioWorklet module is registered automatically on first use.
840
+ */
841
+ static async create(t, e) {
842
+ await Ft(t);
843
+ const i = new AudioWorkletNode(t, "formant-resonator-processor"), s = new C(t, i);
844
+ return s.update(e), s;
845
+ }
846
+ /**
847
+ * Updates the formant resonator parameters.
848
+ * Sets AudioParams via setTargetAtTime for smooth transitions.
849
+ */
850
+ update(t) {
851
+ this.F.setTargetAtTime(t.F, this.ctx.currentTime, 0.02), this.B.setTargetAtTime(t.B, this.ctx.currentTime, 0.02), this.A.setTargetAtTime(t.A, this.ctx.currentTime, 0.02);
852
+ }
853
+ destroy() {
854
+ this.workletNode.disconnect();
855
+ }
856
+ }
857
+ class j {
858
+ constructor(t, e, i) {
859
+ r(this, "resonators");
860
+ r(this, "inputGain");
861
+ r(this, "outputGain");
862
+ r(this, "in");
863
+ r(this, "out");
864
+ this.resonators = t, this.inputGain = e, this.outputGain = i, this.in = e, this.out = i;
865
+ }
866
+ /**
867
+ * Creates a new FormantBank node with the specified formants.
868
+ *
869
+ * All formant resonators are created in parallel for efficiency.
870
+ */
871
+ static async create(t, e) {
872
+ const i = t.createGain();
873
+ i.gain.setValueAtTime(1, t.currentTime);
874
+ const s = t.createGain();
875
+ s.gain.setValueAtTime(1, t.currentTime);
876
+ const n = await Promise.all(
877
+ e.formants.map((o) => C.create(t, o))
878
+ );
879
+ for (const o of n)
880
+ i.connect(o.in), o.out.connect(s);
881
+ return new j(n, i, s);
882
+ }
883
+ /**
884
+ * Updates all formant parameters.
885
+ *
886
+ * The formants array must have the same length as the original.
887
+ * Each resonator receives its corresponding parameters.
888
+ *
889
+ * @throws Error if the formants array length doesn't match
890
+ */
891
+ update(t) {
892
+ if (t.formants.length !== this.resonators.length)
893
+ throw new Error(
894
+ `FormantBank: Cannot change number of formants. Expected ${this.resonators.length}, got ${t.formants.length}. Destroy and recreate the node to change formant count.`
895
+ );
896
+ for (let e = 0; e < this.resonators.length; e++)
897
+ this.resonators[e].update(t.formants[e]);
898
+ }
899
+ /**
900
+ * Returns the number of formant resonators in this bank.
901
+ */
902
+ get count() {
903
+ return this.resonators.length;
904
+ }
905
+ /**
906
+ * Returns the array of formant resonators for direct AudioParam access.
907
+ *
908
+ * Example usage:
909
+ * formantBank.formants[0].F.setValueAtTime(800, ctx.currentTime);
910
+ * formantBank.formants[1].B.linearRampToValueAtTime(100, ctx.currentTime + 0.1);
911
+ */
912
+ get formants() {
913
+ return this.resonators;
914
+ }
915
+ destroy() {
916
+ this.inputGain.disconnect(), this.outputGain.disconnect();
917
+ for (const t of this.resonators)
918
+ t.destroy();
919
+ this.resonators = [];
920
+ }
921
+ }
922
+ const Rt = `
923
+ class AntiResonanceProcessor extends AudioWorkletProcessor {
924
+ static get parameterDescriptors() {
925
+ return [
926
+ { name: "F", defaultValue: 4700, minValue: 1000, maxValue: 10000, automationRate: "k-rate" },
927
+ { name: "Q", defaultValue: 2.5, minValue: 0.5, maxValue: 20, automationRate: "k-rate" }
928
+ ];
929
+ }
930
+
931
+ constructor() {
932
+ super();
933
+ // Filter state (delay line)
934
+ this.x1 = 0;
935
+ this.x2 = 0;
936
+ this.y1 = 0;
937
+ this.y2 = 0;
938
+
939
+ // Cached parameter values for change detection
940
+ this.lastF = -1;
941
+ this.lastQ = -1;
942
+
943
+ // Cached coefficients
944
+ this.b0 = 1;
945
+ this.b1 = 0;
946
+ this.b2 = 1;
947
+ this.a1 = 0;
948
+ this.a2 = 0;
949
+ }
950
+
951
+ process(inputs, outputs, parameters) {
952
+ const input = inputs[0]?.[0];
953
+ const output = outputs[0]?.[0];
954
+
955
+ if (!input || !output) {
956
+ return true;
957
+ }
958
+
959
+ // k-rate parameters (single value per block)
960
+ const F = parameters.F[0];
961
+ const Q = parameters.Q[0];
962
+
963
+ // Recompute coefficients if any parameter changed
964
+ if (F !== this.lastF || Q !== this.lastQ) {
965
+ const Ts = 1 / sampleRate;
966
+ const omega = 2 * Math.PI * F * Ts;
967
+
968
+ // Intermediate coefficients from the paper
969
+ const alpha = Math.sin(omega) / (2 * Q);
970
+ const beta = -2 * Math.cos(omega);
971
+
972
+ // Normalize by dividing by (1 + alpha) so that a0 = 1
973
+ const a0 = 1 + alpha;
974
+
975
+ // Numerator coefficients (feedforward)
976
+ // From: (1 + β*z⁻¹ + z⁻²) / a0
977
+ this.b0 = 1 / a0;
978
+ this.b1 = beta / a0;
979
+ this.b2 = 1 / a0;
980
+
981
+ // Denominator coefficients (feedback), normalized
982
+ // From: ((1 + α) + β*z⁻¹ + (1 - α)*z⁻²) / a0
983
+ // a0/a0 = 1 (implicit), a1 = β/a0, a2 = (1 - α)/a0
984
+ this.a1 = beta / a0;
985
+ this.a2 = (1 - alpha) / a0;
986
+
987
+ this.lastF = F;
988
+ this.lastQ = Q;
989
+ }
990
+
991
+ for (let i = 0; i < input.length; i++) {
992
+ const x0 = input[i];
993
+
994
+ // Direct Form I biquad
995
+ const y0 = this.b0 * x0 + this.b1 * this.x1 + this.b2 * this.x2
996
+ - this.a1 * this.y1 - this.a2 * this.y2;
997
+
998
+ // Update delay line
999
+ this.x2 = this.x1;
1000
+ this.x1 = x0;
1001
+ this.y2 = this.y1;
1002
+ this.y1 = y0;
1003
+
1004
+ output[i] = y0;
1005
+ }
1006
+
1007
+ return true;
1008
+ }
1009
+ }
1010
+
1011
+ registerProcessor('anti-resonance-processor', AntiResonanceProcessor);
1012
+ `;
1013
+ let I = !1;
1014
+ async function At(a) {
1015
+ if (I)
1016
+ return;
1017
+ const t = new Blob([Rt], { type: "application/javascript" }), e = URL.createObjectURL(t);
1018
+ try {
1019
+ await a.audioWorklet.addModule(e), I = !0;
1020
+ } finally {
1021
+ URL.revokeObjectURL(e);
1022
+ }
1023
+ }
1024
+ class G {
1025
+ constructor(t, e) {
1026
+ r(this, "ctx");
1027
+ r(this, "workletNode");
1028
+ r(this, "in");
1029
+ r(this, "out");
1030
+ this.ctx = t, this.workletNode = e, this.in = e, this.out = e;
1031
+ }
1032
+ /** Anti-formant centre frequency AudioParam (k-rate, 1000-10000 Hz) */
1033
+ get F() {
1034
+ return this.workletNode.parameters.get("F");
1035
+ }
1036
+ /** Anti-formant quality factor AudioParam (k-rate, 0.5-20) */
1037
+ get Q() {
1038
+ return this.workletNode.parameters.get("Q");
1039
+ }
1040
+ /**
1041
+ * Creates a new AntiResonance node.
1042
+ *
1043
+ * The AudioWorklet module is registered automatically on first use.
1044
+ */
1045
+ static async create(t, e) {
1046
+ await At(t);
1047
+ const i = new AudioWorkletNode(t, "anti-resonance-processor"), s = new G(t, i);
1048
+ return s.update(e), s;
1049
+ }
1050
+ /**
1051
+ * Updates the anti-resonance parameters.
1052
+ * Sets AudioParams via setTargetAtTime for smooth transitions.
1053
+ */
1054
+ update(t) {
1055
+ this.F.setTargetAtTime(t.F, this.ctx.currentTime, 0.02), this.Q.setTargetAtTime(t.Q, this.ctx.currentTime, 0.02);
1056
+ }
1057
+ destroy() {
1058
+ this.workletNode.disconnect();
1059
+ }
1060
+ }
1061
+ class L {
1062
+ constructor(t, e) {
1063
+ r(this, "formantBank");
1064
+ r(this, "antiResonance");
1065
+ r(this, "in");
1066
+ r(this, "out");
1067
+ this.formantBank = t, this.antiResonance = e, this.in = t.in, this.out = e.out;
1068
+ }
1069
+ /**
1070
+ * Creates a new VocalTract node.
1071
+ *
1072
+ * Sets up the parallel formant bank followed by the anti-resonance filter.
1073
+ */
1074
+ static async create(t, e) {
1075
+ const [i, s] = await Promise.all([
1076
+ j.create(t, { formants: e.formants }),
1077
+ G.create(t, { F: e.F_BQ, Q: e.Q_BQ })
1078
+ ]);
1079
+ return i.out.connect(s.in), new L(i, s);
1080
+ }
1081
+ /**
1082
+ * Updates all vocal tract parameters.
1083
+ *
1084
+ * @throws Error if the formants array length doesn't match the original
1085
+ */
1086
+ update(t) {
1087
+ this.formantBank.update({ formants: t.formants }), this.antiResonance.update({ F: t.F_BQ, Q: t.Q_BQ });
1088
+ }
1089
+ /**
1090
+ * Returns the number of formant resonators in the vocal tract.
1091
+ */
1092
+ get formantCount() {
1093
+ return this.formantBank.count;
1094
+ }
1095
+ /**
1096
+ * Returns the array of formant resonators for direct AudioParam access.
1097
+ *
1098
+ * Example usage:
1099
+ * vocalTract.formants[0].F.setValueAtTime(800, ctx.currentTime);
1100
+ * vocalTract.formants[2].B.linearRampToValueAtTime(100, ctx.currentTime + 0.1);
1101
+ */
1102
+ get formants() {
1103
+ return this.formantBank.formants;
1104
+ }
1105
+ /**
1106
+ * Returns the anti-resonance node for direct AudioParam access.
1107
+ *
1108
+ * Example usage:
1109
+ * vocalTract.antiResonanceNode.F.setValueAtTime(4500, ctx.currentTime);
1110
+ * vocalTract.antiResonanceNode.Q.setValueAtTime(3, ctx.currentTime);
1111
+ */
1112
+ get antiResonanceNode() {
1113
+ return this.antiResonance;
1114
+ }
1115
+ destroy() {
1116
+ this.formantBank.destroy(), this.antiResonance.destroy();
1117
+ }
1118
+ }
1119
+ class E {
1120
+ constructor(t, e, i) {
1121
+ r(this, "glottalFlowDerivative");
1122
+ r(this, "vocalTract");
1123
+ r(this, "outputGainNode");
1124
+ r(this, "in", null);
1125
+ // Source node has no input
1126
+ r(this, "out");
1127
+ this.glottalFlowDerivative = t, this.vocalTract = e, this.outputGainNode = i, this.out = i.out;
1128
+ }
1129
+ /**
1130
+ * Creates a new Voice synthesizer node.
1131
+ *
1132
+ * Sets up the complete source-filter synthesis pipeline.
1133
+ */
1134
+ static async create(t, e) {
1135
+ const [i, s, n] = await Promise.all([
1136
+ S.create(t, e),
1137
+ L.create(t, e),
1138
+ w.create(t, { gain: e.outputGain ?? 1 })
1139
+ ]);
1140
+ return i.out.connect(s.in), s.out.connect(n.in), new E(i, s, n);
1141
+ }
1142
+ /**
1143
+ * Updates all voice parameters.
1144
+ *
1145
+ * @throws Error if the formants array length doesn't match the original
1146
+ */
1147
+ update(t) {
1148
+ this.glottalFlowDerivative.update(t), this.vocalTract.update(t), t.outputGain !== void 0 && this.outputGainNode.update({ gain: t.outputGain });
1149
+ }
1150
+ /**
1151
+ * Starts the voice (begins voiced excitation).
1152
+ */
1153
+ start() {
1154
+ this.glottalFlowDerivative.start();
1155
+ }
1156
+ /**
1157
+ * Stops the voice (stops voiced excitation).
1158
+ * Note: Noise component continues based on An parameter.
1159
+ */
1160
+ stop() {
1161
+ this.glottalFlowDerivative.stop();
1162
+ }
1163
+ /**
1164
+ * Returns the number of formant resonators in the vocal tract.
1165
+ */
1166
+ get formantCount() {
1167
+ return this.vocalTract.formantCount;
1168
+ }
1169
+ /**
1170
+ * Returns the glottal flow derivative node for direct AudioParam access.
1171
+ *
1172
+ * Example usage:
1173
+ * voice.source.pulseTrainNode.f0.setValueAtTime(440, ctx.currentTime);
1174
+ * voice.source.glottalFormantNode.Ag.setTargetAtTime(0.5, ctx.currentTime, 0.1);
1175
+ */
1176
+ get source() {
1177
+ return this.glottalFlowDerivative;
1178
+ }
1179
+ /**
1180
+ * Returns the vocal tract node for direct AudioParam access.
1181
+ *
1182
+ * Example usage:
1183
+ * voice.tract.formants[0].F.linearRampToValueAtTime(800, ctx.currentTime + 0.1);
1184
+ * voice.tract.antiResonanceNode.F.setValueAtTime(4500, ctx.currentTime);
1185
+ */
1186
+ get tract() {
1187
+ return this.vocalTract;
1188
+ }
1189
+ /**
1190
+ * Returns the output gain node for direct AudioParam access.
1191
+ *
1192
+ * Example usage:
1193
+ * voice.outputGain.gain.linearRampToValueAtTime(0.5, ctx.currentTime + 1);
1194
+ */
1195
+ get outputGain() {
1196
+ return this.outputGainNode;
1197
+ }
1198
+ destroy() {
1199
+ this.glottalFlowDerivative.destroy(), this.vocalTract.destroy(), this.outputGainNode.destroy();
1200
+ }
1201
+ }
1202
+ const p = [0, 0.5, 1], m = [0, 1 / 3, 2 / 3, 1], F = [
1203
+ ["u", "y", "i"],
1204
+ // H = 0
1205
+ ["o", "œ", "e"],
1206
+ // H = 1/3
1207
+ ["ɔ", "ø", "ɛ"],
1208
+ // H = 2/3
1209
+ ["a", "a", "a"]
1210
+ // H = 1 (all map to /a/)
1211
+ ];
1212
+ function R(a) {
1213
+ return qt.find((t) => t.ipa === a);
1214
+ }
1215
+ function B(a, t, e) {
1216
+ return a + (t - a) * e;
1217
+ }
1218
+ function vt(a, t, e) {
1219
+ return {
1220
+ frequency: B(a.frequency, t.frequency, e),
1221
+ amplitude: B(a.amplitude, t.amplitude, e),
1222
+ bandwidth: B(a.bandwidth, t.bandwidth, e)
1223
+ };
1224
+ }
1225
+ function x(a, t, e) {
1226
+ return a.map((i, s) => vt(i, t[s], e));
1227
+ }
1228
+ function kt(a, t) {
1229
+ a = Math.max(0, Math.min(1, a)), t = Math.max(0, Math.min(1, t));
1230
+ let e = 0, i = 1;
1231
+ for (let u = 0; u < m.length - 1; u++)
1232
+ if (t >= m[u] && t <= m[u + 1]) {
1233
+ e = u, i = u + 1;
1234
+ break;
1235
+ }
1236
+ let s = 0, n = 1;
1237
+ for (let u = 0; u < p.length - 1; u++)
1238
+ if (a >= p[u] && a <= p[u + 1]) {
1239
+ s = u, n = u + 1;
1240
+ break;
1241
+ }
1242
+ const o = R(F[e][s]), c = R(F[e][n]), l = R(F[i][s]), A = R(F[i][n]), f = m[i] - m[e], b = p[n] - p[s], v = f > 0 ? (t - m[e]) / f : 0, d = b > 0 ? (a - p[s]) / b : 0, k = x(o.formants, c.formants, d), h = x(l.formants, A.formants, d);
1243
+ return x(k, h, v);
1244
+ }
1245
+ const qt = [
1246
+ {
1247
+ ipa: "i",
1248
+ v: 1,
1249
+ h: 0,
1250
+ formants: [
1251
+ { frequency: 215, amplitude: -10, bandwidth: 10 },
1252
+ { frequency: 1900, amplitude: -10, bandwidth: 18 },
1253
+ { frequency: 2630, amplitude: -8, bandwidth: 20 },
1254
+ { frequency: 3170, amplitude: -4, bandwidth: 30 },
1255
+ { frequency: 3710, amplitude: -15, bandwidth: 40 },
1256
+ { frequency: 6340, amplitude: -15, bandwidth: 150 }
1257
+ ]
1258
+ },
1259
+ {
1260
+ ipa: "e",
1261
+ v: 1,
1262
+ h: 1 / 3,
1263
+ formants: [
1264
+ { frequency: 410, amplitude: -1, bandwidth: 10 },
1265
+ { frequency: 2e3, amplitude: -3, bandwidth: 15 },
1266
+ { frequency: 2570, amplitude: -2, bandwidth: 20 },
1267
+ { frequency: 2980, amplitude: -2, bandwidth: 30 },
1268
+ { frequency: 3900, amplitude: -5, bandwidth: 40 },
1269
+ { frequency: 5960, amplitude: -15, bandwidth: 150 }
1270
+ ]
1271
+ },
1272
+ {
1273
+ ipa: "ɛ",
1274
+ v: 1,
1275
+ h: 2 / 3,
1276
+ formants: [
1277
+ { frequency: 590, amplitude: 0, bandwidth: 10 },
1278
+ { frequency: 1700, amplitude: -4, bandwidth: 15 },
1279
+ { frequency: 2540, amplitude: -5, bandwidth: 30 },
1280
+ { frequency: 2800, amplitude: -12, bandwidth: 50 },
1281
+ { frequency: 3900, amplitude: -24, bandwidth: 40 },
1282
+ { frequency: 5600, amplitude: -15, bandwidth: 150 }
1283
+ ]
1284
+ },
1285
+ {
1286
+ ipa: "y",
1287
+ v: 0.5,
1288
+ h: 0,
1289
+ formants: [
1290
+ { frequency: 250, amplitude: -12, bandwidth: 10 },
1291
+ { frequency: 1750, amplitude: -9, bandwidth: 10 },
1292
+ { frequency: 2160, amplitude: -14, bandwidth: 20 },
1293
+ { frequency: 3060, amplitude: -11, bandwidth: 30 },
1294
+ { frequency: 3900, amplitude: -11, bandwidth: 40 },
1295
+ { frequency: 6120, amplitude: -15, bandwidth: 150 }
1296
+ ]
1297
+ },
1298
+ {
1299
+ ipa: "œ",
1300
+ v: 0.5,
1301
+ h: 1 / 3,
1302
+ formants: [
1303
+ { frequency: 350, amplitude: -6, bandwidth: 10 },
1304
+ { frequency: 1350, amplitude: -3, bandwidth: 10 },
1305
+ { frequency: 2250, amplitude: -8, bandwidth: 20 },
1306
+ { frequency: 3170, amplitude: -8, bandwidth: 30 },
1307
+ { frequency: 3900, amplitude: -10, bandwidth: 40 },
1308
+ { frequency: 6340, amplitude: -15, bandwidth: 150 }
1309
+ ]
1310
+ },
1311
+ {
1312
+ ipa: "ø",
1313
+ v: 0.5,
1314
+ h: 2 / 3,
1315
+ formants: [
1316
+ { frequency: 620, amplitude: -3, bandwidth: 10 },
1317
+ { frequency: 1300, amplitude: -3, bandwidth: 10 },
1318
+ { frequency: 2520, amplitude: -3, bandwidth: 20 },
1319
+ { frequency: 3310, amplitude: -7, bandwidth: 30 },
1320
+ { frequency: 3900, amplitude: -14, bandwidth: 40 },
1321
+ { frequency: 6620, amplitude: -15, bandwidth: 150 }
1322
+ ]
1323
+ },
1324
+ {
1325
+ ipa: "u",
1326
+ v: 0,
1327
+ h: 0,
1328
+ formants: [
1329
+ { frequency: 290, amplitude: -6, bandwidth: 10 },
1330
+ { frequency: 750, amplitude: -8, bandwidth: 10 },
1331
+ { frequency: 2300, amplitude: -13, bandwidth: 20 },
1332
+ { frequency: 3080, amplitude: -8, bandwidth: 30 },
1333
+ { frequency: 3900, amplitude: -9, bandwidth: 40 },
1334
+ { frequency: 6160, amplitude: -15, bandwidth: 150 }
1335
+ ]
1336
+ },
1337
+ {
1338
+ ipa: "o",
1339
+ v: 0,
1340
+ h: 1 / 3,
1341
+ formants: [
1342
+ { frequency: 440, amplitude: -6, bandwidth: 10 },
1343
+ { frequency: 750, amplitude: -1, bandwidth: 12 },
1344
+ { frequency: 2160, amplitude: -10, bandwidth: 20 },
1345
+ { frequency: 2860, amplitude: -6, bandwidth: 30 },
1346
+ { frequency: 3900, amplitude: -28, bandwidth: 40 },
1347
+ { frequency: 5720, amplitude: -15, bandwidth: 150 }
1348
+ ]
1349
+ },
1350
+ {
1351
+ ipa: "ɔ",
1352
+ v: 0,
1353
+ h: 2 / 3,
1354
+ formants: [
1355
+ { frequency: 610, amplitude: -3, bandwidth: 10 },
1356
+ { frequency: 950, amplitude: 0, bandwidth: 12 },
1357
+ { frequency: 2510, amplitude: -12, bandwidth: 20 },
1358
+ { frequency: 2830, amplitude: -15, bandwidth: 30 },
1359
+ { frequency: 3900, amplitude: -20, bandwidth: 40 },
1360
+ { frequency: 5660, amplitude: -15, bandwidth: 150 }
1361
+ ]
1362
+ },
1363
+ {
1364
+ ipa: "a",
1365
+ v: 0.5,
1366
+ h: 1,
1367
+ formants: [
1368
+ { frequency: 700, amplitude: 0, bandwidth: 13 },
1369
+ { frequency: 1200, amplitude: 0, bandwidth: 13 },
1370
+ { frequency: 2500, amplitude: -5, bandwidth: 40 },
1371
+ { frequency: 2800, amplitude: -7, bandwidth: 60 },
1372
+ { frequency: 3600, amplitude: -24, bandwidth: 40 },
1373
+ { frequency: 5600, amplitude: -15, bandwidth: 150 }
1374
+ ]
1375
+ }
1376
+ ], Mt = {
1377
+ harmonicCoincidenceAttenuation: !0,
1378
+ formantFrequencyScaling: !0,
1379
+ f1Tuning: !0,
1380
+ f2Tuning: !0,
1381
+ antiResonanceScaling: !0
1382
+ }, y = 0.2, H = 0.2, _ = 4700, Bt = 2.5;
1383
+ function xt(a) {
1384
+ return 440 * Math.pow(2, (a - 69) / 12);
1385
+ }
1386
+ function Vt(a) {
1387
+ return Math.pow(10, a / 20);
1388
+ }
1389
+ function Dt(a, t, e) {
1390
+ const i = e === 1 ? 0.903 - 0.426 * t : 0.978 - 0.279 * t;
1391
+ return a <= 0.5 ? Math.pow(10, -2 * (1 - i) * a) : Math.pow(10, 2 * i * (1 - a) - 1);
1392
+ }
1393
+ function Nt(a, t) {
1394
+ const e = t === 1 ? 0.66 : 0.55;
1395
+ return a <= 0.5 ? 0.5 + 2 * (e - 0.5) * a : 0.9 - 2 * (0.9 - e) * (1 - a);
1396
+ }
1397
+ function Pt(a, t, e) {
1398
+ const i = Math.PI * (1 - e), s = Math.tan(Math.max(0.01, Math.min(Math.PI - 0.01, i)));
1399
+ return a / (t * Math.abs(s));
1400
+ }
1401
+ function St(a, t) {
1402
+ return t === 1 ? {
1403
+ Tl1: 27 - 21 * a,
1404
+ Tl2: 11 - 11 * a
1405
+ } : {
1406
+ Tl1: 45 - 36 * a,
1407
+ Tl2: 20 - 18.5 * a
1408
+ };
1409
+ }
1410
+ function Ct(a, t) {
1411
+ return a <= y ? 0 : ((1 - H) * ((a - y) / (1 - y)) + H) / t;
1412
+ }
1413
+ function jt(a, t) {
1414
+ return t > y ? a : 1.5 * t * a;
1415
+ }
1416
+ function Gt(a) {
1417
+ return 1.7 * a + 0.5;
1418
+ }
1419
+ function Lt(a) {
1420
+ return 125e-6 * a + 0.975;
1421
+ }
1422
+ function Ut(a, t, e) {
1423
+ if (e >= 3)
1424
+ return 0;
1425
+ const i = 15 + 85 * ((a - 50) / 1450), s = [10, 15, 25][e], o = Math.round(t / a) * a, c = Math.abs(o - t);
1426
+ return c < i ? (1 - c / i) * s : 0;
1427
+ }
1428
+ function Ot(a, t = {}) {
1429
+ const e = { ...Mt, ...t }, {
1430
+ pitch: i,
1431
+ pitchOffset: s,
1432
+ vocalEffort: n,
1433
+ vowelHeight: o,
1434
+ vowelBackness: c,
1435
+ tenseness: l,
1436
+ breathiness: A,
1437
+ roughness: f,
1438
+ vocalTractSize: b,
1439
+ isFalsetto: v
1440
+ } = a, d = v ? 2 : 1, k = s + 35 * i, h = xt(k), u = Dt(l, n, d), J = Nt(l, d), K = h / (2 * u), X = Pt(h, u, J), Y = Ct(n, u), { Tl1: Z, Tl2: tt } = St(n, d), et = jt(A, n), at = f * 0.3, it = f * 1, U = Gt(b), st = Lt(h), rt = kt(c, o).map((g, q) => {
1441
+ const M = e.formantFrequencyScaling ? st * U : 1;
1442
+ let T = M * g.frequency;
1443
+ if (q === 0 && e.f1Tuning) {
1444
+ const ct = M * g.frequency + 140 / (1 - y) * n - 70;
1445
+ T = Math.max(h + 50, ct);
1446
+ }
1447
+ q === 1 && e.f2Tuning && (T = Math.max(2 * h + 50, M * g.frequency));
1448
+ const ot = e.harmonicCoincidenceAttenuation ? Ut(h, T, q) : 0, ut = Vt(g.amplitude - ot);
1449
+ return {
1450
+ F: T,
1451
+ B: g.bandwidth,
1452
+ A: ut
1453
+ };
1454
+ }), nt = e.antiResonanceScaling ? _ * U : _;
1455
+ return {
1456
+ f0: h,
1457
+ Fg: K,
1458
+ Bg: X,
1459
+ Ag: Y,
1460
+ Tl1: Z,
1461
+ Tl2: tt,
1462
+ An: et,
1463
+ jitterDepth: at,
1464
+ shimmerDepth: it,
1465
+ formants: rt,
1466
+ F_BQ: nt,
1467
+ Q_BQ: Bt
1468
+ };
1469
+ }
1470
+ export {
1471
+ G as AntiResonance,
1472
+ j as FormantBank,
1473
+ C as FormantResonator,
1474
+ w as Gain,
1475
+ S as GlottalFlowDerivative,
1476
+ D as GlottalFormant,
1477
+ P as NoiseSource,
1478
+ V as PulseTrain,
1479
+ N as SpectralTilt,
1480
+ L as VocalTract,
1481
+ E as Voice,
1482
+ Ot as generateSynthParams,
1483
+ kt as interpolateFormants,
1484
+ qt as vowels
1485
+ };
1486
+ //# sourceMappingURL=index.js.map