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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/nodes/gain.ts","../src/nodes/pulse-train.ts","../src/nodes/glottal-formant.ts","../src/nodes/spectral-tilt.ts","../src/nodes/noise-source.ts","../src/nodes/glottal-flow-derivative.ts","../src/nodes/formant-resonator.ts","../src/nodes/formant-bank.ts","../src/nodes/anti-resonance.ts","../src/nodes/vocal-tract.ts","../src/nodes/voice.ts","../src/parameters/vowels.ts","../src/parameters/index.ts"],"sourcesContent":["import type { Node } from \"./types\";\n\nexport type GainParams = {\n gain: number;\n}\n\nexport class Gain implements Node<GainParams> {\n private ctx: AudioContext;\n private gainNode: GainNode;\n public in: AudioNode;\n public out: AudioNode;\n\n private constructor(ctx: AudioContext, gainNode: GainNode) {\n this.ctx = ctx;\n this.gainNode = gainNode;\n this.in = gainNode;\n this.out = gainNode;\n }\n\n static async create(ctx: AudioContext, params: GainParams): Promise<Gain> {\n const gainNode = ctx.createGain();\n gainNode.gain.setValueAtTime(params.gain, ctx.currentTime);\n return new Gain(ctx, gainNode);\n }\n\n update(params: GainParams) {\n this.gainNode.gain.setTargetAtTime(params.gain, this.ctx.currentTime, 0.02);\n }\n\n destroy() {\n this.gainNode.disconnect();\n }\n\n get gain(): AudioParam {\n return this.gainNode.gain;\n }\n}\n","import type { Node } from \"./types\";\nimport { Gain } from \"./gain\";\n\nexport type PulseTrainParams = {\n /** Fundamental frequency in Hz (derived from P, P₀) */\n f0: number;\n /** Jitter depth: maximum f₀ perturbation as a fraction (0-0.3 for ±30%) */\n jitterDepth: number;\n /** Shimmer depth: maximum amplitude perturbation as a fraction (0-1 for ±100%) */\n shimmerDepth: number;\n};\n\n/**\n * Pulse Train AudioWorklet Processor code.\n *\n * Generates a band-limited impulse train at the fundamental frequency f₀,\n * with per-period jitter (frequency perturbation) and shimmer (amplitude\n * perturbation) for roughness simulation.\n *\n * The processor tracks phase internally and generates new random perturbation\n * values at each period boundary, ensuring jitter and shimmer are synchronized\n * to the glottal cycle.\n *\n * Band-limiting is achieved by summing harmonics up to the Nyquist frequency,\n * which avoids aliasing artifacts.\n *\n * Paper reference: Section 3.1 (Dirac comb), Section 4.1.2 (jitter),\n * Section 4.2.4 (shimmer)\n */\nconst processorCode = `\nclass PulseTrainProcessor extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n { name: \"f0\", defaultValue: 220, minValue: 20, maxValue: 20000, automationRate: \"a-rate\" },\n { name: \"jitterDepth\", defaultValue: 0, minValue: 0, maxValue: 0.3, automationRate: \"k-rate\" },\n { name: \"shimmerDepth\", defaultValue: 0, minValue: 0, maxValue: 1, automationRate: \"k-rate\" }\n ];\n }\n\n constructor() {\n super();\n // Phase accumulator (0 to 1)\n this.phase = 0;\n\n // Current perturbation values (updated each period)\n this.currentJitter = 1; // Multiplier for f0 (1 = no jitter)\n this.currentShimmer = 1; // Multiplier for amplitude (1 = no shimmer)\n\n // Box-Muller spare for Gaussian random\n this.hasSpare = false;\n this.spare = 0;\n\n // Cached harmonic count for band-limiting\n this.harmonicCount = 0;\n this.lastF0ForHarmonics = 220;\n this.updateHarmonicCount(220);\n }\n\n /**\n * Update the number of harmonics to sum for band-limited impulse.\n * We include harmonics up to Nyquist to avoid aliasing.\n */\n updateHarmonicCount(f0) {\n const nyquist = sampleRate / 2;\n this.harmonicCount = Math.max(1, Math.floor(nyquist / f0));\n this.lastF0ForHarmonics = f0;\n }\n\n /**\n * Generate a Gaussian-distributed random number using Box-Muller transform.\n * Returns values with mean 0 and standard deviation 1.\n */\n gaussianRandom() {\n if (this.hasSpare) {\n this.hasSpare = false;\n return this.spare;\n }\n\n let u, v, s;\n do {\n u = Math.random() * 2 - 1;\n v = Math.random() * 2 - 1;\n s = u * u + v * v;\n } while (s >= 1 || s === 0);\n\n const mul = Math.sqrt(-2 * Math.log(s) / s);\n this.spare = v * mul;\n this.hasSpare = true;\n return u * mul;\n }\n\n /**\n * Generate new perturbation values at a period boundary.\n * Jitter affects the next period's frequency.\n * Shimmer affects the current pulse's amplitude.\n */\n generatePerturbations(jitterDepth, shimmerDepth) {\n // Jitter: Gaussian perturbation of f0, clipped to ±jitterDepth\n // Using Gaussian makes small perturbations more likely than large ones\n const jitterRaw = this.gaussianRandom() * jitterDepth * 0.5;\n const jitterClamped = Math.max(-jitterDepth, Math.min(jitterDepth, jitterRaw));\n this.currentJitter = 1 + jitterClamped;\n\n // Shimmer: Gaussian perturbation of amplitude, clipped to [0, 2]\n // At shimmerDepth=1, amplitude can vary from 0 to 2 (±100%)\n const shimmerRaw = this.gaussianRandom() * shimmerDepth * 0.5;\n const shimmerClamped = Math.max(-shimmerDepth, Math.min(shimmerDepth, shimmerRaw));\n this.currentShimmer = 1 + shimmerClamped;\n\n // Clamp shimmer to non-negative (amplitude can't be negative)\n this.currentShimmer = Math.max(0, this.currentShimmer);\n }\n\n /**\n * Compute a band-limited impulse using additive synthesis.\n * Sums cosines at all harmonic frequencies up to Nyquist.\n *\n * The impulse is normalized so that its peak amplitude is approximately 1.\n */\n computeBandLimitedImpulse(phase) {\n // Sum all harmonics: Σ cos(2π * k * phase) for k = 1 to harmonicCount\n // This produces a Dirac comb approximation\n let sum = 0;\n const twoPiPhase = 2 * Math.PI * phase;\n for (let k = 1; k <= this.harmonicCount; k++) {\n sum += Math.cos(k * twoPiPhase);\n }\n // Normalize by harmonic count to keep amplitude reasonable\n return sum / this.harmonicCount;\n }\n\n process(inputs, outputs, parameters) {\n const output = outputs[0]?.[0];\n if (!output) {\n return true;\n }\n\n const dt = 1 / sampleRate;\n // k-rate parameters (single value per block)\n const jitterDepth = parameters.jitterDepth[0];\n const shimmerDepth = parameters.shimmerDepth[0];\n\n for (let i = 0; i < output.length; i++) {\n // a-rate: per-sample value for f0\n const f0 = parameters.f0.length > 1 ? parameters.f0[i] : parameters.f0[0];\n\n // Compute effective f0 with current jitter\n const f0Effective = f0 * this.currentJitter;\n\n // Advance phase\n this.phase += f0Effective * dt;\n\n // Check for period boundary (phase wrapped)\n if (this.phase >= 1) {\n this.phase -= 1;\n // Generate new perturbations for the next period\n this.generatePerturbations(jitterDepth, shimmerDepth);\n // Update harmonic count if f0 changed significantly (>10%)\n if (Math.abs(f0 - this.lastF0ForHarmonics) / this.lastF0ForHarmonics > 0.1) {\n this.updateHarmonicCount(f0);\n }\n }\n\n // Generate band-limited impulse and apply shimmer\n output[i] = this.currentShimmer * this.computeBandLimitedImpulse(this.phase);\n }\n\n return true;\n }\n}\n\nregisterProcessor('pulse-train-processor', PulseTrainProcessor);\n`;\n\n// Track whether the worklet module has been registered\nlet moduleRegistered = false;\n\n/**\n * Registers the pulse train worklet module with the AudioContext.\n * Only registers once per application lifetime.\n */\nasync function ensureModuleRegistered(ctx: AudioContext): Promise<void> {\n if (moduleRegistered) {\n return;\n }\n\n const blob = new Blob([processorCode], { type: \"application/javascript\" });\n const url = URL.createObjectURL(blob);\n\n try {\n await ctx.audioWorklet.addModule(url);\n moduleRegistered = true;\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\n/**\n * Pulse Train (Voiced Excitation Source)\n *\n * Generates a periodic impulse train at the fundamental frequency f₀,\n * with integrated jitter (f₀ perturbation) and shimmer (amplitude perturbation)\n * for roughness simulation.\n *\n * In the spectral domain, the unperturbed output is a Dirac comb: Σδ(f − n·f₀),\n * representing energy at each harmonic frequency. Jitter spreads energy around\n * harmonics, and shimmer modulates the overall amplitude cycle-to-cycle.\n *\n * The impulse train is the input to the glottal formant filter, which shapes\n * each impulse into the characteristic glottal pulse waveform.\n *\n * Paper reference: Section 3.1 (spectral equation showing the Dirac comb term),\n * Section 4.1.2 (jitter), Section 4.2.4 (shimmer)\n *\n * Input parameters:\n * - f0: Fundamental frequency in Hz\n * - jitterDepth: Maximum f₀ perturbation (0-0.3 for up to ±30%)\n * - shimmerDepth: Maximum amplitude perturbation (0-1 for up to ±100%)\n */\nexport class PulseTrain implements Node<PulseTrainParams> {\n private ctx: AudioContext;\n private workletNode: AudioWorkletNode;\n private gain: Gain;\n\n public in: null;\n public out: AudioNode;\n\n private constructor(ctx: AudioContext, workletNode: AudioWorkletNode, gain: Gain) {\n this.ctx = ctx;\n this.workletNode = workletNode;\n this.gain = gain;\n this.out = gain.out;\n this.in = null;\n }\n\n /** Fundamental frequency AudioParam (a-rate, 20-20000 Hz) */\n get f0(): AudioParam {\n return this.workletNode.parameters.get(\"f0\")!;\n }\n\n /** Jitter depth AudioParam (k-rate, 0-0.3) */\n get jitterDepth(): AudioParam {\n return this.workletNode.parameters.get(\"jitterDepth\")!;\n }\n\n /** Shimmer depth AudioParam (k-rate, 0-1) */\n get shimmerDepth(): AudioParam {\n return this.workletNode.parameters.get(\"shimmerDepth\")!;\n }\n\n static async create(ctx: AudioContext, params: PulseTrainParams): Promise<PulseTrain> {\n await ensureModuleRegistered(ctx);\n\n const workletNode = new AudioWorkletNode(ctx, \"pulse-train-processor\");\n const gain = await Gain.create(ctx, { gain: 0 });\n\n workletNode.connect(gain.in);\n\n const pulseTrain = new PulseTrain(ctx, workletNode, gain);\n pulseTrain.update(params);\n\n return pulseTrain;\n }\n\n update(params: PulseTrainParams) {\n this.f0.setTargetAtTime(params.f0, this.ctx.currentTime, 0.02);\n this.jitterDepth.setTargetAtTime(params.jitterDepth, this.ctx.currentTime, 0.02);\n this.shimmerDepth.setTargetAtTime(params.shimmerDepth, this.ctx.currentTime, 0.02);\n }\n\n destroy() {\n this.workletNode.disconnect();\n this.gain.destroy();\n }\n\n start() {\n this.gain.update({ gain: 1 });\n }\n\n stop() {\n this.gain.update({ gain: 0 });\n }\n}\n","import type { Node } from \"./types\";\n\nexport type GlottalFormantParams = {\n /** Glottal formant centre frequency in Hz, computed as f0 / (2 * Oq) */\n Fg: number;\n /** Glottal formant bandwidth in Hz, computed from f0, Oq, and αm */\n Bg: number;\n /** Source amplitude, derived from E, Oq, and R (shimmer) */\n Ag: number;\n};\n\n/**\n * Glottal Formant AudioWorklet Processor code.\n *\n * Implements the transfer function from Section 3.2.1:\n * GF(z) = -Ag * z^{-1} * (1 - z^{-1}) / (1 - 2R*cos(θ)*z^{-1} + R²*z^{-2})\n *\n * Where:\n * R = e^(-π * Bg * Ts) (pole radius, Ts = 1/sampleRate)\n * θ = 2π * Fg * Ts (pole angle)\n */\nconst processorCode = `\nclass GlottalFormantProcessor extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n { name: \"Fg\", defaultValue: 110, minValue: 20, maxValue: 2000, automationRate: \"a-rate\" },\n { name: \"Bg\", defaultValue: 50, minValue: 10, maxValue: 500, automationRate: \"a-rate\" },\n { name: \"Ag\", defaultValue: 1, minValue: 0, maxValue: 10, automationRate: \"a-rate\" }\n ];\n }\n\n constructor() {\n super();\n // Filter state (delay line)\n this.x1 = 0;\n this.x2 = 0;\n this.y1 = 0;\n this.y2 = 0;\n\n // Cached coefficient values to avoid recomputing when unchanged\n this.lastFg = -1;\n this.lastBg = -1;\n this.R = 0;\n this.cosTheta = 0;\n this.a1 = 0;\n this.a2 = 0;\n }\n\n process(inputs, outputs, parameters) {\n const input = inputs[0]?.[0];\n const output = outputs[0]?.[0];\n\n if (!input || !output) {\n return true;\n }\n\n const Ts = 1 / sampleRate;\n // k-rate parameters (single value per block)\n const Fg = parameters.Fg[0];\n const Bg = parameters.Bg[0];\n\n // Recompute filter coefficients if Fg or Bg changed\n if (Fg !== this.lastFg || Bg !== this.lastBg) {\n this.R = Math.exp(-Math.PI * Bg * Ts);\n const theta = 2 * Math.PI * Fg * Ts;\n this.cosTheta = Math.cos(theta);\n this.a1 = -2 * this.R * this.cosTheta;\n this.a2 = this.R * this.R;\n this.lastFg = Fg;\n this.lastBg = Bg;\n }\n\n for (let i = 0; i < input.length; i++) {\n // a-rate: per-sample value for Ag\n const Ag = parameters.Ag.length > 1 ? parameters.Ag[i] : parameters.Ag[0];\n\n const x0 = input[i];\n\n // Numerator coefficients (feedforward) depend on Ag\n // From: -Ag * z^{-1} * (1 - z^{-1}) = -Ag*z^{-1} + Ag*z^{-2}\n const b1 = -Ag;\n const b2 = Ag;\n\n // Direct Form I biquad (b0 is always 0)\n const y0 = b1 * this.x1 + b2 * this.x2\n - this.a1 * this.y1 - this.a2 * this.y2;\n\n // Update delay line\n this.x2 = this.x1;\n this.x1 = x0;\n this.y2 = this.y1;\n this.y1 = y0;\n\n output[i] = y0;\n }\n\n return true;\n }\n}\n\nregisterProcessor('glottal-formant-processor', GlottalFormantProcessor);\n`;\n\n// Track whether the worklet module has been registered\nlet moduleRegistered = false;\n\n/**\n * Registers the glottal formant worklet module with the AudioContext.\n * Only registers once per application lifetime.\n */\nasync function ensureModuleRegistered(ctx: AudioContext): Promise<void> {\n if (moduleRegistered) {\n return;\n }\n\n const blob = new Blob([processorCode], { type: \"application/javascript\" });\n const url = URL.createObjectURL(blob);\n\n try {\n await ctx.audioWorklet.addModule(url);\n moduleRegistered = true;\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\n/**\n * Glottal Formant (GF)\n *\n * The glottal formant represents the main spectral peak of the voice source,\n * corresponding to the resonance characteristics of the glottal pulse. It is\n * implemented as a 2-pole 1-zero digital resonant filter in series with a\n * 1-zero differentiation filter.\n *\n * The transfer function (Section 3.2.1) is:\n *\n * GF(z) = -Ag * z^{-1} * (1 - z^{-1}) / (1 - 2R*cos(θ)*z^{-1} + R²*z^{-2})\n *\n * Where:\n * R = e^(-π * Bg * Ts) (pole radius, Ts = sampling period)\n * θ = 2π * Fg * Ts (pole angle)\n *\n * The filter shapes the periodic impulse train (at frequency f0) to produce\n * the characteristic spectrum of the glottal flow derivative.\n *\n * Paper reference: Section 3.2.1 (filter structure), Section 4.2.2 (parameter mapping)\n *\n * Input parameters:\n * - Fg (glottal formant centre frequency) — computed as f0 / (2·Oq)\n * - Bg (glottal formant bandwidth) — computed from f0, Oq, and αm\n * - Ag (source amplitude) — derived from E, Oq, and R (shimmer)\n *\n * The intermediate parameters Oq (open quotient) and αm (asymmetry coefficient)\n * are computed from T (tenseness), E (vocal effort), and M (laryngeal mechanism).\n */\nexport class GlottalFormant implements Node<GlottalFormantParams> {\n private ctx: AudioContext;\n private workletNode: AudioWorkletNode;\n public in: AudioNode;\n public out: AudioNode;\n\n private constructor(ctx: AudioContext, workletNode: AudioWorkletNode) {\n this.ctx = ctx;\n this.workletNode = workletNode;\n this.in = workletNode;\n this.out = workletNode;\n }\n\n /** Glottal formant centre frequency AudioParam (a-rate, 20-2000 Hz) */\n get Fg(): AudioParam {\n return this.workletNode.parameters.get(\"Fg\")!;\n }\n\n /** Glottal formant bandwidth AudioParam (a-rate, 10-500 Hz) */\n get Bg(): AudioParam {\n return this.workletNode.parameters.get(\"Bg\")!;\n }\n\n /** Source amplitude AudioParam (a-rate, 0-10) */\n get Ag(): AudioParam {\n return this.workletNode.parameters.get(\"Ag\")!;\n }\n\n /**\n * Creates a new GlottalFormant node.\n *\n * The AudioWorklet module is registered automatically on first use.\n */\n static async create(\n ctx: AudioContext,\n params: GlottalFormantParams\n ): Promise<GlottalFormant> {\n await ensureModuleRegistered(ctx);\n\n const workletNode = new AudioWorkletNode(ctx, \"glottal-formant-processor\");\n const node = new GlottalFormant(ctx, workletNode);\n node.update(params);\n\n return node;\n }\n\n /**\n * Updates the glottal formant parameters.\n * Sets AudioParams via setTargetAtTime for smooth transitions.\n */\n update(params: GlottalFormantParams): void {\n this.Fg.setTargetAtTime(params.Fg, this.ctx.currentTime, 0.02);\n this.Bg.setTargetAtTime(params.Bg, this.ctx.currentTime, 0.02);\n this.Ag.setTargetAtTime(params.Ag, this.ctx.currentTime, 0.02);\n }\n\n destroy(): void {\n this.workletNode.disconnect();\n }\n}\n","import type { Node } from \"./types\";\n\nexport type SpectralTiltParams = {\n /** First stage attenuation in dB at 3000 Hz, derived from E and M */\n Tl1: number;\n /** Second stage attenuation in dB at 3000 Hz, derived from E and M */\n Tl2: number;\n};\n\n/**\n * Spectral Tilt AudioWorklet Processor code.\n *\n * Implements the transfer function from Section 3.2.2:\n * ST(z) = ST₁(z) × ST₂(z)\n *\n * Where each stage is:\n * STᵢ(z) = (1 - aᵢ) / (1 - aᵢ·z⁻¹)\n *\n * And:\n * aᵢ = νᵢ - √(νᵢ² - 1)\n * νᵢ = 1 + (1 - cos(2π·3000·Ts)) / (10^(Tlᵢ/10) - 1)\n *\n * This gives unity gain at DC and specified attenuation (Tlᵢ dB) at 3000 Hz.\n */\nconst processorCode = `\nclass SpectralTiltProcessor extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n { name: \"Tl1\", defaultValue: 0, minValue: 0, maxValue: 50, automationRate: \"k-rate\" },\n { name: \"Tl2\", defaultValue: 0, minValue: 0, maxValue: 30, automationRate: \"k-rate\" }\n ];\n }\n\n constructor() {\n super();\n // Filter coefficients for two stages\n this.a1 = 0; // First stage pole\n this.a2 = 0; // Second stage pole\n this.g1 = 1; // First stage gain (1 - a1)\n this.g2 = 1; // Second stage gain (1 - a2)\n\n // Filter state (one delay element per stage)\n this.y1_prev = 0; // Previous output of stage 1\n this.y2_prev = 0; // Previous output of stage 2\n\n // Cache last parameter values for change detection\n this.lastTl1 = -1;\n this.lastTl2 = -1;\n\n // Pre-compute cosOmega since it's constant for a given sample rate\n const Ts = 1 / sampleRate;\n this.cosOmega = Math.cos(2 * Math.PI * 3000 * Ts);\n }\n\n /**\n * Compute the pole coefficient for a single stage.\n * Returns [a, g] where a is the pole and g = 1 - a is the gain.\n */\n computeStageCoefficients(Tl) {\n // Bypass if attenuation is zero or negative\n if (Tl <= 0) {\n return [0, 1];\n }\n\n // Power ratio at 3000 Hz (10^(Tl/10) is the attenuation factor)\n const attenuationRatio = Math.pow(10, Tl / 10);\n\n // Compute ν from Section 3.2.2\n // νᵢ = 1 + (1 - cos(ω)) / (10^(Tlᵢ/10) - 1)\n const nu = 1 + (1 - this.cosOmega) / (attenuationRatio - 1);\n\n // Compute pole coefficient: a = ν - √(ν² - 1)\n // This ensures 0 < a < 1 for stability\n const a = nu - Math.sqrt(nu * nu - 1);\n const g = 1 - a;\n\n return [a, g];\n }\n\n process(inputs, outputs, parameters) {\n const input = inputs[0]?.[0];\n const output = outputs[0]?.[0];\n\n if (!input || !output) {\n return true;\n }\n\n // k-rate parameters (single value per block)\n const Tl1 = parameters.Tl1[0];\n const Tl2 = parameters.Tl2[0];\n\n // Recompute coefficients if parameters changed\n if (Tl1 !== this.lastTl1) {\n [this.a1, this.g1] = this.computeStageCoefficients(Tl1);\n this.lastTl1 = Tl1;\n }\n if (Tl2 !== this.lastTl2) {\n [this.a2, this.g2] = this.computeStageCoefficients(Tl2);\n this.lastTl2 = Tl2;\n }\n\n for (let i = 0; i < input.length; i++) {\n // Stage 1: y1[n] = g1 * x[n] + a1 * y1[n-1]\n const y1 = this.g1 * input[i] + this.a1 * this.y1_prev;\n this.y1_prev = y1;\n\n // Stage 2: y2[n] = g2 * y1[n] + a2 * y2[n-1]\n const y2 = this.g2 * y1 + this.a2 * this.y2_prev;\n this.y2_prev = y2;\n\n output[i] = y2;\n }\n\n return true;\n }\n}\n\nregisterProcessor('spectral-tilt-processor', SpectralTiltProcessor);\n`;\n\n// Track whether the worklet module has been registered\nlet moduleRegistered = false;\n\n/**\n * Registers the spectral tilt worklet module with the AudioContext.\n * Only registers once per application lifetime.\n */\nasync function ensureModuleRegistered(ctx: AudioContext): Promise<void> {\n if (moduleRegistered) {\n return;\n }\n\n const blob = new Blob([processorCode], { type: \"application/javascript\" });\n const url = URL.createObjectURL(blob);\n\n try {\n await ctx.audioWorklet.addModule(url);\n moduleRegistered = true;\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\n/**\n * Spectral Tilt (ST)\n *\n * The spectral tilt filter models the high-frequency roll-off of the glottal\n * source spectrum. Louder, more pressed phonation has less spectral tilt\n * (brighter sound), while softer or breathier phonation has more tilt\n * (darker sound).\n *\n * It is implemented as a cascade of two 1-pole 1-zero low-pass filters, each\n * providing attenuation specified in dB at 3000 Hz.\n *\n * The transfer function (Section 3.2.2) is:\n *\n * ST(z) = ST₁(z) × ST₂(z)\n *\n * Where each stage is:\n * STᵢ(z) = (1 - aᵢ) / (1 - aᵢ·z⁻¹)\n *\n * With:\n * aᵢ = νᵢ - √(νᵢ² - 1)\n * νᵢ = 1 + (1 - cos(2π·3000·Ts)) / (10^(Tlᵢ/10) - 1)\n *\n * Paper reference: Section 3.2.2 (filter structure), Section 4.2.3 (parameter mapping)\n *\n * Input parameters:\n * - Tl₁ (first tilt stage attenuation in dB at 3 kHz) — derived from E and M\n * - Tl₂ (second tilt stage attenuation in dB at 3 kHz) — derived from E and M\n *\n * Parameter mapping from Section 4.2.3:\n * For M=1 (chest voice):\n * Tl₁ = 27 - 21·Ep dB\n * Tl₂ = 11 - 11·Ep dB\n * For M=2 (falsetto):\n * Tl₁ = 45 - 36·Ep dB\n * Tl₂ = 20 - 18.5·Ep dB\n *\n * Where Ep is the perturbed vocal effort (E with heartbeat and slow perturbations).\n */\nexport class SpectralTilt implements Node<SpectralTiltParams> {\n private ctx: AudioContext;\n private workletNode: AudioWorkletNode;\n public in: AudioNode;\n public out: AudioNode;\n\n private constructor(ctx: AudioContext, workletNode: AudioWorkletNode) {\n this.ctx = ctx;\n this.workletNode = workletNode;\n this.in = workletNode;\n this.out = workletNode;\n }\n\n /** First stage spectral tilt attenuation AudioParam (k-rate, 0-50 dB at 3kHz) */\n get Tl1(): AudioParam {\n return this.workletNode.parameters.get(\"Tl1\")!;\n }\n\n /** Second stage spectral tilt attenuation AudioParam (k-rate, 0-30 dB at 3kHz) */\n get Tl2(): AudioParam {\n return this.workletNode.parameters.get(\"Tl2\")!;\n }\n\n /**\n * Creates a new SpectralTilt node.\n *\n * The AudioWorklet module is registered automatically on first use.\n */\n static async create(\n ctx: AudioContext,\n params: SpectralTiltParams\n ): Promise<SpectralTilt> {\n await ensureModuleRegistered(ctx);\n\n const workletNode = new AudioWorkletNode(ctx, \"spectral-tilt-processor\");\n const node = new SpectralTilt(ctx, workletNode);\n node.update(params);\n\n return node;\n }\n\n /**\n * Updates the spectral tilt parameters.\n * Sets AudioParams via setTargetAtTime for smooth transitions.\n */\n update(params: SpectralTiltParams): void {\n this.Tl1.setTargetAtTime(params.Tl1, this.ctx.currentTime, 0.02);\n this.Tl2.setTargetAtTime(params.Tl2, this.ctx.currentTime, 0.02);\n }\n\n destroy(): void {\n this.workletNode.disconnect();\n }\n}\n","import type { Node } from \"./types\";\n\nexport type NoiseSourceParams = {\n /** Noise amplitude, derived directly from B (breathiness) */\n An: number;\n};\n\n/**\n * Noise Source AudioWorklet Processor code.\n *\n * Generates Gaussian white noise using the Box-Muller transform,\n * scaled by an amplitude parameter.\n */\nconst processorCode = `\nclass NoiseSourceProcessor extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n { name: \"An\", defaultValue: 0, minValue: 0, maxValue: 1, automationRate: \"a-rate\" }\n ];\n }\n\n constructor() {\n super();\n // Box-Muller spare value\n this.hasSpare = false;\n this.spare = 0;\n }\n\n /**\n * Generate a Gaussian-distributed random number using Box-Muller transform.\n * Returns values with mean 0 and standard deviation 1.\n */\n gaussianRandom() {\n if (this.hasSpare) {\n this.hasSpare = false;\n return this.spare;\n }\n\n // Generate two uniform random numbers in (0, 1)\n let u, v, s;\n do {\n u = Math.random() * 2 - 1;\n v = Math.random() * 2 - 1;\n s = u * u + v * v;\n } while (s >= 1 || s === 0);\n\n const mul = Math.sqrt(-2 * Math.log(s) / s);\n this.spare = v * mul;\n this.hasSpare = true;\n return u * mul;\n }\n\n process(inputs, outputs, parameters) {\n const output = outputs[0]?.[0];\n\n if (!output) {\n return true;\n }\n\n for (let i = 0; i < output.length; i++) {\n // a-rate: per-sample value for An\n const An = parameters.An.length > 1 ? parameters.An[i] : parameters.An[0];\n output[i] = An * this.gaussianRandom();\n }\n\n return true;\n }\n}\n\nregisterProcessor('noise-source-processor', NoiseSourceProcessor);\n`;\n\n// Track whether the worklet module has been registered\nlet moduleRegistered = false;\n\n/**\n * Registers the noise source worklet module with the AudioContext.\n * Only registers once per application lifetime.\n */\nasync function ensureModuleRegistered(ctx: AudioContext): Promise<void> {\n if (moduleRegistered) {\n return;\n }\n\n const blob = new Blob([processorCode], { type: \"application/javascript\" });\n const url = URL.createObjectURL(blob);\n\n try {\n await ctx.audioWorklet.addModule(url);\n moduleRegistered = true;\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\n/**\n * Noise Source (NS)\n *\n * The noise source provides the aspiration component for breathy voice qualities.\n * It consists of Gaussian white noise filtered through a bandpass filter\n * (Butterworth, 1000–6000 Hz cutoff frequencies) and scaled by an amplitude factor.\n *\n * The noise can be modulated by the glottal flow derivative for mixed voice\n * qualities (not implemented in this basic version).\n *\n * Paper reference: Section 3.2.3 (filter structure), Section 4.2.5 (parameter mapping)\n *\n * Input parameters:\n * - An (noise amplitude) — derived directly from B (breathiness), with\n * scaling by E when voicing is off\n *\n * Signal flow:\n * Gaussian Noise → Highpass (1000 Hz) → Lowpass (6000 Hz) → Output\n *\n * The bandpass is implemented as a cascade of 2nd-order Butterworth highpass\n * and lowpass filters using native BiquadFilterNodes.\n */\nexport class NoiseSource implements Node<NoiseSourceParams> {\n private ctx: AudioContext;\n private workletNode: AudioWorkletNode;\n private highpassFilter: BiquadFilterNode;\n private lowpassFilter: BiquadFilterNode;\n\n public in: AudioNode | null = null; // Noise source has no input\n public out: AudioNode;\n\n private constructor(\n ctx: AudioContext,\n workletNode: AudioWorkletNode,\n highpassFilter: BiquadFilterNode,\n lowpassFilter: BiquadFilterNode\n ) {\n this.ctx = ctx;\n this.workletNode = workletNode;\n this.highpassFilter = highpassFilter;\n this.lowpassFilter = lowpassFilter;\n this.out = lowpassFilter;\n }\n\n /** Noise amplitude AudioParam (a-rate, 0-1) */\n get An(): AudioParam {\n return this.workletNode.parameters.get(\"An\")!;\n }\n\n /**\n * Creates a new NoiseSource node.\n *\n * The AudioWorklet module is registered automatically on first use.\n */\n static async create(\n ctx: AudioContext,\n params: NoiseSourceParams\n ): Promise<NoiseSource> {\n await ensureModuleRegistered(ctx);\n\n // Create noise generator worklet\n const workletNode = new AudioWorkletNode(ctx, \"noise-source-processor\");\n\n // Create Butterworth bandpass as highpass + lowpass cascade\n // Highpass at 1000 Hz\n const highpassFilter = ctx.createBiquadFilter();\n highpassFilter.type = \"highpass\";\n highpassFilter.frequency.setValueAtTime(1000, ctx.currentTime);\n highpassFilter.Q.setValueAtTime(Math.SQRT1_2, ctx.currentTime); // Butterworth Q = 1/√2\n\n // Lowpass at 6000 Hz\n const lowpassFilter = ctx.createBiquadFilter();\n lowpassFilter.type = \"lowpass\";\n lowpassFilter.frequency.setValueAtTime(6000, ctx.currentTime);\n lowpassFilter.Q.setValueAtTime(Math.SQRT1_2, ctx.currentTime); // Butterworth Q = 1/√2\n\n // Connect signal chain: noise → highpass → lowpass\n workletNode.connect(highpassFilter);\n highpassFilter.connect(lowpassFilter);\n\n const node = new NoiseSource(ctx, workletNode, highpassFilter, lowpassFilter);\n node.update(params);\n\n return node;\n }\n\n /**\n * Updates the noise source amplitude.\n * Sets AudioParam via setTargetAtTime for smooth transitions.\n */\n update(params: NoiseSourceParams): void {\n this.An.setTargetAtTime(params.An, this.ctx.currentTime, 0.02);\n }\n\n destroy(): void {\n this.workletNode.disconnect();\n this.highpassFilter.disconnect();\n this.lowpassFilter.disconnect();\n }\n}\n","import type { Node } from \"./types\";\nimport { PulseTrain } from \"./pulse-train\";\nimport { GlottalFormant } from \"./glottal-formant\";\nimport { SpectralTilt } from \"./spectral-tilt\";\nimport { NoiseSource } from \"./noise-source\";\nimport { Gain } from \"./gain\";\n\nexport type GlottalFlowDerivativeParams = {\n /** Fundamental frequency in Hz (derived from P, P₀) */\n f0: number;\n /** Glottal formant centre frequency in Hz, computed as f0 / (2 * Oq) */\n Fg: number;\n /** Glottal formant bandwidth in Hz, computed from f0, Oq, and αm */\n Bg: number;\n /** Source amplitude, derived from E and Oq */\n Ag: number;\n /** First stage spectral tilt attenuation in dB at 3 kHz, derived from E and M */\n Tl1: number;\n /** Second stage spectral tilt attenuation in dB at 3 kHz, derived from E and M */\n Tl2: number;\n /** Noise amplitude, derived from B (breathiness) */\n An: number;\n /** Jitter depth: maximum f₀ perturbation as a fraction (0-0.3 for ±30%), derived from R */\n jitterDepth: number;\n /** Shimmer depth: maximum amplitude perturbation as a fraction (0-1 for ±100%), derived from R */\n shimmerDepth: number;\n};\n\n/**\n * Glottal Flow Derivative (GFD)\n *\n * The glottal flow derivative model represents the sound source produced by\n * the vibrating vocal folds. It consists of a voiced component (periodic pulses\n * shaped by the glottal formant and spectral tilt) and an unvoiced component\n * (filtered noise for breathiness).\n *\n * The noise is modulated by the glottal flow derivative, so aspiration noise\n * follows the glottal cycle - loudest during the open phase, quiet when closed.\n *\n * Signal flow:\n *\n * PulseTrain(f0) → GlottalFormant(Fg, Bg, Ag) → SpectralTilt(Tl1, Tl2) ──┬────→ Output\n * │ ↑\n * │ (mod) │\n * ↓ │\n * NoiseSource(An) ─────────────────────────────────────────────────→ [Multiply] ──┘\n *\n * Paper reference: Section 3.2 (overall structure)\n *\n * Input parameters:\n * - f0 (fundamental frequency) — controls the pulse train period\n * - Fg (glottal formant centre frequency) — computed as f0 / (2·Oq)\n * - Bg (glottal formant bandwidth) — computed from f0, Oq, and αm\n * - Ag (source amplitude) — derived from E, Oq, and R (shimmer)\n * - Tl1 (first tilt stage attenuation in dB at 3 kHz) — derived from E and M\n * - Tl2 (second tilt stage attenuation in dB at 3 kHz) — derived from E and M\n * - An (noise amplitude) — derived from B (breathiness)\n */\nexport class GlottalFlowDerivative implements Node<GlottalFlowDerivativeParams> {\n private pulseTrain: PulseTrain;\n private glottalFormant: GlottalFormant;\n private spectralTilt: SpectralTilt;\n private noiseSource: NoiseSource;\n private noiseModulator: GainNode; // Multiplies noise by GFD signal\n private outputGain: Gain;\n\n public in: AudioNode | null = null; // Source node has no input\n public out: AudioNode;\n\n private constructor(\n pulseTrain: PulseTrain,\n glottalFormant: GlottalFormant,\n spectralTilt: SpectralTilt,\n noiseSource: NoiseSource,\n noiseModulator: GainNode,\n outputGain: Gain\n ) {\n this.pulseTrain = pulseTrain;\n this.glottalFormant = glottalFormant;\n this.spectralTilt = spectralTilt;\n this.noiseSource = noiseSource;\n this.noiseModulator = noiseModulator;\n this.outputGain = outputGain;\n this.out = outputGain.out;\n }\n\n /**\n * Creates a new GlottalFlowDerivative node.\n *\n * Sets up the voiced path (pulse train → glottal formant → spectral tilt)\n * and the noise modulation path where noise is multiplied by the GFD signal.\n */\n static async create(\n ctx: AudioContext,\n params: GlottalFlowDerivativeParams\n ): Promise<GlottalFlowDerivative> {\n // Create all sub-nodes in parallel\n const [pulseTrain, glottalFormant, spectralTilt, noiseSource, outputGain] =\n await Promise.all([\n PulseTrain.create(ctx, {\n f0: params.f0,\n jitterDepth: params.jitterDepth,\n shimmerDepth: params.shimmerDepth,\n }),\n GlottalFormant.create(ctx, { Fg: params.Fg, Bg: params.Bg, Ag: params.Ag }),\n SpectralTilt.create(ctx, { Tl1: params.Tl1, Tl2: params.Tl2 }),\n NoiseSource.create(ctx, { An: params.An }),\n Gain.create(ctx, { gain: 1 }),\n ]);\n\n // Create noise modulator - a GainNode whose gain is modulated by the GFD signal\n // This implements the multiplication: noise × GFD\n const noiseModulator = ctx.createGain();\n noiseModulator.gain.setValueAtTime(0, ctx.currentTime); // Base gain is 0, modulated by audio signal\n\n // Connect voiced path: PulseTrain → GlottalFormant → SpectralTilt → Output\n pulseTrain.out.connect(glottalFormant.in);\n glottalFormant.out.connect(spectralTilt.in);\n spectralTilt.out.connect(outputGain.in);\n\n // Connect GFD signal to modulate the noise gain (audio-rate modulation)\n spectralTilt.out.connect(noiseModulator.gain);\n\n // Connect noise through modulator to output: NoiseSource → [×GFD] → Output\n noiseSource.out.connect(noiseModulator);\n noiseModulator.connect(outputGain.in);\n\n return new GlottalFlowDerivative(\n pulseTrain,\n glottalFormant,\n spectralTilt,\n noiseSource,\n noiseModulator,\n outputGain\n );\n }\n\n /**\n * Updates all glottal flow derivative parameters.\n * Each sub-node receives its relevant parameters.\n */\n update(params: GlottalFlowDerivativeParams): void {\n this.pulseTrain.update({\n f0: params.f0,\n jitterDepth: params.jitterDepth,\n shimmerDepth: params.shimmerDepth,\n });\n this.glottalFormant.update({ Fg: params.Fg, Bg: params.Bg, Ag: params.Ag });\n this.spectralTilt.update({ Tl1: params.Tl1, Tl2: params.Tl2 });\n this.noiseSource.update({ An: params.An });\n }\n\n /**\n * Returns the pulse train node for direct AudioParam access.\n *\n * Example usage:\n * gfd.pulseTrainNode.f0.setValueAtTime(440, ctx.currentTime);\n * gfd.pulseTrainNode.jitterDepth.linearRampToValueAtTime(0.1, ctx.currentTime + 0.5);\n */\n get pulseTrainNode(): PulseTrain {\n return this.pulseTrain;\n }\n\n /**\n * Returns the glottal formant node for direct AudioParam access.\n *\n * Example usage:\n * gfd.glottalFormantNode.Fg.setValueAtTime(220, ctx.currentTime);\n * gfd.glottalFormantNode.Ag.setTargetAtTime(0.5, ctx.currentTime, 0.1);\n */\n get glottalFormantNode(): GlottalFormant {\n return this.glottalFormant;\n }\n\n /**\n * Returns the spectral tilt node for direct AudioParam access.\n *\n * Example usage:\n * gfd.spectralTiltNode.Tl1.setValueAtTime(10, ctx.currentTime);\n */\n get spectralTiltNode(): SpectralTilt {\n return this.spectralTilt;\n }\n\n /**\n * Returns the noise source node for direct AudioParam access.\n *\n * Example usage:\n * gfd.noiseSourceNode.An.setValueAtTime(0.3, ctx.currentTime);\n */\n get noiseSourceNode(): NoiseSource {\n return this.noiseSource;\n }\n\n /**\n * Starts the voiced excitation (pulse train).\n */\n start(): void {\n this.pulseTrain.start();\n }\n\n /**\n * Stops the voiced excitation (pulse train).\n * Note: Noise source continues to produce output based on An parameter.\n */\n stop(): void {\n this.pulseTrain.stop();\n }\n\n destroy(): void {\n this.pulseTrain.destroy();\n this.glottalFormant.destroy();\n this.spectralTilt.destroy();\n this.noiseSource.destroy();\n this.noiseModulator.disconnect();\n this.outputGain.destroy();\n }\n}\n","import type { Node } from \"./types\";\n\nexport type FormantResonatorParams = {\n /** Formant centre frequency in Hz */\n F: number;\n /** Formant bandwidth in Hz */\n B: number;\n /** Formant amplitude (linear gain) */\n A: number;\n};\n\n/**\n * Formant Resonator AudioWorklet Processor code.\n *\n * Implements the transfer function from Section 3.3.1:\n * R(z) = A * (1 - R) * (1 - R*z^{-2}) / (1 - 2R*cos(θ)*z^{-1} + R²*z^{-2})\n *\n * Where:\n * R = e^(-π * B * Ts) (pole radius, Ts = 1/sampleRate)\n * θ = 2π * F * Ts (pole angle)\n *\n * This is a 2-pole 2-zero digital resonator (bandpass filter).\n */\nconst processorCode = `\nclass FormantResonatorProcessor extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n { name: \"F\", defaultValue: 500, minValue: 100, maxValue: 8000, automationRate: \"a-rate\" },\n { name: \"B\", defaultValue: 100, minValue: 20, maxValue: 1000, automationRate: \"a-rate\" },\n { name: \"A\", defaultValue: 1, minValue: 0, maxValue: 10, automationRate: \"a-rate\" }\n ];\n }\n\n constructor() {\n super();\n // Filter state (delay line)\n this.x1 = 0;\n this.x2 = 0;\n this.y1 = 0;\n this.y2 = 0;\n\n // Cached parameter values for change detection\n this.lastF = -1;\n this.lastB = -1;\n this.lastA = -1;\n\n // Cached coefficients\n this.b0 = 0;\n this.b2 = 0;\n this.a1 = 0;\n this.a2 = 0;\n }\n\n process(inputs, outputs, parameters) {\n const input = inputs[0]?.[0];\n const output = outputs[0]?.[0];\n\n if (!input || !output) {\n return true;\n }\n\n // a-rate parameters (per-sample values)\n const F = parameters.F[0];\n const B = parameters.B[0];\n const A = parameters.A[0];\n\n // Recompute coefficients if any parameter changed\n if (F !== this.lastF || B !== this.lastB || A !== this.lastA) {\n const Ts = 1 / sampleRate;\n\n // Pole radius and angle\n const R = Math.exp(-Math.PI * B * Ts);\n const theta = 2 * Math.PI * F * Ts;\n\n // Gain scaling factor\n const g = 1 - R;\n\n // Numerator coefficients (feedforward)\n // From: A * g * (1 - R*z^{-2}) = A*g + 0*z^{-1} - A*g*R*z^{-2}\n this.b0 = A * g;\n this.b2 = -A * g * R;\n\n // Denominator coefficients (feedback)\n // From: 1 - 2R*cos(θ)*z^{-1} + R²*z^{-2}\n this.a1 = -2 * R * Math.cos(theta);\n this.a2 = R * R;\n\n this.lastF = F;\n this.lastB = B;\n this.lastA = A;\n }\n\n for (let i = 0; i < input.length; i++) {\n const x0 = input[i];\n\n // Direct Form I biquad (b1 is always 0)\n const y0 = this.b0 * x0 + this.b2 * this.x2\n - this.a1 * this.y1 - this.a2 * this.y2;\n\n // Update delay line\n this.x2 = this.x1;\n this.x1 = x0;\n this.y2 = this.y1;\n this.y1 = y0;\n\n output[i] = y0;\n }\n\n return true;\n }\n}\n\nregisterProcessor('formant-resonator-processor', FormantResonatorProcessor);\n`;\n\n// Track whether the worklet module has been registered\nlet moduleRegistered = false;\n\n/**\n * Registers the formant resonator worklet module with the AudioContext.\n * Only registers once per application lifetime.\n */\nasync function ensureModuleRegistered(ctx: AudioContext): Promise<void> {\n if (moduleRegistered) {\n return;\n }\n\n const blob = new Blob([processorCode], { type: \"application/javascript\" });\n const url = URL.createObjectURL(blob);\n\n try {\n await ctx.audioWorklet.addModule(url);\n moduleRegistered = true;\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\n/**\n * Formant Resonator (Ri)\n *\n * A single formant filter representing one resonance of the vocal tract.\n * Implemented as a 2-pole 2-zero digital resonator (bandpass filter) with\n * controllable centre frequency (F), bandwidth (B), and amplitude (A).\n *\n * The transfer function (Section 3.3.1) is:\n *\n * R(z) = A * (1 - R) * (1 - R*z^{-2}) / (1 - 2R*cos(θ)*z^{-1} + R²*z^{-2})\n *\n * Where:\n * R = e^(-π * B * Ts) (pole radius, Ts = sampling period)\n * θ = 2π * F * Ts (pole angle)\n *\n * The vocal tract model uses six formant resonators (R1-R6) in parallel:\n * - F1-F3: Primary formants that determine vowel identity\n * - F4-F6: Higher formants that contribute to voice timbre\n *\n * Grouping F3, F4, and F5 closely together can produce the \"singer's formant\"\n * characteristic of trained classical voices.\n *\n * Paper reference: Section 3.3.1 (filter structure), Section 4.3.1 (vowel table),\n * Section 4.3.6 (bandwidths), Section 4.3.7 (amplitudes)\n *\n * Input parameters:\n * - F (formant centre frequency in Hz) — interpolated from vowel table,\n * scaled by αS (vocal tract size) and K (larynx position factor)\n * - B (formant bandwidth in Hz) — interpolated from vowel table\n * - A (formant amplitude, linear) — interpolated from vowel table,\n * with correction when harmonics coincide with formant frequencies\n */\nexport class FormantResonator implements Node<FormantResonatorParams> {\n private ctx: AudioContext;\n private workletNode: AudioWorkletNode;\n public in: AudioNode;\n public out: AudioNode;\n\n private constructor(ctx: AudioContext, workletNode: AudioWorkletNode) {\n this.ctx = ctx;\n this.workletNode = workletNode;\n this.in = workletNode;\n this.out = workletNode;\n }\n\n /** Formant centre frequency AudioParam (a-rate, 100-8000 Hz) */\n get F(): AudioParam {\n return this.workletNode.parameters.get(\"F\")!;\n }\n\n /** Formant bandwidth AudioParam (a-rate, 20-1000 Hz) */\n get B(): AudioParam {\n return this.workletNode.parameters.get(\"B\")!;\n }\n\n /** Formant amplitude AudioParam (a-rate, 0-10) */\n get A(): AudioParam {\n return this.workletNode.parameters.get(\"A\")!;\n }\n\n /**\n * Creates a new FormantResonator node.\n *\n * The AudioWorklet module is registered automatically on first use.\n */\n static async create(\n ctx: AudioContext,\n params: FormantResonatorParams\n ): Promise<FormantResonator> {\n await ensureModuleRegistered(ctx);\n\n const workletNode = new AudioWorkletNode(ctx, \"formant-resonator-processor\");\n const node = new FormantResonator(ctx, workletNode);\n node.update(params);\n\n return node;\n }\n\n /**\n * Updates the formant resonator parameters.\n * Sets AudioParams via setTargetAtTime for smooth transitions.\n */\n update(params: FormantResonatorParams): void {\n this.F.setTargetAtTime(params.F, this.ctx.currentTime, 0.02);\n this.B.setTargetAtTime(params.B, this.ctx.currentTime, 0.02);\n this.A.setTargetAtTime(params.A, this.ctx.currentTime, 0.02);\n }\n\n destroy(): void {\n this.workletNode.disconnect();\n }\n}\n","import type { Node } from \"./types\";\nimport { FormantResonator, FormantResonatorParams } from \"./formant-resonator\";\n\nexport type FormantBankParams = {\n /** Array of formant parameters, one per resonator */\n formants: FormantResonatorParams[];\n};\n\n/**\n * Formant Bank\n *\n * A parallel bank of formant resonators representing the vocal tract resonances.\n * Each resonator is a 2-pole 2-zero bandpass filter (see FormantResonator).\n *\n * The input signal is fed to all resonators in parallel, and their outputs\n * are summed to produce a single output.\n *\n * Signal flow:\n *\n * ┌─→ R1(F1, B1, A1) ─┐\n * ├─→ R2(F2, B2, A2) ─┤\n * Input ─────┼─→ R3(F3, B3, A3) ─┼─────→ Output (sum)\n * ├─→ ... ─┤\n * └─→ Rn(Fn, Bn, An) ─┘\n *\n * The paper uses six formants (R1-R6):\n * - F1-F3: Primary formants that determine vowel identity\n * - F4-F6: Higher formants that contribute to voice timbre\n *\n * This implementation accepts any number of formants, allowing flexibility\n * for experimentation or simplified models.\n *\n * Paper reference: Section 3.3.1 (parallel formant structure)\n *\n * Input parameters:\n * - formants: Array of {F, B, A} objects defining each resonator\n *\n * Note: The number of formants is fixed at creation time. Calling update()\n * with a different array length will throw an error. To change the number\n * of formants, destroy this node and create a new one.\n */\nexport class FormantBank implements Node<FormantBankParams> {\n private resonators: FormantResonator[];\n private inputGain: GainNode;\n private outputGain: GainNode;\n\n public in: AudioNode;\n public out: AudioNode;\n\n private constructor(\n resonators: FormantResonator[],\n inputGain: GainNode,\n outputGain: GainNode\n ) {\n this.resonators = resonators;\n this.inputGain = inputGain;\n this.outputGain = outputGain;\n this.in = inputGain;\n this.out = outputGain;\n }\n\n /**\n * Creates a new FormantBank node with the specified formants.\n *\n * All formant resonators are created in parallel for efficiency.\n */\n static async create(\n ctx: AudioContext,\n params: FormantBankParams\n ): Promise<FormantBank> {\n // Create input and output gain nodes for routing\n const inputGain = ctx.createGain();\n inputGain.gain.setValueAtTime(1, ctx.currentTime);\n\n const outputGain = ctx.createGain();\n outputGain.gain.setValueAtTime(1, ctx.currentTime);\n\n // Create all formant resonators in parallel\n const resonators = await Promise.all(\n params.formants.map((formant) => FormantResonator.create(ctx, formant))\n );\n\n // Wire up parallel structure: input → each resonator → output\n for (const resonator of resonators) {\n inputGain.connect(resonator.in);\n resonator.out.connect(outputGain);\n }\n\n return new FormantBank(resonators, inputGain, outputGain);\n }\n\n /**\n * Updates all formant parameters.\n *\n * The formants array must have the same length as the original.\n * Each resonator receives its corresponding parameters.\n *\n * @throws Error if the formants array length doesn't match\n */\n update(params: FormantBankParams): void {\n if (params.formants.length !== this.resonators.length) {\n throw new Error(\n `FormantBank: Cannot change number of formants. ` +\n `Expected ${this.resonators.length}, got ${params.formants.length}. ` +\n `Destroy and recreate the node to change formant count.`\n );\n }\n\n for (let i = 0; i < this.resonators.length; i++) {\n this.resonators[i].update(params.formants[i]);\n }\n }\n\n /**\n * Returns the number of formant resonators in this bank.\n */\n get count(): number {\n return this.resonators.length;\n }\n\n /**\n * Returns the array of formant resonators for direct AudioParam access.\n *\n * Example usage:\n * formantBank.formants[0].F.setValueAtTime(800, ctx.currentTime);\n * formantBank.formants[1].B.linearRampToValueAtTime(100, ctx.currentTime + 0.1);\n */\n get formants(): readonly FormantResonator[] {\n return this.resonators;\n }\n\n destroy(): void {\n // Disconnect routing nodes\n this.inputGain.disconnect();\n this.outputGain.disconnect();\n\n // Destroy all resonators\n for (const resonator of this.resonators) {\n resonator.destroy();\n }\n\n this.resonators = [];\n }\n}\n","import type { Node } from \"./types\";\n\nexport type AntiResonanceParams = {\n /** Anti-formant centre frequency in Hz (nominally 4700 Hz, scaled by αS) */\n F: number;\n /** Anti-formant quality factor (nominally 2.5) */\n Q: number;\n};\n\n/**\n * Anti-Resonance (Notch Filter) AudioWorklet Processor code.\n *\n * Implements the transfer function from Section 3.3.2:\n * BQ(z) = (1 + β*z⁻¹ + z⁻²) / ((1 + α) + β*z⁻¹ + (1 - α)*z⁻²)\n *\n * Where:\n * α = sin(2π * F * Ts) / (2 * Q)\n * β = -2 * cos(2π * F * Ts)\n *\n * This creates a notch (anti-resonance) at frequency F with width controlled by Q.\n */\nconst processorCode = `\nclass AntiResonanceProcessor extends AudioWorkletProcessor {\n static get parameterDescriptors() {\n return [\n { name: \"F\", defaultValue: 4700, minValue: 1000, maxValue: 10000, automationRate: \"k-rate\" },\n { name: \"Q\", defaultValue: 2.5, minValue: 0.5, maxValue: 20, automationRate: \"k-rate\" }\n ];\n }\n\n constructor() {\n super();\n // Filter state (delay line)\n this.x1 = 0;\n this.x2 = 0;\n this.y1 = 0;\n this.y2 = 0;\n\n // Cached parameter values for change detection\n this.lastF = -1;\n this.lastQ = -1;\n\n // Cached coefficients\n this.b0 = 1;\n this.b1 = 0;\n this.b2 = 1;\n this.a1 = 0;\n this.a2 = 0;\n }\n\n process(inputs, outputs, parameters) {\n const input = inputs[0]?.[0];\n const output = outputs[0]?.[0];\n\n if (!input || !output) {\n return true;\n }\n\n // k-rate parameters (single value per block)\n const F = parameters.F[0];\n const Q = parameters.Q[0];\n\n // Recompute coefficients if any parameter changed\n if (F !== this.lastF || Q !== this.lastQ) {\n const Ts = 1 / sampleRate;\n const omega = 2 * Math.PI * F * Ts;\n\n // Intermediate coefficients from the paper\n const alpha = Math.sin(omega) / (2 * Q);\n const beta = -2 * Math.cos(omega);\n\n // Normalize by dividing by (1 + alpha) so that a0 = 1\n const a0 = 1 + alpha;\n\n // Numerator coefficients (feedforward)\n // From: (1 + β*z⁻¹ + z⁻²) / a0\n this.b0 = 1 / a0;\n this.b1 = beta / a0;\n this.b2 = 1 / a0;\n\n // Denominator coefficients (feedback), normalized\n // From: ((1 + α) + β*z⁻¹ + (1 - α)*z⁻²) / a0\n // a0/a0 = 1 (implicit), a1 = β/a0, a2 = (1 - α)/a0\n this.a1 = beta / a0;\n this.a2 = (1 - alpha) / a0;\n\n this.lastF = F;\n this.lastQ = Q;\n }\n\n for (let i = 0; i < input.length; i++) {\n const x0 = input[i];\n\n // Direct Form I biquad\n const y0 = this.b0 * x0 + this.b1 * this.x1 + this.b2 * this.x2\n - this.a1 * this.y1 - this.a2 * this.y2;\n\n // Update delay line\n this.x2 = this.x1;\n this.x1 = x0;\n this.y2 = this.y1;\n this.y1 = y0;\n\n output[i] = y0;\n }\n\n return true;\n }\n}\n\nregisterProcessor('anti-resonance-processor', AntiResonanceProcessor);\n`;\n\n// Track whether the worklet module has been registered\nlet moduleRegistered = false;\n\n/**\n * Registers the anti-resonance worklet module with the AudioContext.\n * Only registers once per application lifetime.\n */\nasync function ensureModuleRegistered(ctx: AudioContext): Promise<void> {\n if (moduleRegistered) {\n return;\n }\n\n const blob = new Blob([processorCode], { type: \"application/javascript\" });\n const url = URL.createObjectURL(blob);\n\n try {\n await ctx.audioWorklet.addModule(url);\n moduleRegistered = true;\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\n/**\n * Hypo-pharynx Anti-Resonance Filter (BQ)\n *\n * A notch filter in cascade with the parallel formants that models the\n * anti-resonances caused by the hypo-pharynx cavity (the space between\n * the larynx and the pharynx). This creates spectral dips at approximately\n * 2.5–3.5 kHz and 4–5 kHz that contribute to natural voice timbre.\n *\n * The transfer function (Section 3.3.2) is:\n *\n * BQ(z) = (1 + β*z⁻¹ + z⁻²) / ((1 + α) + β*z⁻¹ + (1 - α)*z⁻²)\n *\n * Where:\n * α = sin(2π * F * Ts) / (2 * Q)\n * β = -2 * cos(2π * F * Ts)\n *\n * This is a bi-quadratic second-order notch filter that attenuates\n * frequencies near the centre frequency F, with the notch width\n * controlled by the quality factor Q.\n *\n * Paper reference: Section 3.3.2 (filter structure), Section 4.3.8 (parameter values)\n *\n * Input parameters:\n * - F (anti-formant centre frequency in Hz) — nominally 4700 Hz,\n * scaled by αS (vocal tract size factor)\n * - Q (anti-formant quality factor) — fixed at 2.5 in the paper\n */\nexport class AntiResonance implements Node<AntiResonanceParams> {\n private ctx: AudioContext;\n private workletNode: AudioWorkletNode;\n public in: AudioNode;\n public out: AudioNode;\n\n private constructor(ctx: AudioContext, workletNode: AudioWorkletNode) {\n this.ctx = ctx;\n this.workletNode = workletNode;\n this.in = workletNode;\n this.out = workletNode;\n }\n\n /** Anti-formant centre frequency AudioParam (k-rate, 1000-10000 Hz) */\n get F(): AudioParam {\n return this.workletNode.parameters.get(\"F\")!;\n }\n\n /** Anti-formant quality factor AudioParam (k-rate, 0.5-20) */\n get Q(): AudioParam {\n return this.workletNode.parameters.get(\"Q\")!;\n }\n\n /**\n * Creates a new AntiResonance node.\n *\n * The AudioWorklet module is registered automatically on first use.\n */\n static async create(\n ctx: AudioContext,\n params: AntiResonanceParams\n ): Promise<AntiResonance> {\n await ensureModuleRegistered(ctx);\n\n const workletNode = new AudioWorkletNode(ctx, \"anti-resonance-processor\");\n const node = new AntiResonance(ctx, workletNode);\n node.update(params);\n\n return node;\n }\n\n /**\n * Updates the anti-resonance parameters.\n * Sets AudioParams via setTargetAtTime for smooth transitions.\n */\n update(params: AntiResonanceParams): void {\n this.F.setTargetAtTime(params.F, this.ctx.currentTime, 0.02);\n this.Q.setTargetAtTime(params.Q, this.ctx.currentTime, 0.02);\n }\n\n destroy(): void {\n this.workletNode.disconnect();\n }\n}\n","import type { Node } from \"./types\";\nimport { FormantBank } from \"./formant-bank\";\nimport { FormantResonator, FormantResonatorParams } from \"./formant-resonator\";\nimport { AntiResonance } from \"./anti-resonance\";\n\nexport type VocalTractParams = {\n /** Array of formant parameters (F, B, A) for each resonator */\n formants: FormantResonatorParams[];\n /** Anti-formant centre frequency in Hz (nominally 4700 Hz, scaled by αS) */\n F_BQ: number;\n /** Anti-formant quality factor (nominally 2.5) */\n Q_BQ: number;\n};\n\n/**\n * Vocal Tract Model\n *\n * The vocal tract model shapes the glottal source spectrum to produce\n * recognisable vowels and voice timbres. It consists of parallel formant\n * resonators (simulating the resonances of the oral cavity) followed by\n * a cascaded anti-resonance filter (simulating the effect of the hypo-pharynx).\n *\n * Signal flow:\n *\n * ┌─→ R1(F1, B1, A1) ─┐\n * ├─→ R2(F2, B2, A2) ─┤\n * Input ─────┼─→ R3(F3, B3, A3) ─┼─────→ BQ(F_BQ, Q_BQ) ─────→ Output\n * ├─→ ... ─┤\n * └─→ Rn(Fn, Bn, An) ─┘\n *\n * The paper uses six formants (R1-R6):\n * - F1-F3: Primary formants that determine vowel identity\n * - F4-F6: Higher formants that contribute to voice timbre\n *\n * The anti-resonance (BQ) models spectral dips caused by the hypo-pharynx\n * cavity at approximately 2.5–3.5 kHz and 4–5 kHz.\n *\n * Paper reference: Section 3.3 (overall structure), Section 3.3.1 (formants),\n * Section 3.3.2 (anti-resonance)\n *\n * Input parameters:\n * - formants: Array of {F, B, A} objects for each resonator\n * - F_BQ: Anti-formant centre frequency (nominally 4700 Hz, scaled by αS)\n * - Q_BQ: Anti-formant quality factor (nominally 2.5)\n */\nexport class VocalTract implements Node<VocalTractParams> {\n private formantBank: FormantBank;\n private antiResonance: AntiResonance;\n\n public in: AudioNode;\n public out: AudioNode;\n\n private constructor(\n formantBank: FormantBank,\n antiResonance: AntiResonance\n ) {\n this.formantBank = formantBank;\n this.antiResonance = antiResonance;\n this.in = formantBank.in;\n this.out = antiResonance.out;\n }\n\n /**\n * Creates a new VocalTract node.\n *\n * Sets up the parallel formant bank followed by the anti-resonance filter.\n */\n static async create(\n ctx: AudioContext,\n params: VocalTractParams\n ): Promise<VocalTract> {\n // Create sub-nodes in parallel\n const [formantBank, antiResonance] = await Promise.all([\n FormantBank.create(ctx, { formants: params.formants }),\n AntiResonance.create(ctx, { F: params.F_BQ, Q: params.Q_BQ }),\n ]);\n\n // Connect in cascade: FormantBank → AntiResonance\n formantBank.out.connect(antiResonance.in);\n\n return new VocalTract(formantBank, antiResonance);\n }\n\n /**\n * Updates all vocal tract parameters.\n *\n * @throws Error if the formants array length doesn't match the original\n */\n update(params: VocalTractParams): void {\n this.formantBank.update({ formants: params.formants });\n this.antiResonance.update({ F: params.F_BQ, Q: params.Q_BQ });\n }\n\n /**\n * Returns the number of formant resonators in the vocal tract.\n */\n get formantCount(): number {\n return this.formantBank.count;\n }\n\n /**\n * Returns the array of formant resonators for direct AudioParam access.\n *\n * Example usage:\n * vocalTract.formants[0].F.setValueAtTime(800, ctx.currentTime);\n * vocalTract.formants[2].B.linearRampToValueAtTime(100, ctx.currentTime + 0.1);\n */\n get formants(): readonly FormantResonator[] {\n return this.formantBank.formants;\n }\n\n /**\n * Returns the anti-resonance node for direct AudioParam access.\n *\n * Example usage:\n * vocalTract.antiResonanceNode.F.setValueAtTime(4500, ctx.currentTime);\n * vocalTract.antiResonanceNode.Q.setValueAtTime(3, ctx.currentTime);\n */\n get antiResonanceNode(): AntiResonance {\n return this.antiResonance;\n }\n\n destroy(): void {\n this.formantBank.destroy();\n this.antiResonance.destroy();\n }\n}\n","import type { Node } from \"./types\";\nimport { GlottalFlowDerivative } from \"./glottal-flow-derivative\";\nimport { VocalTract } from \"./vocal-tract\";\nimport { Gain } from \"./gain\";\nimport { SynthParams } from \"../parameters\";\n\n/**\n * Voice Synthesizer\n *\n * The complete voice synthesis pipeline combining the glottal flow derivative\n * (voice source) with the vocal tract model (formant filtering). This implements\n * the full source-filter model described in the paper.\n *\n * Signal flow:\n *\n * ┌─────────────────────────────────────────────────────────────────────────┐\n * │ Glottal Flow Derivative (Source) │\n * │ │\n * │ PulseTrain(f0) → GlottalFormant(Fg,Bg,Ag) → SpectralTilt(Tl1,Tl2) ─┬─┤\n * │ ↓ (mod) │ │\n * │ NoiseSource(An) ──────────────────────────────────→ [×] ───────────┘ │\n * └─────────────────────────────────────────────────────────────────────────┘\n * │\n * ↓\n * ┌─────────────────────────────────────────────────────────────────────────┐\n * │ Vocal Tract (Filter) │\n * │ │\n * │ ┌─→ R1(F1, B1, A1) ─┐ │\n * │ ├─→ R2(F2, B2, A2) ─┤ │\n * │ Input ───┼─→ R3(F3, B3, A3) ─┼───→ BQ(F_BQ, Q_BQ) ───→ Output │\n * │ ├─→ ... ─┤ │\n * │ └─→ Rn(Fn, Bn, An) ─┘ │\n * └─────────────────────────────────────────────────────────────────────────┘\n * │\n * ↓\n * OutputGain ───→ Output\n *\n * Paper reference: Section 3.1 (overall model), Section 3.2 (voice source),\n * Section 3.3 (vocal tract)\n *\n * Input parameters:\n * - source: GlottalFlowDerivativeParams (f0, Fg, Bg, Ag, Tl1, Tl2, An)\n * - tract: VocalTractParams (formants array, F_BQ, Q_BQ)\n * - outputGain: Overall output level (optional, defaults to 1)\n */\nexport class Voice implements Node<SynthParams> {\n private glottalFlowDerivative: GlottalFlowDerivative;\n private vocalTract: VocalTract;\n private outputGainNode: Gain;\n\n public in: AudioNode | null = null; // Source node has no input\n public out: AudioNode;\n\n private constructor(\n glottalFlowDerivative: GlottalFlowDerivative,\n vocalTract: VocalTract,\n outputGainNode: Gain\n ) {\n this.glottalFlowDerivative = glottalFlowDerivative;\n this.vocalTract = vocalTract;\n this.outputGainNode = outputGainNode;\n this.out = outputGainNode.out;\n }\n\n /**\n * Creates a new Voice synthesizer node.\n *\n * Sets up the complete source-filter synthesis pipeline.\n */\n static async create(\n ctx: AudioContext,\n params: SynthParams\n ): Promise<Voice> {\n // Create all sub-nodes in parallel\n const [glottalFlowDerivative, vocalTract, outputGainNode] = await Promise.all([\n GlottalFlowDerivative.create(ctx, params),\n VocalTract.create(ctx, params),\n Gain.create(ctx, { gain: params.outputGain ?? 1 }),\n ]);\n\n // Connect the pipeline: GFD → VocalTract → OutputGain\n glottalFlowDerivative.out.connect(vocalTract.in);\n vocalTract.out.connect(outputGainNode.in);\n\n return new Voice(glottalFlowDerivative, vocalTract, outputGainNode);\n }\n\n /**\n * Updates all voice parameters.\n *\n * @throws Error if the formants array length doesn't match the original\n */\n update(params: SynthParams): void {\n this.glottalFlowDerivative.update(params);\n this.vocalTract.update(params);\n if (params.outputGain !== undefined) {\n this.outputGainNode.update({ gain: params.outputGain });\n }\n }\n\n /**\n * Starts the voice (begins voiced excitation).\n */\n start(): void {\n this.glottalFlowDerivative.start();\n }\n\n /**\n * Stops the voice (stops voiced excitation).\n * Note: Noise component continues based on An parameter.\n */\n stop(): void {\n this.glottalFlowDerivative.stop();\n }\n\n /**\n * Returns the number of formant resonators in the vocal tract.\n */\n get formantCount(): number {\n return this.vocalTract.formantCount;\n }\n\n /**\n * Returns the glottal flow derivative node for direct AudioParam access.\n *\n * Example usage:\n * voice.source.pulseTrainNode.f0.setValueAtTime(440, ctx.currentTime);\n * voice.source.glottalFormantNode.Ag.setTargetAtTime(0.5, ctx.currentTime, 0.1);\n */\n get source(): GlottalFlowDerivative {\n return this.glottalFlowDerivative;\n }\n\n /**\n * Returns the vocal tract node for direct AudioParam access.\n *\n * Example usage:\n * voice.tract.formants[0].F.linearRampToValueAtTime(800, ctx.currentTime + 0.1);\n * voice.tract.antiResonanceNode.F.setValueAtTime(4500, ctx.currentTime);\n */\n get tract(): VocalTract {\n return this.vocalTract;\n }\n\n /**\n * Returns the output gain node for direct AudioParam access.\n *\n * Example usage:\n * voice.outputGain.gain.linearRampToValueAtTime(0.5, ctx.currentTime + 1);\n */\n get outputGain(): Gain {\n return this.outputGainNode;\n }\n\n destroy(): void {\n this.glottalFlowDerivative.destroy();\n this.vocalTract.destroy();\n this.outputGainNode.destroy();\n }\n}\n","/**\n * Vowel formant data from Cantor Digitalis Table 3\n *\n * Reference: Section 4.3.1 \"Generic Formant Values\"\n *\n * The sixth formant is derived: F6 = 2*F4, A6 = -15 dB, B6 = 150 Hz\n */\n\nexport interface Formant {\n frequency: number; // Hz\n amplitude: number; // dB\n bandwidth: number; // Hz\n}\n\nexport interface VowelData {\n ipa: string;\n v: number; // backness: 0 = back, 1 = front\n h: number; // height: 0 = close, 1 = open\n formants: [Formant, Formant, Formant, Formant, Formant, Formant];\n}\n\n// Grid structure for interpolation\n// V levels: 0 (back), 0.5 (central), 1 (front)\n// H levels: 0 (close), 1/3, 2/3, 1 (open)\nconst V_LEVELS = [0, 0.5, 1];\nconst H_LEVELS = [0, 1 / 3, 2 / 3, 1];\n\n// Vowel grid indexed by [h_index][v_index]\n// At H=1, only /a/ exists (V=0.5), so we treat it as spanning the full width\nconst VOWEL_GRID: (string | null)[][] = [\n [\"u\", \"y\", \"i\"], // H = 0\n [\"o\", \"œ\", \"e\"], // H = 1/3\n [\"ɔ\", \"ø\", \"ɛ\"], // H = 2/3\n [\"a\", \"a\", \"a\"], // H = 1 (all map to /a/)\n];\n\nfunction findVowelByIpa(ipa: string): VowelData | undefined {\n return vowels.find((v) => v.ipa === ipa);\n}\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\nfunction lerpFormant(a: Formant, b: Formant, t: number): Formant {\n return {\n frequency: lerp(a.frequency, b.frequency, t),\n amplitude: lerp(a.amplitude, b.amplitude, t),\n bandwidth: lerp(a.bandwidth, b.bandwidth, t),\n };\n}\n\nfunction lerpFormants(\n a: Formant[],\n b: Formant[],\n t: number\n): [Formant, Formant, Formant, Formant, Formant, Formant] {\n return a.map((f, i) => lerpFormant(f, b[i], t)) as [\n Formant,\n Formant,\n Formant,\n Formant,\n Formant,\n Formant,\n ];\n}\n\n/**\n * Interpolate formants for arbitrary vowel coordinates using bilinear interpolation.\n *\n * @param v - Vowel backness (0 = back, 1 = front)\n * @param h - Vowel height (0 = close, 1 = open)\n * @returns Interpolated formant array\n */\nexport function interpolateFormants(\n v: number,\n h: number\n): [Formant, Formant, Formant, Formant, Formant, Formant] {\n // Clamp inputs to valid range\n v = Math.max(0, Math.min(1, v));\n h = Math.max(0, Math.min(1, h));\n\n // Find surrounding grid indices\n let hLow = 0;\n let hHigh = 1;\n for (let i = 0; i < H_LEVELS.length - 1; i++) {\n if (h >= H_LEVELS[i] && h <= H_LEVELS[i + 1]) {\n hLow = i;\n hHigh = i + 1;\n break;\n }\n }\n\n let vLow = 0;\n let vHigh = 1;\n for (let i = 0; i < V_LEVELS.length - 1; i++) {\n if (v >= V_LEVELS[i] && v <= V_LEVELS[i + 1]) {\n vLow = i;\n vHigh = i + 1;\n break;\n }\n }\n\n // Get the four corner vowels\n const bottomLeft = findVowelByIpa(VOWEL_GRID[hLow][vLow]!)!;\n const bottomRight = findVowelByIpa(VOWEL_GRID[hLow][vHigh]!)!;\n const topLeft = findVowelByIpa(VOWEL_GRID[hHigh][vLow]!)!;\n const topRight = findVowelByIpa(VOWEL_GRID[hHigh][vHigh]!)!;\n\n // Calculate interpolation factors\n const hRange = H_LEVELS[hHigh] - H_LEVELS[hLow];\n const vRange = V_LEVELS[vHigh] - V_LEVELS[vLow];\n\n const tH = hRange > 0 ? (h - H_LEVELS[hLow]) / hRange : 0;\n const tV = vRange > 0 ? (v - V_LEVELS[vLow]) / vRange : 0;\n\n // Bilinear interpolation\n const bottom = lerpFormants(bottomLeft.formants, bottomRight.formants, tV);\n const top = lerpFormants(topLeft.formants, topRight.formants, tV);\n\n return lerpFormants(bottom, top, tH);\n}\n\nexport const vowels: VowelData[] = [\n {\n ipa: \"i\",\n v: 1,\n h: 0,\n formants: [\n { frequency: 215, amplitude: -10, bandwidth: 10 },\n { frequency: 1900, amplitude: -10, bandwidth: 18 },\n { frequency: 2630, amplitude: -8, bandwidth: 20 },\n { frequency: 3170, amplitude: -4, bandwidth: 30 },\n { frequency: 3710, amplitude: -15, bandwidth: 40 },\n { frequency: 6340, amplitude: -15, bandwidth: 150 },\n ],\n },\n {\n ipa: \"e\",\n v: 1,\n h: 1 / 3,\n formants: [\n { frequency: 410, amplitude: -1, bandwidth: 10 },\n { frequency: 2000, amplitude: -3, bandwidth: 15 },\n { frequency: 2570, amplitude: -2, bandwidth: 20 },\n { frequency: 2980, amplitude: -2, bandwidth: 30 },\n { frequency: 3900, amplitude: -5, bandwidth: 40 },\n { frequency: 5960, amplitude: -15, bandwidth: 150 },\n ],\n },\n {\n ipa: \"ɛ\",\n v: 1,\n h: 2 / 3,\n formants: [\n { frequency: 590, amplitude: 0, bandwidth: 10 },\n { frequency: 1700, amplitude: -4, bandwidth: 15 },\n { frequency: 2540, amplitude: -5, bandwidth: 30 },\n { frequency: 2800, amplitude: -12, bandwidth: 50 },\n { frequency: 3900, amplitude: -24, bandwidth: 40 },\n { frequency: 5600, amplitude: -15, bandwidth: 150 },\n ],\n },\n {\n ipa: \"y\",\n v: 0.5,\n h: 0,\n formants: [\n { frequency: 250, amplitude: -12, bandwidth: 10 },\n { frequency: 1750, amplitude: -9, bandwidth: 10 },\n { frequency: 2160, amplitude: -14, bandwidth: 20 },\n { frequency: 3060, amplitude: -11, bandwidth: 30 },\n { frequency: 3900, amplitude: -11, bandwidth: 40 },\n { frequency: 6120, amplitude: -15, bandwidth: 150 },\n ],\n },\n {\n ipa: \"œ\",\n v: 0.5,\n h: 1 / 3,\n formants: [\n { frequency: 350, amplitude: -6, bandwidth: 10 },\n { frequency: 1350, amplitude: -3, bandwidth: 10 },\n { frequency: 2250, amplitude: -8, bandwidth: 20 },\n { frequency: 3170, amplitude: -8, bandwidth: 30 },\n { frequency: 3900, amplitude: -10, bandwidth: 40 },\n { frequency: 6340, amplitude: -15, bandwidth: 150 },\n ],\n },\n {\n ipa: \"ø\",\n v: 0.5,\n h: 2 / 3,\n formants: [\n { frequency: 620, amplitude: -3, bandwidth: 10 },\n { frequency: 1300, amplitude: -3, bandwidth: 10 },\n { frequency: 2520, amplitude: -3, bandwidth: 20 },\n { frequency: 3310, amplitude: -7, bandwidth: 30 },\n { frequency: 3900, amplitude: -14, bandwidth: 40 },\n { frequency: 6620, amplitude: -15, bandwidth: 150 },\n ],\n },\n {\n ipa: \"u\",\n v: 0,\n h: 0,\n formants: [\n { frequency: 290, amplitude: -6, bandwidth: 10 },\n { frequency: 750, amplitude: -8, bandwidth: 10 },\n { frequency: 2300, amplitude: -13, bandwidth: 20 },\n { frequency: 3080, amplitude: -8, bandwidth: 30 },\n { frequency: 3900, amplitude: -9, bandwidth: 40 },\n { frequency: 6160, amplitude: -15, bandwidth: 150 },\n ],\n },\n {\n ipa: \"o\",\n v: 0,\n h: 1 / 3,\n formants: [\n { frequency: 440, amplitude: -6, bandwidth: 10 },\n { frequency: 750, amplitude: -1, bandwidth: 12 },\n { frequency: 2160, amplitude: -10, bandwidth: 20 },\n { frequency: 2860, amplitude: -6, bandwidth: 30 },\n { frequency: 3900, amplitude: -28, bandwidth: 40 },\n { frequency: 5720, amplitude: -15, bandwidth: 150 },\n ],\n },\n {\n ipa: \"ɔ\",\n v: 0,\n h: 2 / 3,\n formants: [\n { frequency: 610, amplitude: -3, bandwidth: 10 },\n { frequency: 950, amplitude: 0, bandwidth: 12 },\n { frequency: 2510, amplitude: -12, bandwidth: 20 },\n { frequency: 2830, amplitude: -15, bandwidth: 30 },\n { frequency: 3900, amplitude: -20, bandwidth: 40 },\n { frequency: 5660, amplitude: -15, bandwidth: 150 },\n ],\n },\n {\n ipa: \"a\",\n v: 0.5,\n h: 1,\n formants: [\n { frequency: 700, amplitude: 0, bandwidth: 13 },\n { frequency: 1200, amplitude: 0, bandwidth: 13 },\n { frequency: 2500, amplitude: -5, bandwidth: 40 },\n { frequency: 2800, amplitude: -7, bandwidth: 60 },\n { frequency: 3600, amplitude: -24, bandwidth: 40 },\n { frequency: 5600, amplitude: -15, bandwidth: 150 },\n ],\n },\n];\n","/**\n * Perceptual and Synth Parameters\n *\n * There are high-level, user-controllable parameters for the voice synthesiser.\n * They map to the perceptually meaningful dimensions described in Section 2.1 of the paper.\n * These are converted to the low-level synthesis parameters required by\n * the voice engine using the generateSynthParams function.\n *\n * All normalised parameters range from 0 to 1 unless otherwise noted.\n */\n\nimport { interpolateFormants, Formant } from \"./vowels\";\n\nexport type SynthFormant = {\n /** Formant centre frequency in Hz */\n F: number;\n /** Formant bandwidth in Hz */\n B: number;\n /** Formant amplitude (linear gain) */\n A: number;\n};\n\nexport type SynthParams = {\n /** Fundamental frequency in Hz */\n f0: number;\n /** Glottal formant centre frequency in Hz */\n Fg: number;\n /** Glottal formant bandwidth in Hz */\n Bg: number;\n /** Source amplitude */\n Ag: number;\n /** First stage spectral tilt attenuation in dB at 3 kHz */\n Tl1: number;\n /** Second stage spectral tilt attenuation in dB at 3 kHz */\n Tl2: number;\n /** Noise amplitude */\n An: number;\n /** Jitter depth: maximum f₀ perturbation as a fraction (0-0.3 for ±30%) */\n jitterDepth: number;\n /** Shimmer depth: maximum amplitude perturbation as a fraction (0-1 for ±100%) */\n shimmerDepth: number;\n /** Array of formant parameters (F, B, A) for each resonator */\n formants: SynthFormant[];\n /** Anti-formant centre frequency in Hz */\n F_BQ: number;\n /** Anti-formant quality factor */\n Q_BQ: number;\n /** Overall output gain (linear) */\n outputGain?: number;\n}\n\nexport type PerceptualParams = {\n /** (P) Normalised melodic position across the pitch range (0-1) */\n pitch: number;\n /** (P₀) Base MIDI note number (e.g., 48 for C3, 60 for C4) */\n pitchOffset: number;\n /** (E) Perceived force/dynamics of the voice (0-1) */\n vocalEffort: number;\n /** (H) Vertical tongue position: 0 = close (e.g., /i/, /u/), 1 = open (e.g., /a/) */\n vowelHeight: number;\n /** (V) Horizontal tongue position: 0 = back (e.g., /u/), 1 = front (e.g., /i/) */\n vowelBackness: number;\n /** (T) Degree of vocal fold adduction: 0 = lax, 1 = tense */\n tenseness: number;\n /** (B) Amount of aspiration noise (0-1) */\n breathiness: number;\n /** (R) Structural aperiodicities causing jitter/shimmer (0-1) */\n roughness: number;\n /** (S) Apparent size of the vocal tract: 0 = small/child, 1 = large/giant */\n vocalTractSize: number;\n /** (M) Vocal fold vibration mode: false = chest voice (M1), true = falsetto (M2) */\n isFalsetto: boolean;\n};\n\n/**\n * Feature flags for the voice parameter conversion.\n * All features are enabled by default (true). Set to false to disable.\n */\nexport type SynthFeatures = {\n /**\n * Attenuate formant amplitudes when harmonics coincide with formant frequencies.\n * Paper reference: Section 4.3.7\n */\n harmonicCoincidenceAttenuation?: boolean;\n /**\n * Scale formant frequencies by larynx position factor (K) and vocal tract size (αS).\n * Paper reference: Sections 4.3.2, 4.3.3\n */\n formantFrequencyScaling?: boolean;\n /**\n * Raise F1 with vocal effort and constrain it above f₀ + 50 Hz.\n * Paper reference: Section 4.3.4\n */\n f1Tuning?: boolean;\n /**\n * Constrain F2 above 2·f₀ + 50 Hz.\n * Paper reference: Section 4.3.5\n */\n f2Tuning?: boolean;\n /**\n * Scale the anti-resonance frequency (F_BQ) by vocal tract size (αS).\n * When disabled, uses nominal 4700 Hz.\n */\n antiResonanceScaling?: boolean;\n};\n\nconst DEFAULT_FEATURES: Required<SynthFeatures> = {\n harmonicCoincidenceAttenuation: true,\n formantFrequencyScaling: true,\n f1Tuning: true,\n f2Tuning: true,\n antiResonanceScaling: true,\n};\n\n/** Phonation threshold - below this vocal effort, no voiced sound is produced */\nconst E_THR = 0.2;\n\n/** Signal amplitude at phonation threshold */\nconst C_AG = 0.2;\n\n/** Nominal anti-formant frequency in Hz */\nconst F_BQ_BASE = 4700;\n\n/** Fixed anti-formant quality factor */\nconst Q_BQ = 2.5;\n\n/**\n * Converts MIDI note number to frequency in Hz.\n */\nfunction midiToFrequency(midi: number): number {\n return 440 * Math.pow(2, (midi - 69) / 12);\n}\n\n/**\n * Converts dB to linear amplitude.\n */\nfunction dbToLinear(db: number): number {\n return Math.pow(10, db / 20);\n}\n\n/**\n * Computes the open quotient (Oq) from tenseness, vocal effort, and mechanism.\n *\n * Paper reference: Section 4.2.2\n */\nfunction computeOq(T: number, E: number, M: 1 | 2): number {\n // Oq0 depends on mechanism and vocal effort\n const Oq0 = M === 1 ? 0.903 - 0.426 * E : 0.978 - 0.279 * E;\n\n // Oq varies with tenseness\n if (T <= 0.5) {\n return Math.pow(10, -2 * (1 - Oq0) * T);\n } else {\n return Math.pow(10, 2 * Oq0 * (1 - T) - 1);\n }\n}\n\n/**\n * Computes the asymmetry coefficient (αm) from tenseness and mechanism.\n *\n * Paper reference: Section 4.2.2\n */\nfunction computeAlphaM(T: number, M: 1 | 2): number {\n const alphaM0 = M === 1 ? 0.66 : 0.55;\n\n if (T <= 0.5) {\n return 0.5 + 2 * (alphaM0 - 0.5) * T;\n } else {\n return 0.9 - 2 * (0.9 - alphaM0) * (1 - T);\n }\n}\n\n/**\n * Computes the glottal formant bandwidth (Bg) from f0, Oq, and αm.\n *\n * Paper reference: Section 4.2.2\n */\nfunction computeBg(f0: number, Oq: number, alphaM: number): number {\n // Avoid division by zero for extreme αm values\n const tanArg = Math.PI * (1 - alphaM);\n const tanValue = Math.tan(Math.max(0.01, Math.min(Math.PI - 0.01, tanArg)));\n return f0 / (Oq * Math.abs(tanValue));\n}\n\n/**\n * Computes the spectral tilt parameters (Tl1, Tl2) from vocal effort and mechanism.\n *\n * Paper reference: Section 4.2.3\n */\nfunction computeSpectralTilt(E: number, M: 1 | 2): { Tl1: number; Tl2: number } {\n if (M === 1) {\n return {\n Tl1: 27 - 21 * E,\n Tl2: 11 - 11 * E,\n };\n } else {\n return {\n Tl1: 45 - 36 * E,\n Tl2: 20 - 18.5 * E,\n };\n }\n}\n\n/**\n * Computes the source amplitude (Ag) from vocal effort and open quotient.\n *\n * Paper reference: Section 4.2.4\n */\nfunction computeAg(E: number, Oq: number): number {\n if (E <= E_THR) {\n return 0;\n }\n return ((1 - C_AG) * ((E - E_THR) / (1 - E_THR)) + C_AG) / Oq;\n}\n\n/**\n * Computes the noise amplitude (An) from breathiness and vocal effort.\n *\n * Paper reference: Section 4.2.5\n */\nfunction computeAn(B: number, E: number): number {\n const isVoiced = E > E_THR;\n return isVoiced ? B : 1.5 * E * B;\n}\n\n/**\n * Computes the vocal tract scale factor (αS) from the size parameter.\n *\n * Paper reference: Section 4.3.2\n */\nfunction computeAlphaS(S: number): number {\n return 1.7 * S + 0.5; // Ranges from 0.5 to 2.2\n}\n\n/**\n * Computes the larynx position factor (K) from fundamental frequency.\n *\n * Paper reference: Section 4.3.3\n */\nfunction computeK(f0: number): number {\n return 1.25e-4 * f0 + 0.975;\n}\n\n/**\n * Computes the formant amplitude attenuation when a harmonic coincides with a formant.\n *\n * Paper reference: Section 4.3.7\n */\nfunction computeFormantAttenuation(\n f0: number,\n formantFreq: number,\n formantIndex: number\n): number {\n // Only apply to first three formants\n if (formantIndex >= 3) {\n return 0;\n }\n\n // ΔF varies with f0 (15 Hz at f0=50, 100 Hz at f0=1500)\n const deltaF = 15 + (100 - 15) * ((f0 - 50) / (1500 - 50));\n\n // Maximum attenuation varies by formant (10-25 dB)\n const attMax = [10, 15, 25][formantIndex];\n\n // Find the closest harmonic\n const harmonic = Math.round(formantFreq / f0);\n const harmonicFreq = harmonic * f0;\n const distance = Math.abs(harmonicFreq - formantFreq);\n\n if (distance < deltaF) {\n return (1 - distance / deltaF) * attMax;\n }\n return 0;\n}\n\n/**\n * Converts high-level perceptually relevant voice parameters to internal synthesizer parameters.\n *\n * This implements the parameter mapping rules from Section 4 of the paper,\n * converting perceptually meaningful high-level parameters to the low-level\n * synthesis parameters needed by the voice engine.\n *\n * @param params - The high-level, perceptual voice parameters\n * @param features - Optional feature flags to enable/disable specific behaviours (all enabled by default)\n */\nexport function generateSynthParams(\n params: PerceptualParams,\n features: SynthFeatures = {}\n): SynthParams {\n const opts = { ...DEFAULT_FEATURES, ...features };\n const {\n pitch: P,\n pitchOffset: P0,\n vocalEffort: E,\n vowelHeight: H,\n vowelBackness: V,\n tenseness: T,\n breathiness: B,\n roughness: R,\n vocalTractSize: S,\n isFalsetto,\n } = params;\n const M: 1 | 2 = isFalsetto ? 2 : 1;\n\n // Compute fundamental frequency\n const pitchMidi = P0 + 35 * P;\n const f0 = midiToFrequency(pitchMidi);\n\n // Compute intermediate voice source parameters\n const Oq = computeOq(T, E, M);\n const alphaM = computeAlphaM(T, M);\n\n // Compute glottal formant parameters\n const Fg = f0 / (2 * Oq);\n const Bg = computeBg(f0, Oq, alphaM);\n const Ag = computeAg(E, Oq);\n\n // Compute spectral tilt\n const { Tl1, Tl2 } = computeSpectralTilt(E, M);\n\n // Compute noise amplitude\n const An = computeAn(B, E);\n\n // Compute roughness parameters (jitter and shimmer)\n // Jitter: up to ±30% f₀ perturbation at max roughness\n const jitterDepth = R * 0.3;\n // Shimmer: up to ±100% amplitude perturbation at max roughness\n const shimmerDepth = R * 1.0;\n\n // Compute vocal tract scaling factors\n const alphaS = computeAlphaS(S);\n const K = computeK(f0);\n\n // Interpolate base formant values from vowel table\n const baseFormants = interpolateFormants(V, H);\n\n // Apply formant tuning rules and scaling\n const formants = baseFormants.map((formant: Formant, i: number) => {\n // Base frequency, optionally scaled by K and alphaS\n const scaleFactor = opts.formantFrequencyScaling ? K * alphaS : 1;\n let F = scaleFactor * formant.frequency;\n\n // F1 tuning (Section 4.3.4): raised with effort, constrained above f0\n if (i === 0 && opts.f1Tuning) {\n const F1Raised = scaleFactor * formant.frequency + (140 / (1 - E_THR)) * E - 70;\n F = Math.max(f0 + 50, F1Raised);\n }\n\n // F2 tuning (Section 4.3.5): constrained above 2*f0\n if (i === 1 && opts.f2Tuning) {\n F = Math.max(2 * f0 + 50, scaleFactor * formant.frequency);\n }\n\n // Amplitude correction for harmonic coincidence\n const attenuation = opts.harmonicCoincidenceAttenuation\n ? computeFormantAttenuation(f0, F, i)\n : 0;\n const A = dbToLinear(formant.amplitude - attenuation);\n\n return {\n F,\n B: formant.bandwidth,\n A,\n };\n });\n\n // Compute anti-formant frequency (optionally scaled by vocal tract size)\n const F_BQ = opts.antiResonanceScaling ? F_BQ_BASE * alphaS : F_BQ_BASE;\n\n return {\n f0,\n Fg,\n Bg,\n Ag,\n Tl1,\n Tl2,\n An,\n jitterDepth,\n shimmerDepth,\n formants,\n F_BQ,\n Q_BQ,\n };\n}\n"],"names":["Gain","ctx","gainNode","__publicField","params","processorCode","moduleRegistered","ensureModuleRegistered","blob","url","PulseTrain","workletNode","gain","pulseTrain","GlottalFormant","node","SpectralTilt","NoiseSource","highpassFilter","lowpassFilter","GlottalFlowDerivative","glottalFormant","spectralTilt","noiseSource","noiseModulator","outputGain","FormantResonator","FormantBank","resonators","inputGain","formant","resonator","i","AntiResonance","VocalTract","formantBank","antiResonance","Voice","glottalFlowDerivative","vocalTract","outputGainNode","V_LEVELS","H_LEVELS","VOWEL_GRID","findVowelByIpa","ipa","vowels","v","lerp","b","t","lerpFormant","lerpFormants","f","interpolateFormants","h","hLow","hHigh","vLow","vHigh","bottomLeft","bottomRight","topLeft","topRight","hRange","vRange","tH","tV","bottom","top","DEFAULT_FEATURES","E_THR","C_AG","F_BQ_BASE","Q_BQ","midiToFrequency","midi","dbToLinear","db","computeOq","T","E","M","Oq0","computeAlphaM","alphaM0","computeBg","f0","Oq","alphaM","tanArg","tanValue","computeSpectralTilt","computeAg","computeAn","B","computeAlphaS","S","computeK","computeFormantAttenuation","formantFreq","formantIndex","deltaF","attMax","harmonicFreq","distance","generateSynthParams","features","opts","P","P0","H","V","R","isFalsetto","pitchMidi","Fg","Bg","Ag","Tl1","Tl2","An","jitterDepth","shimmerDepth","alphaS","K","formants","scaleFactor","F","F1Raised","attenuation","A","F_BQ"],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { Node } from "./types";
2
+ export type AntiResonanceParams = {
3
+ /** Anti-formant centre frequency in Hz (nominally 4700 Hz, scaled by αS) */
4
+ F: number;
5
+ /** Anti-formant quality factor (nominally 2.5) */
6
+ Q: number;
7
+ };
8
+ /**
9
+ * Hypo-pharynx Anti-Resonance Filter (BQ)
10
+ *
11
+ * A notch filter in cascade with the parallel formants that models the
12
+ * anti-resonances caused by the hypo-pharynx cavity (the space between
13
+ * the larynx and the pharynx). This creates spectral dips at approximately
14
+ * 2.5–3.5 kHz and 4–5 kHz that contribute to natural voice timbre.
15
+ *
16
+ * The transfer function (Section 3.3.2) is:
17
+ *
18
+ * BQ(z) = (1 + β*z⁻¹ + z⁻²) / ((1 + α) + β*z⁻¹ + (1 - α)*z⁻²)
19
+ *
20
+ * Where:
21
+ * α = sin(2π * F * Ts) / (2 * Q)
22
+ * β = -2 * cos(2π * F * Ts)
23
+ *
24
+ * This is a bi-quadratic second-order notch filter that attenuates
25
+ * frequencies near the centre frequency F, with the notch width
26
+ * controlled by the quality factor Q.
27
+ *
28
+ * Paper reference: Section 3.3.2 (filter structure), Section 4.3.8 (parameter values)
29
+ *
30
+ * Input parameters:
31
+ * - F (anti-formant centre frequency in Hz) — nominally 4700 Hz,
32
+ * scaled by αS (vocal tract size factor)
33
+ * - Q (anti-formant quality factor) — fixed at 2.5 in the paper
34
+ */
35
+ export declare class AntiResonance implements Node<AntiResonanceParams> {
36
+ private ctx;
37
+ private workletNode;
38
+ in: AudioNode;
39
+ out: AudioNode;
40
+ private constructor();
41
+ /** Anti-formant centre frequency AudioParam (k-rate, 1000-10000 Hz) */
42
+ get F(): AudioParam;
43
+ /** Anti-formant quality factor AudioParam (k-rate, 0.5-20) */
44
+ get Q(): AudioParam;
45
+ /**
46
+ * Creates a new AntiResonance node.
47
+ *
48
+ * The AudioWorklet module is registered automatically on first use.
49
+ */
50
+ static create(ctx: AudioContext, params: AntiResonanceParams): Promise<AntiResonance>;
51
+ /**
52
+ * Updates the anti-resonance parameters.
53
+ * Sets AudioParams via setTargetAtTime for smooth transitions.
54
+ */
55
+ update(params: AntiResonanceParams): void;
56
+ destroy(): void;
57
+ }
58
+ //# sourceMappingURL=anti-resonance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anti-resonance.d.ts","sourceRoot":"","sources":["../../src/nodes/anti-resonance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,MAAM,mBAAmB,GAAG;IAChC,4EAA4E;IAC5E,CAAC,EAAE,MAAM,CAAC;IACV,kDAAkD;IAClD,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAiIF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,aAAc,YAAW,IAAI,CAAC,mBAAmB,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAe;IAC1B,OAAO,CAAC,WAAW,CAAmB;IAC/B,EAAE,EAAE,SAAS,CAAC;IACd,GAAG,EAAE,SAAS,CAAC;IAEtB,OAAO;IAOP,uEAAuE;IACvE,IAAI,CAAC,IAAI,UAAU,CAElB;IAED,8DAA8D;IAC9D,IAAI,CAAC,IAAI,UAAU,CAElB;IAED;;;;OAIG;WACU,MAAM,CACjB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,aAAa,CAAC;IAUzB;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAKzC,OAAO,IAAI,IAAI;CAGhB"}
@@ -0,0 +1,76 @@
1
+ import type { Node } from "./types";
2
+ import { FormantResonator, FormantResonatorParams } from "./formant-resonator";
3
+ export type FormantBankParams = {
4
+ /** Array of formant parameters, one per resonator */
5
+ formants: FormantResonatorParams[];
6
+ };
7
+ /**
8
+ * Formant Bank
9
+ *
10
+ * A parallel bank of formant resonators representing the vocal tract resonances.
11
+ * Each resonator is a 2-pole 2-zero bandpass filter (see FormantResonator).
12
+ *
13
+ * The input signal is fed to all resonators in parallel, and their outputs
14
+ * are summed to produce a single output.
15
+ *
16
+ * Signal flow:
17
+ *
18
+ * ┌─→ R1(F1, B1, A1) ─┐
19
+ * ├─→ R2(F2, B2, A2) ─┤
20
+ * Input ─────┼─→ R3(F3, B3, A3) ─┼─────→ Output (sum)
21
+ * ├─→ ... ─┤
22
+ * └─→ Rn(Fn, Bn, An) ─┘
23
+ *
24
+ * The paper uses six formants (R1-R6):
25
+ * - F1-F3: Primary formants that determine vowel identity
26
+ * - F4-F6: Higher formants that contribute to voice timbre
27
+ *
28
+ * This implementation accepts any number of formants, allowing flexibility
29
+ * for experimentation or simplified models.
30
+ *
31
+ * Paper reference: Section 3.3.1 (parallel formant structure)
32
+ *
33
+ * Input parameters:
34
+ * - formants: Array of {F, B, A} objects defining each resonator
35
+ *
36
+ * Note: The number of formants is fixed at creation time. Calling update()
37
+ * with a different array length will throw an error. To change the number
38
+ * of formants, destroy this node and create a new one.
39
+ */
40
+ export declare class FormantBank implements Node<FormantBankParams> {
41
+ private resonators;
42
+ private inputGain;
43
+ private outputGain;
44
+ in: AudioNode;
45
+ out: AudioNode;
46
+ private constructor();
47
+ /**
48
+ * Creates a new FormantBank node with the specified formants.
49
+ *
50
+ * All formant resonators are created in parallel for efficiency.
51
+ */
52
+ static create(ctx: AudioContext, params: FormantBankParams): Promise<FormantBank>;
53
+ /**
54
+ * Updates all formant parameters.
55
+ *
56
+ * The formants array must have the same length as the original.
57
+ * Each resonator receives its corresponding parameters.
58
+ *
59
+ * @throws Error if the formants array length doesn't match
60
+ */
61
+ update(params: FormantBankParams): void;
62
+ /**
63
+ * Returns the number of formant resonators in this bank.
64
+ */
65
+ get count(): number;
66
+ /**
67
+ * Returns the array of formant resonators for direct AudioParam access.
68
+ *
69
+ * Example usage:
70
+ * formantBank.formants[0].F.setValueAtTime(800, ctx.currentTime);
71
+ * formantBank.formants[1].B.linearRampToValueAtTime(100, ctx.currentTime + 0.1);
72
+ */
73
+ get formants(): readonly FormantResonator[];
74
+ destroy(): void;
75
+ }
76
+ //# sourceMappingURL=formant-bank.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formant-bank.d.ts","sourceRoot":"","sources":["../../src/nodes/formant-bank.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,qDAAqD;IACrD,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,WAAY,YAAW,IAAI,CAAC,iBAAiB,CAAC;IACzD,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,UAAU,CAAW;IAEtB,EAAE,EAAE,SAAS,CAAC;IACd,GAAG,EAAE,SAAS,CAAC;IAEtB,OAAO;IAYP;;;;OAIG;WACU,MAAM,CACjB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,WAAW,CAAC;IAsBvB;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAcvC;;OAEG;IACH,IAAI,KAAK,IAAI,MAAM,CAElB;IAED;;;;;;OAMG;IACH,IAAI,QAAQ,IAAI,SAAS,gBAAgB,EAAE,CAE1C;IAED,OAAO,IAAI,IAAI;CAYhB"}
@@ -0,0 +1,67 @@
1
+ import type { Node } from "./types";
2
+ export type FormantResonatorParams = {
3
+ /** Formant centre frequency in Hz */
4
+ F: number;
5
+ /** Formant bandwidth in Hz */
6
+ B: number;
7
+ /** Formant amplitude (linear gain) */
8
+ A: number;
9
+ };
10
+ /**
11
+ * Formant Resonator (Ri)
12
+ *
13
+ * A single formant filter representing one resonance of the vocal tract.
14
+ * Implemented as a 2-pole 2-zero digital resonator (bandpass filter) with
15
+ * controllable centre frequency (F), bandwidth (B), and amplitude (A).
16
+ *
17
+ * The transfer function (Section 3.3.1) is:
18
+ *
19
+ * R(z) = A * (1 - R) * (1 - R*z^{-2}) / (1 - 2R*cos(θ)*z^{-1} + R²*z^{-2})
20
+ *
21
+ * Where:
22
+ * R = e^(-π * B * Ts) (pole radius, Ts = sampling period)
23
+ * θ = 2π * F * Ts (pole angle)
24
+ *
25
+ * The vocal tract model uses six formant resonators (R1-R6) in parallel:
26
+ * - F1-F3: Primary formants that determine vowel identity
27
+ * - F4-F6: Higher formants that contribute to voice timbre
28
+ *
29
+ * Grouping F3, F4, and F5 closely together can produce the "singer's formant"
30
+ * characteristic of trained classical voices.
31
+ *
32
+ * Paper reference: Section 3.3.1 (filter structure), Section 4.3.1 (vowel table),
33
+ * Section 4.3.6 (bandwidths), Section 4.3.7 (amplitudes)
34
+ *
35
+ * Input parameters:
36
+ * - F (formant centre frequency in Hz) — interpolated from vowel table,
37
+ * scaled by αS (vocal tract size) and K (larynx position factor)
38
+ * - B (formant bandwidth in Hz) — interpolated from vowel table
39
+ * - A (formant amplitude, linear) — interpolated from vowel table,
40
+ * with correction when harmonics coincide with formant frequencies
41
+ */
42
+ export declare class FormantResonator implements Node<FormantResonatorParams> {
43
+ private ctx;
44
+ private workletNode;
45
+ in: AudioNode;
46
+ out: AudioNode;
47
+ private constructor();
48
+ /** Formant centre frequency AudioParam (a-rate, 100-8000 Hz) */
49
+ get F(): AudioParam;
50
+ /** Formant bandwidth AudioParam (a-rate, 20-1000 Hz) */
51
+ get B(): AudioParam;
52
+ /** Formant amplitude AudioParam (a-rate, 0-10) */
53
+ get A(): AudioParam;
54
+ /**
55
+ * Creates a new FormantResonator node.
56
+ *
57
+ * The AudioWorklet module is registered automatically on first use.
58
+ */
59
+ static create(ctx: AudioContext, params: FormantResonatorParams): Promise<FormantResonator>;
60
+ /**
61
+ * Updates the formant resonator parameters.
62
+ * Sets AudioParams via setTargetAtTime for smooth transitions.
63
+ */
64
+ update(params: FormantResonatorParams): void;
65
+ destroy(): void;
66
+ }
67
+ //# sourceMappingURL=formant-resonator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formant-resonator.d.ts","sourceRoot":"","sources":["../../src/nodes/formant-resonator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,MAAM,sBAAsB,GAAG;IACnC,qCAAqC;IACrC,CAAC,EAAE,MAAM,CAAC;IACV,8BAA8B;IAC9B,CAAC,EAAE,MAAM,CAAC;IACV,sCAAsC;IACtC,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAiIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,gBAAiB,YAAW,IAAI,CAAC,sBAAsB,CAAC;IACnE,OAAO,CAAC,GAAG,CAAe;IAC1B,OAAO,CAAC,WAAW,CAAmB;IAC/B,EAAE,EAAE,SAAS,CAAC;IACd,GAAG,EAAE,SAAS,CAAC;IAEtB,OAAO;IAOP,gEAAgE;IAChE,IAAI,CAAC,IAAI,UAAU,CAElB;IAED,wDAAwD;IACxD,IAAI,CAAC,IAAI,UAAU,CAElB;IAED,kDAAkD;IAClD,IAAI,CAAC,IAAI,UAAU,CAElB;IAED;;;;OAIG;WACU,MAAM,CACjB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,gBAAgB,CAAC;IAU5B;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAM5C,OAAO,IAAI,IAAI;CAGhB"}
@@ -0,0 +1,16 @@
1
+ import type { Node } from "./types";
2
+ export type GainParams = {
3
+ gain: number;
4
+ };
5
+ export declare class Gain implements Node<GainParams> {
6
+ private ctx;
7
+ private gainNode;
8
+ in: AudioNode;
9
+ out: AudioNode;
10
+ private constructor();
11
+ static create(ctx: AudioContext, params: GainParams): Promise<Gain>;
12
+ update(params: GainParams): void;
13
+ destroy(): void;
14
+ get gain(): AudioParam;
15
+ }
16
+ //# sourceMappingURL=gain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gain.d.ts","sourceRoot":"","sources":["../../src/nodes/gain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;CACd,CAAA;AAED,qBAAa,IAAK,YAAW,IAAI,CAAC,UAAU,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAe;IAC1B,OAAO,CAAC,QAAQ,CAAW;IACpB,EAAE,EAAE,SAAS,CAAC;IACd,GAAG,EAAE,SAAS,CAAC;IAEtB,OAAO;WAOM,MAAM,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzE,MAAM,CAAC,MAAM,EAAE,UAAU;IAIzB,OAAO;IAIP,IAAI,IAAI,IAAI,UAAU,CAErB;CACF"}
@@ -0,0 +1,119 @@
1
+ import type { Node } from "./types";
2
+ import { PulseTrain } from "./pulse-train";
3
+ import { GlottalFormant } from "./glottal-formant";
4
+ import { SpectralTilt } from "./spectral-tilt";
5
+ import { NoiseSource } from "./noise-source";
6
+ export type GlottalFlowDerivativeParams = {
7
+ /** Fundamental frequency in Hz (derived from P, P₀) */
8
+ f0: number;
9
+ /** Glottal formant centre frequency in Hz, computed as f0 / (2 * Oq) */
10
+ Fg: number;
11
+ /** Glottal formant bandwidth in Hz, computed from f0, Oq, and αm */
12
+ Bg: number;
13
+ /** Source amplitude, derived from E and Oq */
14
+ Ag: number;
15
+ /** First stage spectral tilt attenuation in dB at 3 kHz, derived from E and M */
16
+ Tl1: number;
17
+ /** Second stage spectral tilt attenuation in dB at 3 kHz, derived from E and M */
18
+ Tl2: number;
19
+ /** Noise amplitude, derived from B (breathiness) */
20
+ An: number;
21
+ /** Jitter depth: maximum f₀ perturbation as a fraction (0-0.3 for ±30%), derived from R */
22
+ jitterDepth: number;
23
+ /** Shimmer depth: maximum amplitude perturbation as a fraction (0-1 for ±100%), derived from R */
24
+ shimmerDepth: number;
25
+ };
26
+ /**
27
+ * Glottal Flow Derivative (GFD)
28
+ *
29
+ * The glottal flow derivative model represents the sound source produced by
30
+ * the vibrating vocal folds. It consists of a voiced component (periodic pulses
31
+ * shaped by the glottal formant and spectral tilt) and an unvoiced component
32
+ * (filtered noise for breathiness).
33
+ *
34
+ * The noise is modulated by the glottal flow derivative, so aspiration noise
35
+ * follows the glottal cycle - loudest during the open phase, quiet when closed.
36
+ *
37
+ * Signal flow:
38
+ *
39
+ * PulseTrain(f0) → GlottalFormant(Fg, Bg, Ag) → SpectralTilt(Tl1, Tl2) ──┬────→ Output
40
+ * │ ↑
41
+ * │ (mod) │
42
+ * ↓ │
43
+ * NoiseSource(An) ─────────────────────────────────────────────────→ [Multiply] ──┘
44
+ *
45
+ * Paper reference: Section 3.2 (overall structure)
46
+ *
47
+ * Input parameters:
48
+ * - f0 (fundamental frequency) — controls the pulse train period
49
+ * - Fg (glottal formant centre frequency) — computed as f0 / (2·Oq)
50
+ * - Bg (glottal formant bandwidth) — computed from f0, Oq, and αm
51
+ * - Ag (source amplitude) — derived from E, Oq, and R (shimmer)
52
+ * - Tl1 (first tilt stage attenuation in dB at 3 kHz) — derived from E and M
53
+ * - Tl2 (second tilt stage attenuation in dB at 3 kHz) — derived from E and M
54
+ * - An (noise amplitude) — derived from B (breathiness)
55
+ */
56
+ export declare class GlottalFlowDerivative implements Node<GlottalFlowDerivativeParams> {
57
+ private pulseTrain;
58
+ private glottalFormant;
59
+ private spectralTilt;
60
+ private noiseSource;
61
+ private noiseModulator;
62
+ private outputGain;
63
+ in: AudioNode | null;
64
+ out: AudioNode;
65
+ private constructor();
66
+ /**
67
+ * Creates a new GlottalFlowDerivative node.
68
+ *
69
+ * Sets up the voiced path (pulse train → glottal formant → spectral tilt)
70
+ * and the noise modulation path where noise is multiplied by the GFD signal.
71
+ */
72
+ static create(ctx: AudioContext, params: GlottalFlowDerivativeParams): Promise<GlottalFlowDerivative>;
73
+ /**
74
+ * Updates all glottal flow derivative parameters.
75
+ * Each sub-node receives its relevant parameters.
76
+ */
77
+ update(params: GlottalFlowDerivativeParams): void;
78
+ /**
79
+ * Returns the pulse train node for direct AudioParam access.
80
+ *
81
+ * Example usage:
82
+ * gfd.pulseTrainNode.f0.setValueAtTime(440, ctx.currentTime);
83
+ * gfd.pulseTrainNode.jitterDepth.linearRampToValueAtTime(0.1, ctx.currentTime + 0.5);
84
+ */
85
+ get pulseTrainNode(): PulseTrain;
86
+ /**
87
+ * Returns the glottal formant node for direct AudioParam access.
88
+ *
89
+ * Example usage:
90
+ * gfd.glottalFormantNode.Fg.setValueAtTime(220, ctx.currentTime);
91
+ * gfd.glottalFormantNode.Ag.setTargetAtTime(0.5, ctx.currentTime, 0.1);
92
+ */
93
+ get glottalFormantNode(): GlottalFormant;
94
+ /**
95
+ * Returns the spectral tilt node for direct AudioParam access.
96
+ *
97
+ * Example usage:
98
+ * gfd.spectralTiltNode.Tl1.setValueAtTime(10, ctx.currentTime);
99
+ */
100
+ get spectralTiltNode(): SpectralTilt;
101
+ /**
102
+ * Returns the noise source node for direct AudioParam access.
103
+ *
104
+ * Example usage:
105
+ * gfd.noiseSourceNode.An.setValueAtTime(0.3, ctx.currentTime);
106
+ */
107
+ get noiseSourceNode(): NoiseSource;
108
+ /**
109
+ * Starts the voiced excitation (pulse train).
110
+ */
111
+ start(): void;
112
+ /**
113
+ * Stops the voiced excitation (pulse train).
114
+ * Note: Noise source continues to produce output based on An parameter.
115
+ */
116
+ stop(): void;
117
+ destroy(): void;
118
+ }
119
+ //# sourceMappingURL=glottal-flow-derivative.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glottal-flow-derivative.d.ts","sourceRoot":"","sources":["../../src/nodes/glottal-flow-derivative.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,MAAM,MAAM,2BAA2B,GAAG;IACxC,uDAAuD;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,2FAA2F;IAC3F,WAAW,EAAE,MAAM,CAAC;IACpB,kGAAkG;IAClG,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,qBAAsB,YAAW,IAAI,CAAC,2BAA2B,CAAC;IAC7E,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,cAAc,CAAW;IACjC,OAAO,CAAC,UAAU,CAAO;IAElB,EAAE,EAAE,SAAS,GAAG,IAAI,CAAQ;IAC5B,GAAG,EAAE,SAAS,CAAC;IAEtB,OAAO;IAiBP;;;;;OAKG;WACU,MAAM,CACjB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,qBAAqB,CAAC;IA0CjC;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,2BAA2B,GAAG,IAAI;IAWjD;;;;;;OAMG;IACH,IAAI,cAAc,IAAI,UAAU,CAE/B;IAED;;;;;;OAMG;IACH,IAAI,kBAAkB,IAAI,cAAc,CAEvC;IAED;;;;;OAKG;IACH,IAAI,gBAAgB,IAAI,YAAY,CAEnC;IAED;;;;;OAKG;IACH,IAAI,eAAe,IAAI,WAAW,CAEjC;IAED;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;OAGG;IACH,IAAI,IAAI,IAAI;IAIZ,OAAO,IAAI,IAAI;CAQhB"}