@staticcanvas/typeflow 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +637 -0
  3. package/branding/dist/ico/typeflow-logo-16x16.ico +0 -0
  4. package/branding/dist/ico/typeflow-logo-256x256.ico +0 -0
  5. package/branding/dist/ico/typeflow-logo-32x32.ico +0 -0
  6. package/branding/dist/ico/typeflow-logo-48x48.ico +0 -0
  7. package/branding/dist/png/typeflow-logo-1024x1024.png +0 -0
  8. package/branding/dist/png/typeflow-logo-128x128.png +0 -0
  9. package/branding/dist/png/typeflow-logo-16x16.png +0 -0
  10. package/branding/dist/png/typeflow-logo-256x256.png +0 -0
  11. package/branding/dist/png/typeflow-logo-512x512.png +0 -0
  12. package/branding/dist/png/typeflow-logo-64x64.png +0 -0
  13. package/branding/dist/webp/typeflow-logo-1024x1024.webp +0 -0
  14. package/branding/dist/webp/typeflow-logo-128x128.webp +0 -0
  15. package/branding/dist/webp/typeflow-logo-16x16.webp +0 -0
  16. package/branding/dist/webp/typeflow-logo-256x256.webp +0 -0
  17. package/branding/dist/webp/typeflow-logo-32x32.webp +0 -0
  18. package/branding/dist/webp/typeflow-logo-48x48.webp +0 -0
  19. package/branding/dist/webp/typeflow-logo-512x512.webp +0 -0
  20. package/branding/dist/webp/typeflow-logo-64x64.webp +0 -0
  21. package/branding/gen-logo.ps1 +14 -0
  22. package/branding/typeflow-logo.svg +162 -0
  23. package/dist/typeflow/LICENSE +21 -0
  24. package/dist/typeflow/README.md +231 -0
  25. package/dist/typeflow/branding/dist/ico/typeflow-logo-16x16.ico +0 -0
  26. package/dist/typeflow/branding/dist/ico/typeflow-logo-256x256.ico +0 -0
  27. package/dist/typeflow/branding/dist/ico/typeflow-logo-32x32.ico +0 -0
  28. package/dist/typeflow/branding/dist/ico/typeflow-logo-48x48.ico +0 -0
  29. package/dist/typeflow/branding/dist/png/typeflow-logo-1024x1024.png +0 -0
  30. package/dist/typeflow/branding/dist/png/typeflow-logo-128x128.png +0 -0
  31. package/dist/typeflow/branding/dist/png/typeflow-logo-16x16.png +0 -0
  32. package/dist/typeflow/branding/dist/png/typeflow-logo-256x256.png +0 -0
  33. package/dist/typeflow/branding/dist/png/typeflow-logo-512x512.png +0 -0
  34. package/dist/typeflow/branding/dist/png/typeflow-logo-64x64.png +0 -0
  35. package/dist/typeflow/branding/dist/webp/typeflow-logo-1024x1024.webp +0 -0
  36. package/dist/typeflow/branding/dist/webp/typeflow-logo-128x128.webp +0 -0
  37. package/dist/typeflow/branding/dist/webp/typeflow-logo-16x16.webp +0 -0
  38. package/dist/typeflow/branding/dist/webp/typeflow-logo-256x256.webp +0 -0
  39. package/dist/typeflow/branding/dist/webp/typeflow-logo-32x32.webp +0 -0
  40. package/dist/typeflow/branding/dist/webp/typeflow-logo-48x48.webp +0 -0
  41. package/dist/typeflow/branding/dist/webp/typeflow-logo-512x512.webp +0 -0
  42. package/dist/typeflow/branding/dist/webp/typeflow-logo-64x64.webp +0 -0
  43. package/dist/typeflow/branding/gen-logo.ps1 +14 -0
  44. package/dist/typeflow/branding/typeflow-logo.svg +162 -0
  45. package/dist/typeflow/dist/typeflow-caret.cjs +47 -0
  46. package/dist/typeflow/dist/typeflow-caret.cjs.map +1 -0
  47. package/dist/typeflow/dist/typeflow-caret.esm.js +47 -0
  48. package/dist/typeflow/dist/typeflow-caret.esm.js.map +1 -0
  49. package/dist/typeflow/dist/typeflow-caret.esm.min.js +2 -0
  50. package/dist/typeflow/dist/typeflow-caret.esm.min.js.map +1 -0
  51. package/dist/typeflow/dist/typeflow-caret.js +47 -0
  52. package/dist/typeflow/dist/typeflow-caret.js.map +1 -0
  53. package/dist/typeflow/dist/typeflow-caret.min.js +2 -0
  54. package/dist/typeflow/dist/typeflow-caret.min.js.map +1 -0
  55. package/dist/typeflow/dist/typeflow-caret.mjs +47 -0
  56. package/dist/typeflow/dist/typeflow-caret.mjs.map +1 -0
  57. package/dist/typeflow/dist/typeflow-debug.cjs +109 -0
  58. package/dist/typeflow/dist/typeflow-debug.cjs.map +1 -0
  59. package/dist/typeflow/dist/typeflow-debug.esm.js +109 -0
  60. package/dist/typeflow/dist/typeflow-debug.esm.js.map +1 -0
  61. package/dist/typeflow/dist/typeflow-debug.esm.min.js +2 -0
  62. package/dist/typeflow/dist/typeflow-debug.esm.min.js.map +1 -0
  63. package/dist/typeflow/dist/typeflow-debug.js +109 -0
  64. package/dist/typeflow/dist/typeflow-debug.js.map +1 -0
  65. package/dist/typeflow/dist/typeflow-debug.min.js +2 -0
  66. package/dist/typeflow/dist/typeflow-debug.min.js.map +1 -0
  67. package/dist/typeflow/dist/typeflow-debug.mjs +109 -0
  68. package/dist/typeflow/dist/typeflow-debug.mjs.map +1 -0
  69. package/dist/typeflow/dist/typeflow-extchars.cjs +66 -0
  70. package/dist/typeflow/dist/typeflow-extchars.cjs.map +1 -0
  71. package/dist/typeflow/dist/typeflow-extchars.esm.js +66 -0
  72. package/dist/typeflow/dist/typeflow-extchars.esm.js.map +1 -0
  73. package/dist/typeflow/dist/typeflow-extchars.esm.min.js +2 -0
  74. package/dist/typeflow/dist/typeflow-extchars.esm.min.js.map +1 -0
  75. package/dist/typeflow/dist/typeflow-extchars.js +66 -0
  76. package/dist/typeflow/dist/typeflow-extchars.js.map +1 -0
  77. package/dist/typeflow/dist/typeflow-extchars.min.js +2 -0
  78. package/dist/typeflow/dist/typeflow-extchars.min.js.map +1 -0
  79. package/dist/typeflow/dist/typeflow-extchars.mjs +66 -0
  80. package/dist/typeflow/dist/typeflow-extchars.mjs.map +1 -0
  81. package/dist/typeflow/dist/typeflow-keystroke.cjs +47 -0
  82. package/dist/typeflow/dist/typeflow-keystroke.cjs.map +1 -0
  83. package/dist/typeflow/dist/typeflow-keystroke.esm.js +47 -0
  84. package/dist/typeflow/dist/typeflow-keystroke.esm.js.map +1 -0
  85. package/dist/typeflow/dist/typeflow-keystroke.esm.min.js +2 -0
  86. package/dist/typeflow/dist/typeflow-keystroke.esm.min.js.map +1 -0
  87. package/dist/typeflow/dist/typeflow-keystroke.js +47 -0
  88. package/dist/typeflow/dist/typeflow-keystroke.js.map +1 -0
  89. package/dist/typeflow/dist/typeflow-keystroke.min.js +2 -0
  90. package/dist/typeflow/dist/typeflow-keystroke.min.js.map +1 -0
  91. package/dist/typeflow/dist/typeflow-keystroke.mjs +47 -0
  92. package/dist/typeflow/dist/typeflow-keystroke.mjs.map +1 -0
  93. package/dist/typeflow/dist/typeflow-metrics.cjs +76 -0
  94. package/dist/typeflow/dist/typeflow-metrics.cjs.map +1 -0
  95. package/dist/typeflow/dist/typeflow-metrics.esm.js +76 -0
  96. package/dist/typeflow/dist/typeflow-metrics.esm.js.map +1 -0
  97. package/dist/typeflow/dist/typeflow-metrics.esm.min.js +2 -0
  98. package/dist/typeflow/dist/typeflow-metrics.esm.min.js.map +1 -0
  99. package/dist/typeflow/dist/typeflow-metrics.js +76 -0
  100. package/dist/typeflow/dist/typeflow-metrics.js.map +1 -0
  101. package/dist/typeflow/dist/typeflow-metrics.min.js +2 -0
  102. package/dist/typeflow/dist/typeflow-metrics.min.js.map +1 -0
  103. package/dist/typeflow/dist/typeflow-metrics.mjs +76 -0
  104. package/dist/typeflow/dist/typeflow-metrics.mjs.map +1 -0
  105. package/dist/typeflow/dist/typeflow-parallel.cjs +3 -0
  106. package/dist/typeflow/dist/typeflow-parallel.cjs.map +1 -0
  107. package/dist/typeflow/dist/typeflow-parallel.esm.js +3 -0
  108. package/dist/typeflow/dist/typeflow-parallel.esm.js.map +1 -0
  109. package/dist/typeflow/dist/typeflow-parallel.esm.min.js +2 -0
  110. package/dist/typeflow/dist/typeflow-parallel.esm.min.js.map +1 -0
  111. package/dist/typeflow/dist/typeflow-parallel.js +3 -0
  112. package/dist/typeflow/dist/typeflow-parallel.js.map +1 -0
  113. package/dist/typeflow/dist/typeflow-parallel.min.js +2 -0
  114. package/dist/typeflow/dist/typeflow-parallel.min.js.map +1 -0
  115. package/dist/typeflow/dist/typeflow-parallel.mjs +3 -0
  116. package/dist/typeflow/dist/typeflow-parallel.mjs.map +1 -0
  117. package/dist/typeflow/dist/typeflow-webaudio.cjs +81 -0
  118. package/dist/typeflow/dist/typeflow-webaudio.cjs.map +1 -0
  119. package/dist/typeflow/dist/typeflow-webaudio.esm.js +81 -0
  120. package/dist/typeflow/dist/typeflow-webaudio.esm.js.map +1 -0
  121. package/dist/typeflow/dist/typeflow-webaudio.esm.min.js +2 -0
  122. package/dist/typeflow/dist/typeflow-webaudio.esm.min.js.map +1 -0
  123. package/dist/typeflow/dist/typeflow-webaudio.js +81 -0
  124. package/dist/typeflow/dist/typeflow-webaudio.js.map +1 -0
  125. package/dist/typeflow/dist/typeflow-webaudio.min.js +2 -0
  126. package/dist/typeflow/dist/typeflow-webaudio.min.js.map +1 -0
  127. package/dist/typeflow/dist/typeflow-webaudio.mjs +81 -0
  128. package/dist/typeflow/dist/typeflow-webaudio.mjs.map +1 -0
  129. package/dist/typeflow/dist/typeflow.cjs +277 -0
  130. package/dist/typeflow/dist/typeflow.cjs.map +1 -0
  131. package/dist/typeflow/dist/typeflow.esm.js +277 -0
  132. package/dist/typeflow/dist/typeflow.esm.js.map +1 -0
  133. package/dist/typeflow/dist/typeflow.esm.min.js +2 -0
  134. package/dist/typeflow/dist/typeflow.esm.min.js.map +1 -0
  135. package/dist/typeflow/dist/typeflow.js +277 -0
  136. package/dist/typeflow/dist/typeflow.js.map +1 -0
  137. package/dist/typeflow/dist/typeflow.min.js +2 -0
  138. package/dist/typeflow/dist/typeflow.min.js.map +1 -0
  139. package/dist/typeflow/dist/typeflow.mjs +277 -0
  140. package/dist/typeflow/dist/typeflow.mjs.map +1 -0
  141. package/dist/typeflow/jsr.jsonc +27 -0
  142. package/dist/typeflow/package.json +79 -0
  143. package/dist/typeflow/src/typeflow-audio-presets.js +137 -0
  144. package/dist/typeflow/src/typeflow-caret.d.ts +65 -0
  145. package/dist/typeflow/src/typeflow-caret.js +303 -0
  146. package/dist/typeflow/src/typeflow-debug.d.ts +34 -0
  147. package/dist/typeflow/src/typeflow-debug.js +223 -0
  148. package/dist/typeflow/src/typeflow-extchars.d.ts +28 -0
  149. package/dist/typeflow/src/typeflow-extchars.js +132 -0
  150. package/dist/typeflow/src/typeflow-keystroke.d.ts +28 -0
  151. package/dist/typeflow/src/typeflow-keystroke.js +220 -0
  152. package/dist/typeflow/src/typeflow-metrics.d.ts +38 -0
  153. package/dist/typeflow/src/typeflow-metrics.js +556 -0
  154. package/dist/typeflow/src/typeflow-parallel.d.ts +36 -0
  155. package/dist/typeflow/src/typeflow-parallel.js +157 -0
  156. package/dist/typeflow/src/typeflow-webaudio.d.ts +83 -0
  157. package/dist/typeflow/src/typeflow-webaudio.js +517 -0
  158. package/dist/typeflow/src/typeflow.d.ts +328 -0
  159. package/dist/typeflow/src/typeflow.js +2119 -0
  160. package/jsr.jsonc +27 -0
  161. package/package.json +133 -0
  162. package/src/typeflow-audio-presets.js +137 -0
  163. package/src/typeflow-caret.d.ts +65 -0
  164. package/src/typeflow-caret.js +303 -0
  165. package/src/typeflow-debug.d.ts +34 -0
  166. package/src/typeflow-debug.js +223 -0
  167. package/src/typeflow-extchars.d.ts +28 -0
  168. package/src/typeflow-extchars.js +132 -0
  169. package/src/typeflow-keystroke.d.ts +28 -0
  170. package/src/typeflow-keystroke.js +220 -0
  171. package/src/typeflow-metrics.d.ts +38 -0
  172. package/src/typeflow-metrics.js +556 -0
  173. package/src/typeflow-parallel.d.ts +36 -0
  174. package/src/typeflow-parallel.js +157 -0
  175. package/src/typeflow-webaudio.d.ts +83 -0
  176. package/src/typeflow-webaudio.js +517 -0
  177. package/src/typeflow.d.ts +328 -0
  178. package/src/typeflow.js +2119 -0
@@ -0,0 +1,81 @@
1
+ /*! typeflow-webaudio | MIT */
2
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TypeflowWebAudio={})}(this,function(e){"use strict";var t=Object.defineProperty,n=Object.defineProperties,a=Object.getOwnPropertyDescriptors,i=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,c=(e,n,a)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[n]=a,l=(e,t,n)=>c(e,"symbol"!=typeof t?t+"":t,n);const s=e=>(e&&"object"==typeof e&&!Object.isFrozen(e)&&(Object.values(e).forEach(s),Object.freeze(e)),e),u=(e,t,n,a,i,r={})=>{var o,c,l;return{oscillator:e,frequency:{start:t,end:n},gain:{start:a,end:.001},duration:i,filter:null!=(o=r.filter)?o:null,detune:{base:null!=(c=r.detune)?c:0,variance:null!=(l=r.variance)?l:.06}}},d=(e,t)=>({version:1,actions:{type:e,erase:null!=t?t:e}}),p=s({"mechanical-blue":d([u("triangle",3400,900,.18,.008),u("sine",200,50,.32,.035)],[u("triangle",900,180,.12,.025)]),"mechanical-red":d([u("sine",220,70,.26,.028)],[u("sine",150,55,.12,.022)]),"mechanical-brown":d([u("triangle",240,75,.27,.032)],[u("triangle",170,60,.13,.024)]),"soft-keyboard":d([u("sine",520,280,.08,.018,{filter:{type:"lowpass",frequency:1200,q:.7}})],[u("sine",320,180,.06,.016)]),typewriter:d([u("square",320,90,.17,.022)],[u("square",180,70,.13,.026)]),"terminal-beep":d([u("square",880,880,.08,.025)],[u("square",440,330,.06,.03)]),synth:d([u("sawtooth",950,180,.12,.045)],[u("sawtooth",420,120,.09,.04)]),correction:d([u("triangle",360,110,.1,.035)],[u("triangle",260,80,.14,.045)])}),m=s({clicky:"mechanical-blue",mechanical:"mechanical-red",tactile:"mechanical-brown",teletype:"typewriter",vintage:"typewriter","terminal-8bit":"terminal-beep","8bit":"terminal-beep",beep:"terminal-beep","cyber-synth":"synth",neon:"synth",bubble:"soft-keyboard",pop:"soft-keyboard"});function y(e){if(!e||1!==e.version||!e.actions||"object"!=typeof e.actions)throw new TypeError("TypeFlowWebAudio: preset must use schema version 1.");const t={version:1,actions:{}};for(const n of["type","erase"]){const a=e.actions[n];if(!Array.isArray(a)||0===a.length||a.length>4)throw new TypeError(`TypeFlowWebAudio: actions.${n} must contain 1-4 oscillator layers.`);t.actions[n]=a.map(e=>{var t,n,a,i,r,o,c,l,s;const u=String(null!=(t=e.oscillator)?t:"sine");if(!["sine","square","sawtooth","triangle"].includes(u))throw new RangeError("TypeFlowWebAudio: invalid oscillator type.");const d=Number(e.duration),p=Number(null==(n=e.frequency)?void 0:n.start),m=Number(null==(a=e.frequency)?void 0:a.end),y=Number(null==(i=e.gain)?void 0:i.start),f=Number(null==(r=e.gain)?void 0:r.end);if(!(d>=.001&&d<=2))throw new RangeError("TypeFlowWebAudio: duration must be 0.001-2 seconds.");if(!(p>=20&&p<=2e4&&m>=20&&m<=2e4))throw new RangeError("TypeFlowWebAudio: frequency must be 20-20000 Hz.");if(!(y>=0&&y<=1&&f>0&&f<=1))throw new RangeError("TypeFlowWebAudio: gain must be within safe bounds.");const w=Number(null!=(c=null==(o=e.detune)?void 0:o.variance)?c:0);if(!(w>=0&&w<=.5))throw new RangeError("TypeFlowWebAudio: detune variance must be 0-0.5.");const b=null===e.filter||void 0===e.filter?null:{type:String(null!=(l=e.filter.type)?l:"lowpass"),frequency:Math.max(20,Math.min(2e4,Number(e.filter.frequency)||1e3)),q:Math.max(1e-4,Math.min(1e3,Number(e.filter.q)||1))};if(b&&!["lowpass","highpass","bandpass","notch"].includes(b.type))throw new RangeError("TypeFlowWebAudio: invalid filter type.");return{oscillator:u,frequency:{start:p,end:m},gain:{start:y,end:f},duration:d,filter:b,detune:{base:Number(null==(s=e.detune)?void 0:s.base)||0,variance:w}}})}return s(t)}const f=class e{
3
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
4
+ * @description Initializes or returns the shared Web Audio Context singleton.
5
+ * Automatically attaches unlock listeners to resume audio on the first user interaction.
6
+ * @returns {AudioContext | null} The active AudioContext instance or null in non-browser environments.
7
+ *
8
+ * -------------
9
+ *
10
+ * @example
11
+ * const ctx = TypeFlowWebAudio.getContext();
12
+ * console.log('Audio state:', ctx?.state);
13
+ */
14
+ static getContext(){var t;if("undefined"==typeof window)return null;if(!e.ctx){const n=window.AudioContext||window.webkitAudioContext;if(!n)return null;const a=null==(t=window.navigator)?void 0:t.userActivation;if(a&&!a.isActive&&!a.hasBeenActive){if(!e.unlockHandler){const t=()=>{window.removeEventListener("pointerdown",t),window.removeEventListener("keydown",t),e.unlockHandler=null,e.getContext()};e.unlockHandler=t,window.addEventListener("pointerdown",t,{once:!0}),window.addEventListener("keydown",t,{once:!0})}return null}try{e.ctx=new n}catch(e){return null}e.masterGain=e.ctx.createGain(),e.masterGain.gain.setValueAtTime(e.isMuted?0:e.volume,e.ctx.currentTime),e.masterGain.connect(e.ctx.destination)}return"suspended"===e.ctx.state&&e.ctx.resume().catch(()=>{}),e.ctx}
15
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
16
+ * @description Sets the master volume level for synthesized keystrokes.
17
+ * @param {number} vol - Volume level from 0.0 (silent) to 1.0 (maximum).
18
+ * @returns {void}
19
+ *
20
+ * -------------
21
+ *
22
+ * @Properties
23
+ * @param {number} vol - Floating-point number between 0.0 and 1.0.
24
+ *
25
+ * -------------
26
+ *
27
+ * @example
28
+ * TypeFlowWebAudio.setVolume(0.15); // Set subtle background volume
29
+ */static setVolume(t){e.volume=Math.max(0,Math.min(1,t)),e.masterGain&&e.ctx&&!e.isMuted&&e.masterGain.gain.setValueAtTime(e.volume,e.ctx.currentTime)}
30
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
31
+ * @description Toggles or forces the mute state of procedural keystroke audio.
32
+ * @param {boolean} [force] - Optional boolean to explicitly force muted (true) or unmuted (false).
33
+ * @returns {void}
34
+ *
35
+ * -------------
36
+ *
37
+ * @Properties
38
+ * @param {boolean} [force] - If omitted, flips the current mute state.
39
+ *
40
+ * -------------
41
+ *
42
+ * @example
43
+ * TypeFlowWebAudio.mute(); // Toggle mute
44
+ * TypeFlowWebAudio.mute(true); // Force mute
45
+ * TypeFlowWebAudio.mute(false); // Force unmute
46
+ */static mute(t){e.isMuted=void 0!==t?t:!e.isMuted,e.masterGain&&e.ctx&&e.masterGain.gain.setValueAtTime(e.isMuted?0:e.volume,e.ctx.currentTime)}
47
+ /** Registers a validated instance-safe preset. Built-in collisions require `overwrite: true`. */static registerAudioPreset(t,n,a={}){const i=String(null!=t?t:"").trim().toLowerCase();if(!/^[a-z0-9][a-z0-9-]*$/.test(i))throw new TypeError("TypeFlowWebAudio: invalid preset name.");if(!a.overwrite&&(i in p||i in m||e.customPresets.has(i)))throw new Error(`TypeFlowWebAudio: preset \`${i}\` already exists.`);return e.customPresets.set(i,y(n)),i}
48
+ /** Removes a custom preset. Immutable built-ins cannot be removed. */static removeAudioPreset(t){return e.customPresets.delete(String(null!=t?t:"").trim().toLowerCase())}
49
+ /** Removes every custom preset without affecting immutable built-ins. */static resetAudioPresets(){e.customPresets.clear()}
50
+ /** Returns a frozen custom or built-in preset, resolving compatibility aliases. */static getAudioPreset(t){var n,a;const i=String(null!=t?t:"").trim().toLowerCase();return e.customPresets.has(i)?e.customPresets.get(i):null!=(a=p[null!=(n=m[i])?n:i])?a:null}
51
+ /** Stops and disconnects a scoped set of voices. */static stopVoices(t){Array.from(t).forEach(t=>{var n;try{t.oscillator.stop()}catch(e){}t.nodes.forEach(e=>{try{e.disconnect()}catch(e){}}),null==(n=t.scope)||n.delete(t),e.activeVoices.delete(t)})}
52
+ /** Stops and disconnects all voices created by this extension. */static stopAll(){e.stopVoices(e.activeVoices)}
53
+ /** Tears down voices and the shared AudioContext so repeated setup is clean. */static destroy(){return t=this,n=null,a=function*(){var t,n;e.stopAll(),e.unlockHandler&&"undefined"!=typeof window&&(window.removeEventListener("pointerdown",e.unlockHandler),window.removeEventListener("keydown",e.unlockHandler),e.unlockHandler=null);try{null==(t=e.masterGain)||t.disconnect()}catch(e){}e.panners.forEach(e=>{try{e.disconnect()}catch(e){}}),e.panners.clear(),(null==(n=e.ctx)?void 0:n.close)&&(yield e.ctx.close().catch(()=>{})),e.ctx=null,e.masterGain=null},new Promise((e,i)=>{var r=e=>{try{c(a.next(e))}catch(e){i(e)}},o=e=>{try{c(a.throw(e))}catch(e){i(e)}},c=t=>t.done?e(t.value):Promise.resolve(t.value).then(r,o);c((a=a.apply(t,n)).next())});var t,n,a}static playPreset(t,n,a,i){var r,o,c,l;const s="erase"===a.action?"erase":"type",u=t.currentTime;for(const d of n.actions[s]){for(;e.activeVoices.size>=e.maxPolyphony;){const t=e.activeVoices.values().next().value;try{t.oscillator.stop()}catch(e){}t.nodes.forEach(e=>{try{e.disconnect()}catch(e){}}),null==(r=t.scope)||r.delete(t),e.activeVoices.delete(t)}const n=null!=(o=a.pitchVariance)?o:d.detune.variance,s=1+(2*Math.random()-1)*n,p=t.createOscillator(),m=t.createGain(),y=[p,m];p.type=d.oscillator,p.detune.setValueAtTime(d.detune.base,u),p.frequency.setValueAtTime(d.frequency.start*s,u),p.frequency.exponentialRampToValueAtTime(d.frequency.end*s,u+d.duration),m.gain.setValueAtTime(d.gain.start,u),m.gain.exponentialRampToValueAtTime(d.gain.end,u+d.duration),p.connect(m);let f=m;if(d.filter&&"function"==typeof t.createBiquadFilter){const e=t.createBiquadFilter();e.type=d.filter.type,e.frequency.setValueAtTime(d.filter.frequency,u),e.Q.setValueAtTime(d.filter.q,u),m.connect(e),f=e,y.push(e)}f.connect(i);const w={oscillator:p,nodes:y,scope:a.voiceScope};e.activeVoices.add(w),null==(c=a.voiceScope)||c.add(w),null==(l=p.addEventListener)||l.call(p,"ended",()=>{var t;y.forEach(e=>{try{e.disconnect()}catch(e){}}),null==(t=a.voiceScope)||t.delete(w),e.activeVoices.delete(w)},{once:!0}),p.start(u),p.stop(u+d.duration)}}
54
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
55
+ * @description Synthesizes and plays a procedural audio keystroke sound in real time without audio assets.
56
+ * @param {string} [profile='mechanical-blue'] - Sound profile name ('mechanical-blue', 'mechanical-red', 'mechanical-brown', 'teletype', 'cyber-synth', 'terminal-8bit', 'bubble').
57
+ * @param {Object} [options={}] - Keystroke playback parameters.
58
+ * @returns {void}
59
+ *
60
+ * -------------
61
+ *
62
+ * @Properties
63
+ * @param {string} [profile='mechanical-blue'] - Synthesizer profile.
64
+ * @param {string} [options.char=' '] - Typed character (differentiates spacebar, return bells, normal keys).
65
+ * @param {number} [options.pan=0] - Stereo pan from -1.0 (full left) to +1.0 (full right).
66
+ * @param {number} [options.pitchVariance=0.06] - Organic pitch variation percentage (±0.06 = ±6%).
67
+ *
68
+ * -------------
69
+ *
70
+ * @example
71
+ *
72
+ * // Play a clicky switch sound
73
+ * TypeFlowWebAudio.play('mechanical-blue');
74
+ *
75
+ * // Play a cyber synth keystroke panned slightly to the right
76
+ * TypeFlowWebAudio.play('cyber-synth', { char: 'A', pan: 0.3 });
77
+ *
78
+ * // Play a teletype carriage bell on newline
79
+ * TypeFlowWebAudio.play('teletype', { char: '\n' });
80
+ */static play(t="mechanical-blue",n={}){var a,i;if(!0===n.respectReducedMotion&&"undefined"!=typeof window&&(null==(a=window.matchMedia)?void 0:a.call(window,"(prefers-reduced-motion: reduce)").matches))return;const r=e.getContext();if(!r||e.isMuted)return;const o=r.currentTime,c=n.char||" ",l=1+(2*Math.random()-1)*(null!=(i=n.pitchVariance)?i:.06);let s=e.masterGain;if("function"==typeof r.createStereoPanner&&"number"==typeof n.pan){const t=Math.max(-1,Math.min(1,n.pan));let a=e.panners.get(t);a||(a=r.createStereoPanner(),a.pan.setValueAtTime(t,o),a.connect(e.masterGain),e.panners.set(t,a)),s=a}const u=String(t).toLowerCase(),d=e.getAudioPreset(u);if(d)e.playPreset(r,d,n,s);else if("mechanical-blue"===u||"clicky"===u){const e=" "===c||"\n"===c,t=e?140:200,n=r.createOscillator(),a=r.createGain();n.type="triangle",n.frequency.setValueAtTime((e?2800:3400)*l,o),a.gain.setValueAtTime(.2,o),a.gain.exponentialRampToValueAtTime(.001,o+.008),n.connect(a),a.connect(s),n.start(o),n.stop(o+.008);const i=r.createOscillator(),u=r.createGain();i.type="sine",i.frequency.setValueAtTime(t*l,o),i.frequency.exponentialRampToValueAtTime(50,o+.035),u.gain.setValueAtTime(.35,o),u.gain.exponentialRampToValueAtTime(.001,o+.035),i.connect(u),u.connect(s),i.start(o),i.stop(o+.035)}else if("mechanical-red"===u||"mechanical-brown"===u||"tactile"===u||"mechanical"===u){const e=r.createOscillator(),t=r.createGain();e.type=u.includes("brown")?"triangle":"sine",e.frequency.setValueAtTime(220*l,o),e.frequency.exponentialRampToValueAtTime(70,o+.028),t.gain.setValueAtTime(.28,o),t.gain.exponentialRampToValueAtTime(.001,o+.028),e.connect(t),t.connect(s),e.start(o),e.stop(o+.028)}else if("teletype"===u||"vintage"===u||"typewriter"===u)if("\n"===c){const e=r.createOscillator(),t=r.createGain();e.type="sine",e.frequency.setValueAtTime(1760,o),t.gain.setValueAtTime(.3,o),t.gain.exponentialRampToValueAtTime(.001,o+.35),e.connect(t),t.connect(s),e.start(o),e.stop(o+.35)}else{const e=r.createOscillator(),t=r.createGain();e.type="square",e.frequency.setValueAtTime(320*l,o),e.frequency.exponentialRampToValueAtTime(90,o+.022),t.gain.setValueAtTime(.18,o),t.gain.exponentialRampToValueAtTime(.001,o+.022),e.connect(t),t.connect(s),e.start(o),e.stop(o+.022)}else if("cyber-synth"===u||"synth"===u||"neon"===u){const e=r.createOscillator(),t=r.createGain();e.type="sawtooth",e.frequency.setValueAtTime(950*l,o),e.frequency.exponentialRampToValueAtTime(180,o+.045),t.gain.setValueAtTime(.12,o),t.gain.exponentialRampToValueAtTime(.001,o+.045),e.connect(t),t.connect(s),e.start(o),e.stop(o+.045)}else if("terminal-8bit"===u||"8bit"===u||"beep"===u){const e=r.createOscillator(),t=r.createGain();e.type="square",e.frequency.setValueAtTime((" "===c?440:880)*l,o),t.gain.setValueAtTime(.08,o),t.gain.exponentialRampToValueAtTime(.001,o+.025),e.connect(t),t.connect(s),e.start(o),e.stop(o+.025)}else if("bubble"===u||"pop"===u){const e=r.createOscillator(),t=r.createGain();e.type="sine",e.frequency.setValueAtTime(450*l,o),e.frequency.exponentialRampToValueAtTime(1100*l,o+.03),t.gain.setValueAtTime(.25,o),t.gain.exponentialRampToValueAtTime(.001,o+.035),e.connect(t),t.connect(s),e.start(o),e.stop(o+.035)}}};l(f,"ctx",null),l(f,"masterGain",null),l(f,"isMuted",!1),l(f,"volume",.25),l(f,"maxPolyphony",24),l(f,"customPresets",new Map),l(f,"activeVoices",new Set),l(f,"panners",new Map),l(f,"unlockHandler",null);let w=f;function b(e="mechanical-blue",t={}){const l=new Set,s=()=>{return w.play(e,(s=((e,t)=>{for(var n in t||(t={}))r.call(t,n)&&c(e,n,t[n]);if(i)for(var n of i(t))o.call(t,n)&&c(e,n,t[n]);return e})({},t),n(s,a({voiceScope:l}))));var s};return s.stop=()=>w.stopVoices(l),s}"undefined"!=typeof window&&(window.TypeFlowWebAudio=w,window.createKeystrokeAudio=b),e.AUDIO_PRESETS=p,e.AUDIO_PRESET_ALIASES=m,e.AUDIO_PRESET_SCHEMA_VERSION=1,e.TypeFlowWebAudio=w,e.createKeystrokeAudio=b,e.validateAudioPreset=y,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
81
+ //# sourceMappingURL=typeflow-webaudio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeflow-webaudio.js","sources":["../../../src/typeflow-audio-presets.js","../../../src/typeflow-webaudio.js"],"sourcesContent":["export const AUDIO_PRESET_SCHEMA_VERSION = 1;\n\nconst freeze = (value) => {\n if (value && typeof value === 'object' && !Object.isFrozen(value)) {\n Object.values(value).forEach(freeze);\n Object.freeze(value);\n }\n return value;\n};\n\nconst layer = (oscillator, start, end, gain, duration, extras = {}) => ({\n oscillator,\n frequency: { start, end },\n gain: { start: gain, end: 0.001 },\n duration,\n filter: extras.filter ?? null,\n detune: { base: extras.detune ?? 0, variance: extras.variance ?? 0.06 },\n});\n\nconst preset = (type, erase) => ({\n version: AUDIO_PRESET_SCHEMA_VERSION,\n actions: { type, erase: erase ?? type },\n});\n\nexport const AUDIO_PRESETS = freeze({\n 'mechanical-blue': preset(\n [layer('triangle', 3400, 900, 0.18, 0.008), layer('sine', 200, 50, 0.32, 0.035)],\n [layer('triangle', 900, 180, 0.12, 0.025)]\n ),\n 'mechanical-red': preset(\n [layer('sine', 220, 70, 0.26, 0.028)],\n [layer('sine', 150, 55, 0.12, 0.022)]\n ),\n 'mechanical-brown': preset(\n [layer('triangle', 240, 75, 0.27, 0.032)],\n [layer('triangle', 170, 60, 0.13, 0.024)]\n ),\n 'soft-keyboard': preset(\n [\n layer('sine', 520, 280, 0.08, 0.018, {\n filter: { type: 'lowpass', frequency: 1200, q: 0.7 },\n }),\n ],\n [layer('sine', 320, 180, 0.06, 0.016)]\n ),\n typewriter: preset(\n [layer('square', 320, 90, 0.17, 0.022)],\n [layer('square', 180, 70, 0.13, 0.026)]\n ),\n 'terminal-beep': preset(\n [layer('square', 880, 880, 0.08, 0.025)],\n [layer('square', 440, 330, 0.06, 0.03)]\n ),\n synth: preset(\n [layer('sawtooth', 950, 180, 0.12, 0.045)],\n [layer('sawtooth', 420, 120, 0.09, 0.04)]\n ),\n correction: preset(\n [layer('triangle', 360, 110, 0.1, 0.035)],\n [layer('triangle', 260, 80, 0.14, 0.045)]\n ),\n});\n\nexport const AUDIO_PRESET_ALIASES = freeze({\n clicky: 'mechanical-blue',\n mechanical: 'mechanical-red',\n tactile: 'mechanical-brown',\n teletype: 'typewriter',\n vintage: 'typewriter',\n 'terminal-8bit': 'terminal-beep',\n '8bit': 'terminal-beep',\n beep: 'terminal-beep',\n 'cyber-synth': 'synth',\n neon: 'synth',\n bubble: 'soft-keyboard',\n pop: 'soft-keyboard',\n});\n\nexport function validateAudioPreset(config) {\n if (\n !config ||\n config.version !== AUDIO_PRESET_SCHEMA_VERSION ||\n !config.actions ||\n typeof config.actions !== 'object'\n ) {\n throw new TypeError(\n `TypeFlowWebAudio: preset must use schema version ${AUDIO_PRESET_SCHEMA_VERSION}.`\n );\n }\n const output = { version: AUDIO_PRESET_SCHEMA_VERSION, actions: {} };\n for (const action of ['type', 'erase']) {\n const layers = config.actions[action];\n if (!Array.isArray(layers) || layers.length === 0 || layers.length > 4) {\n throw new TypeError(\n `TypeFlowWebAudio: actions.${action} must contain 1-4 oscillator layers.`\n );\n }\n output.actions[action] = layers.map((candidate) => {\n const oscillator = String(candidate.oscillator ?? 'sine');\n if (!['sine', 'square', 'sawtooth', 'triangle'].includes(oscillator))\n throw new RangeError('TypeFlowWebAudio: invalid oscillator type.');\n const duration = Number(candidate.duration);\n const start = Number(candidate.frequency?.start);\n const end = Number(candidate.frequency?.end);\n const gainStart = Number(candidate.gain?.start);\n const gainEnd = Number(candidate.gain?.end);\n if (!(duration >= 0.001 && duration <= 2))\n throw new RangeError('TypeFlowWebAudio: duration must be 0.001-2 seconds.');\n if (!(start >= 20 && start <= 20000 && end >= 20 && end <= 20000))\n throw new RangeError('TypeFlowWebAudio: frequency must be 20-20000 Hz.');\n if (!(gainStart >= 0 && gainStart <= 1 && gainEnd > 0 && gainEnd <= 1))\n throw new RangeError('TypeFlowWebAudio: gain must be within safe bounds.');\n const variance = Number(candidate.detune?.variance ?? 0);\n if (!(variance >= 0 && variance <= 0.5))\n throw new RangeError('TypeFlowWebAudio: detune variance must be 0-0.5.');\n const filter =\n candidate.filter === null || candidate.filter === undefined\n ? null\n : {\n type: String(candidate.filter.type ?? 'lowpass'),\n frequency: Math.max(20, Math.min(20000, Number(candidate.filter.frequency) || 1000)),\n q: Math.max(0.0001, Math.min(1000, Number(candidate.filter.q) || 1)),\n };\n if (filter && !['lowpass', 'highpass', 'bandpass', 'notch'].includes(filter.type))\n throw new RangeError('TypeFlowWebAudio: invalid filter type.');\n return {\n oscillator,\n frequency: { start, end },\n gain: { start: gainStart, end: gainEnd },\n duration,\n filter,\n detune: { base: Number(candidate.detune?.base) || 0, variance },\n };\n });\n }\n return freeze(output);\n}\n","/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow WebAudio - Procedural Audio Synthesis for Typing Interfaces.\n * Synthesizes zero-asset, real-time procedural keystrokes including mechanical switches,\n * vintage teletype strikes, retro terminal beeps, and FM cyber synths with spatial panning.\n *\n * @module @staticcanvas/typeflow/webaudio\n */\n\nimport {\n AUDIO_PRESETS,\n AUDIO_PRESET_ALIASES,\n AUDIO_PRESET_SCHEMA_VERSION,\n validateAudioPreset,\n} from './typeflow-audio-presets.js';\nexport { AUDIO_PRESETS, AUDIO_PRESET_ALIASES, AUDIO_PRESET_SCHEMA_VERSION, validateAudioPreset };\n\nexport class TypeFlowWebAudio {\n static ctx = null;\n static masterGain = null;\n static isMuted = false;\n static volume = 0.25;\n static maxPolyphony = 24;\n static customPresets = new Map();\n static activeVoices = new Set();\n static panners = new Map();\n static unlockHandler = null;\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Initializes or returns the shared Web Audio Context singleton.\n * Automatically attaches unlock listeners to resume audio on the first user interaction.\n * @returns {AudioContext | null} The active AudioContext instance or null in non-browser environments.\n *\n * -------------\n *\n * @example\n * const ctx = TypeFlowWebAudio.getContext();\n * console.log('Audio state:', ctx?.state);\n */\n static getContext() {\n if (typeof window === 'undefined') return null;\n\n if (!TypeFlowWebAudio.ctx) {\n const AudioCtx = window.AudioContext || window.webkitAudioContext;\n if (!AudioCtx) return null;\n\n const activation = window.navigator?.userActivation;\n if (activation && !activation.isActive && !activation.hasBeenActive) {\n if (!TypeFlowWebAudio.unlockHandler) {\n const unlock = () => {\n window.removeEventListener('pointerdown', unlock);\n window.removeEventListener('keydown', unlock);\n TypeFlowWebAudio.unlockHandler = null;\n TypeFlowWebAudio.getContext();\n };\n TypeFlowWebAudio.unlockHandler = unlock;\n window.addEventListener('pointerdown', unlock, { once: true });\n window.addEventListener('keydown', unlock, { once: true });\n }\n return null;\n }\n\n try {\n TypeFlowWebAudio.ctx = new AudioCtx();\n } catch {\n return null;\n }\n TypeFlowWebAudio.masterGain = TypeFlowWebAudio.ctx.createGain();\n TypeFlowWebAudio.masterGain.gain.setValueAtTime(\n TypeFlowWebAudio.isMuted ? 0 : TypeFlowWebAudio.volume,\n TypeFlowWebAudio.ctx.currentTime\n );\n TypeFlowWebAudio.masterGain.connect(TypeFlowWebAudio.ctx.destination);\n }\n\n if (TypeFlowWebAudio.ctx.state === 'suspended') {\n TypeFlowWebAudio.ctx.resume().catch(() => {});\n }\n\n return TypeFlowWebAudio.ctx;\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Sets the master volume level for synthesized keystrokes.\n * @param {number} vol - Volume level from 0.0 (silent) to 1.0 (maximum).\n * @returns {void}\n *\n * -------------\n *\n * @Properties\n * @param {number} vol - Floating-point number between 0.0 and 1.0.\n *\n * -------------\n *\n * @example\n * TypeFlowWebAudio.setVolume(0.15); // Set subtle background volume\n */\n static setVolume(vol) {\n TypeFlowWebAudio.volume = Math.max(0, Math.min(1, vol));\n if (TypeFlowWebAudio.masterGain && TypeFlowWebAudio.ctx && !TypeFlowWebAudio.isMuted) {\n TypeFlowWebAudio.masterGain.gain.setValueAtTime(\n TypeFlowWebAudio.volume,\n TypeFlowWebAudio.ctx.currentTime\n );\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Toggles or forces the mute state of procedural keystroke audio.\n * @param {boolean} [force] - Optional boolean to explicitly force muted (true) or unmuted (false).\n * @returns {void}\n *\n * -------------\n *\n * @Properties\n * @param {boolean} [force] - If omitted, flips the current mute state.\n *\n * -------------\n *\n * @example\n * TypeFlowWebAudio.mute(); // Toggle mute\n * TypeFlowWebAudio.mute(true); // Force mute\n * TypeFlowWebAudio.mute(false); // Force unmute\n */\n static mute(force) {\n TypeFlowWebAudio.isMuted = force !== undefined ? force : !TypeFlowWebAudio.isMuted;\n if (TypeFlowWebAudio.masterGain && TypeFlowWebAudio.ctx) {\n TypeFlowWebAudio.masterGain.gain.setValueAtTime(\n TypeFlowWebAudio.isMuted ? 0 : TypeFlowWebAudio.volume,\n TypeFlowWebAudio.ctx.currentTime\n );\n }\n }\n\n /** Registers a validated instance-safe preset. Built-in collisions require `overwrite: true`. */\n static registerAudioPreset(name, config, options = {}) {\n const key = String(name ?? '')\n .trim()\n .toLowerCase();\n if (!/^[a-z0-9][a-z0-9-]*$/.test(key))\n throw new TypeError('TypeFlowWebAudio: invalid preset name.');\n if (\n !options.overwrite &&\n (key in AUDIO_PRESETS ||\n key in AUDIO_PRESET_ALIASES ||\n TypeFlowWebAudio.customPresets.has(key))\n ) {\n throw new Error(`TypeFlowWebAudio: preset \\`${key}\\` already exists.`);\n }\n TypeFlowWebAudio.customPresets.set(key, validateAudioPreset(config));\n return key;\n }\n\n /** Removes a custom preset. Immutable built-ins cannot be removed. */\n static removeAudioPreset(name) {\n return TypeFlowWebAudio.customPresets.delete(\n String(name ?? '')\n .trim()\n .toLowerCase()\n );\n }\n\n /** Removes every custom preset without affecting immutable built-ins. */\n static resetAudioPresets() {\n TypeFlowWebAudio.customPresets.clear();\n }\n\n /** Returns a frozen custom or built-in preset, resolving compatibility aliases. */\n static getAudioPreset(name) {\n const key = String(name ?? '')\n .trim()\n .toLowerCase();\n if (TypeFlowWebAudio.customPresets.has(key)) return TypeFlowWebAudio.customPresets.get(key);\n return AUDIO_PRESETS[AUDIO_PRESET_ALIASES[key] ?? key] ?? null;\n }\n\n /** Stops and disconnects a scoped set of voices. */\n static stopVoices(voices) {\n Array.from(voices).forEach((voice) => {\n try {\n voice.oscillator.stop();\n } catch {\n /* already stopped */\n }\n voice.nodes.forEach((node) => {\n try {\n node.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n voice.scope?.delete(voice);\n TypeFlowWebAudio.activeVoices.delete(voice);\n });\n }\n\n /** Stops and disconnects all voices created by this extension. */\n static stopAll() {\n TypeFlowWebAudio.stopVoices(TypeFlowWebAudio.activeVoices);\n }\n\n /** Tears down voices and the shared AudioContext so repeated setup is clean. */\n static async destroy() {\n TypeFlowWebAudio.stopAll();\n if (TypeFlowWebAudio.unlockHandler && typeof window !== 'undefined') {\n window.removeEventListener('pointerdown', TypeFlowWebAudio.unlockHandler);\n window.removeEventListener('keydown', TypeFlowWebAudio.unlockHandler);\n TypeFlowWebAudio.unlockHandler = null;\n }\n try {\n TypeFlowWebAudio.masterGain?.disconnect();\n } catch {\n /* already disconnected */\n }\n TypeFlowWebAudio.panners.forEach((panner) => {\n try {\n panner.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n TypeFlowWebAudio.panners.clear();\n if (TypeFlowWebAudio.ctx?.close) await TypeFlowWebAudio.ctx.close().catch(() => {});\n TypeFlowWebAudio.ctx = null;\n TypeFlowWebAudio.masterGain = null;\n }\n\n static playPreset(ctx, config, options, destination) {\n const action = options.action === 'erase' ? 'erase' : 'type';\n const now = ctx.currentTime;\n for (const layer of config.actions[action]) {\n while (TypeFlowWebAudio.activeVoices.size >= TypeFlowWebAudio.maxPolyphony) {\n const oldest = TypeFlowWebAudio.activeVoices.values().next().value;\n try {\n oldest.oscillator.stop();\n } catch {\n /* already stopped */\n }\n oldest.nodes.forEach((node) => {\n try {\n node.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n oldest.scope?.delete(oldest);\n TypeFlowWebAudio.activeVoices.delete(oldest);\n }\n const variance = options.pitchVariance ?? layer.detune.variance;\n const jitter = 1 + (Math.random() * 2 - 1) * variance;\n const oscillator = ctx.createOscillator();\n const gain = ctx.createGain();\n const nodes = [oscillator, gain];\n oscillator.type = layer.oscillator;\n oscillator.detune.setValueAtTime(layer.detune.base, now);\n oscillator.frequency.setValueAtTime(layer.frequency.start * jitter, now);\n oscillator.frequency.exponentialRampToValueAtTime(\n layer.frequency.end * jitter,\n now + layer.duration\n );\n gain.gain.setValueAtTime(layer.gain.start, now);\n gain.gain.exponentialRampToValueAtTime(layer.gain.end, now + layer.duration);\n oscillator.connect(gain);\n let tail = gain;\n if (layer.filter && typeof ctx.createBiquadFilter === 'function') {\n const filter = ctx.createBiquadFilter();\n filter.type = layer.filter.type;\n filter.frequency.setValueAtTime(layer.filter.frequency, now);\n filter.Q.setValueAtTime(layer.filter.q, now);\n gain.connect(filter);\n tail = filter;\n nodes.push(filter);\n }\n tail.connect(destination);\n const voice = { oscillator, nodes, scope: options.voiceScope };\n TypeFlowWebAudio.activeVoices.add(voice);\n options.voiceScope?.add(voice);\n oscillator.addEventListener?.(\n 'ended',\n () => {\n nodes.forEach((node) => {\n try {\n node.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n options.voiceScope?.delete(voice);\n TypeFlowWebAudio.activeVoices.delete(voice);\n },\n { once: true }\n );\n oscillator.start(now);\n oscillator.stop(now + layer.duration);\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Synthesizes and plays a procedural audio keystroke sound in real time without audio assets.\n * @param {string} [profile='mechanical-blue'] - Sound profile name ('mechanical-blue', 'mechanical-red', 'mechanical-brown', 'teletype', 'cyber-synth', 'terminal-8bit', 'bubble').\n * @param {Object} [options={}] - Keystroke playback parameters.\n * @returns {void}\n *\n * -------------\n *\n * @Properties\n * @param {string} [profile='mechanical-blue'] - Synthesizer profile.\n * @param {string} [options.char=' '] - Typed character (differentiates spacebar, return bells, normal keys).\n * @param {number} [options.pan=0] - Stereo pan from -1.0 (full left) to +1.0 (full right).\n * @param {number} [options.pitchVariance=0.06] - Organic pitch variation percentage (±0.06 = ±6%).\n *\n * -------------\n *\n * @example\n *\n * // Play a clicky switch sound\n * TypeFlowWebAudio.play('mechanical-blue');\n *\n * // Play a cyber synth keystroke panned slightly to the right\n * TypeFlowWebAudio.play('cyber-synth', { char: 'A', pan: 0.3 });\n *\n * // Play a teletype carriage bell on newline\n * TypeFlowWebAudio.play('teletype', { char: '\\n' });\n */\n static play(profile = 'mechanical-blue', options = {}) {\n if (\n options.respectReducedMotion === true &&\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches\n )\n return;\n const ctx = TypeFlowWebAudio.getContext();\n if (!ctx || TypeFlowWebAudio.isMuted) return;\n\n const t = ctx.currentTime;\n const char = options.char || ' ';\n const jitter = 1 + (Math.random() * 2 - 1) * (options.pitchVariance ?? 0.06);\n\n // Optional spatial stereo panner\n let dest = TypeFlowWebAudio.masterGain;\n if (typeof ctx.createStereoPanner === 'function' && typeof options.pan === 'number') {\n const pan = Math.max(-1, Math.min(1, options.pan));\n let panner = TypeFlowWebAudio.panners.get(pan);\n if (!panner) {\n panner = ctx.createStereoPanner();\n panner.pan.setValueAtTime(pan, t);\n panner.connect(TypeFlowWebAudio.masterGain);\n TypeFlowWebAudio.panners.set(pan, panner);\n }\n dest = panner;\n }\n\n const normProfile = String(profile).toLowerCase();\n const presetConfig = TypeFlowWebAudio.getAudioPreset(normProfile);\n if (presetConfig) {\n TypeFlowWebAudio.playPreset(ctx, presetConfig, options, dest);\n return;\n }\n\n // 1. Mechanical Clicky Switch (Blue style)\n if (normProfile === 'mechanical-blue' || normProfile === 'clicky') {\n const isSpace = char === ' ' || char === '\\n';\n const baseFreq = isSpace ? 140 : 200;\n\n // Click snap (high transient)\n const snapOsc = ctx.createOscillator();\n const snapGain = ctx.createGain();\n snapOsc.type = 'triangle';\n snapOsc.frequency.setValueAtTime((isSpace ? 2800 : 3400) * jitter, t);\n snapGain.gain.setValueAtTime(0.2, t);\n snapGain.gain.exponentialRampToValueAtTime(0.001, t + 0.008);\n snapOsc.connect(snapGain);\n snapGain.connect(dest);\n snapOsc.start(t);\n snapOsc.stop(t + 0.008);\n\n // Bottom-out thud (low resonant body)\n const thudOsc = ctx.createOscillator();\n const thudGain = ctx.createGain();\n thudOsc.type = 'sine';\n thudOsc.frequency.setValueAtTime(baseFreq * jitter, t);\n thudOsc.frequency.exponentialRampToValueAtTime(50, t + 0.035);\n thudGain.gain.setValueAtTime(0.35, t);\n thudGain.gain.exponentialRampToValueAtTime(0.001, t + 0.035);\n thudOsc.connect(thudGain);\n thudGain.connect(dest);\n thudOsc.start(t);\n thudOsc.stop(t + 0.035);\n }\n // 2. Mechanical Linear / Tactile (Red / Brown style)\n else if (\n normProfile === 'mechanical-red' ||\n normProfile === 'mechanical-brown' ||\n normProfile === 'tactile' ||\n normProfile === 'mechanical'\n ) {\n const thudOsc = ctx.createOscillator();\n const thudGain = ctx.createGain();\n thudOsc.type = normProfile.includes('brown') ? 'triangle' : 'sine';\n thudOsc.frequency.setValueAtTime(220 * jitter, t);\n thudOsc.frequency.exponentialRampToValueAtTime(70, t + 0.028);\n thudGain.gain.setValueAtTime(0.28, t);\n thudGain.gain.exponentialRampToValueAtTime(0.001, t + 0.028);\n thudOsc.connect(thudGain);\n thudGain.connect(dest);\n thudOsc.start(t);\n thudOsc.stop(t + 0.028);\n }\n // 3. Vintage Teletype / Solenoid Strike & Carriage Bell\n else if (\n normProfile === 'teletype' ||\n normProfile === 'vintage' ||\n normProfile === 'typewriter'\n ) {\n if (char === '\\n') {\n const bellOsc = ctx.createOscillator();\n const bellGain = ctx.createGain();\n bellOsc.type = 'sine';\n bellOsc.frequency.setValueAtTime(1760, t);\n bellGain.gain.setValueAtTime(0.3, t);\n bellGain.gain.exponentialRampToValueAtTime(0.001, t + 0.35);\n bellOsc.connect(bellGain);\n bellGain.connect(dest);\n bellOsc.start(t);\n bellOsc.stop(t + 0.35);\n } else {\n const clackOsc = ctx.createOscillator();\n const clackGain = ctx.createGain();\n clackOsc.type = 'square';\n clackOsc.frequency.setValueAtTime(320 * jitter, t);\n clackOsc.frequency.exponentialRampToValueAtTime(90, t + 0.022);\n clackGain.gain.setValueAtTime(0.18, t);\n clackGain.gain.exponentialRampToValueAtTime(0.001, t + 0.022);\n clackOsc.connect(clackGain);\n clackGain.connect(dest);\n clackOsc.start(t);\n clackOsc.stop(t + 0.022);\n }\n }\n // 4. Sci-Fi Cyber Synth / Laser Chirp\n else if (normProfile === 'cyber-synth' || normProfile === 'synth' || normProfile === 'neon') {\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sawtooth';\n osc.frequency.setValueAtTime(950 * jitter, t);\n osc.frequency.exponentialRampToValueAtTime(180, t + 0.045);\n gain.gain.setValueAtTime(0.12, t);\n gain.gain.exponentialRampToValueAtTime(0.001, t + 0.045);\n osc.connect(gain);\n gain.connect(dest);\n osc.start(t);\n osc.stop(t + 0.045);\n }\n // 5. Retro 8-bit Terminal Beep\n else if (normProfile === 'terminal-8bit' || normProfile === '8bit' || normProfile === 'beep') {\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'square';\n osc.frequency.setValueAtTime((char === ' ' ? 440 : 880) * jitter, t);\n gain.gain.setValueAtTime(0.08, t);\n gain.gain.exponentialRampToValueAtTime(0.001, t + 0.025);\n osc.connect(gain);\n gain.connect(dest);\n osc.start(t);\n osc.stop(t + 0.025);\n }\n // 6. Liquid Bubble Pop\n else if (normProfile === 'bubble' || normProfile === 'pop') {\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sine';\n osc.frequency.setValueAtTime(450 * jitter, t);\n osc.frequency.exponentialRampToValueAtTime(1100 * jitter, t + 0.03);\n gain.gain.setValueAtTime(0.25, t);\n gain.gain.exponentialRampToValueAtTime(0.001, t + 0.035);\n osc.connect(gain);\n gain.connect(dest);\n osc.start(t);\n osc.stop(t + 0.035);\n }\n }\n}\n\n/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Creates an audio callback function configured for TypeFlow's `audio` option.\n * @param {string} [profile='mechanical-blue'] - Sound profile identifier.\n * @param {Object} [options={}] - Custom audio synthesis options.\n * @returns {() => void} Zero-argument callback function invoked per keystroke.\n *\n * -------------\n *\n * @Properties\n * @param {string} [profile='mechanical-blue'] - Sound profile name.\n * @param {number} [options.pan=0] - Stereo pan (-1.0 to 1.0).\n * @param {number} [options.pitchVariance=0.06] - Random pitch variance.\n *\n * -------------\n *\n * @example\n * import { TypeFlow } from '@staticcanvas/typeflow';\n * import { createKeystrokeAudio } from '@staticcanvas/typeflow/webaudio';\n *\n * TypeFlow.type('#headline', 'Mechanical Sound Effects', {\n * audio: createKeystrokeAudio('mechanical-blue', { pan: 0.1 }),\n * speed: 40,\n * });\n */\nexport function createKeystrokeAudio(profile = 'mechanical-blue', options = {}) {\n const voiceScope = new Set();\n const play = () => TypeFlowWebAudio.play(profile, { ...options, voiceScope });\n play.stop = () => TypeFlowWebAudio.stopVoices(voiceScope);\n return play;\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowWebAudio = TypeFlowWebAudio;\n window.createKeystrokeAudio = createKeystrokeAudio;\n}\n"],"names":["freeze","value","Object","isFrozen","values","forEach","layer","oscillator","start","end","gain","duration","extras","frequency","filter","_a","detune","base","_b","variance","_c","preset","type","erase","version","actions","AUDIO_PRESETS","q","typewriter","synth","correction","AUDIO_PRESET_ALIASES","clicky","mechanical","tactile","teletype","vintage","beep","neon","bubble","pop","validateAudioPreset","config","TypeError","output","action","layers","Array","isArray","length","map","candidate","String","includes","RangeError","Number","gainStart","_d","gainEnd","_e","_g","_f","_h","Math","max","min","_i","_TypeFlowWebAudio","getContext","window","ctx","AudioCtx","AudioContext","webkitAudioContext","activation","navigator","userActivation","isActive","hasBeenActive","unlockHandler","unlock","removeEventListener","addEventListener","once","e","masterGain","createGain","setValueAtTime","isMuted","volume","currentTime","connect","destination","state","resume","catch","setVolume","vol","mute","force","registerAudioPreset","name","options","key","trim","toLowerCase","test","overwrite","customPresets","has","Error","set","removeAudioPreset","delete","resetAudioPresets","clear","getAudioPreset","get","stopVoices","voices","from","voice","stop","nodes","node","disconnect","scope","activeVoices","stopAll","destroy","this","panners","panner","close","playPreset","now","size","maxPolyphony","oldest","next","pitchVariance","jitter","random","createOscillator","exponentialRampToValueAtTime","tail","createBiquadFilter","Q","push","voiceScope","add","call","play","profile","respectReducedMotion","matchMedia","matches","t","char","dest","createStereoPanner","pan","normProfile","presetConfig","isSpace","baseFreq","snapOsc","snapGain","thudOsc","thudGain","bellOsc","bellGain","clackOsc","clackGain","osc","__publicField","Map","Set","TypeFlowWebAudio","createKeystrokeAudio","__spreadValues"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TypeflowWebAudio={})}(this,function(e){"use strict";var t=Object.defineProperty,n=Object.defineProperties,a=Object.getOwnPropertyDescriptors,i=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,c=(e,n,a)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[n]=a,l=(e,t,n)=>c(e,"symbol"!=typeof t?t+"":t,n);const s=e=>(e&&"object"==typeof e&&!Object.isFrozen(e)&&(Object.values(e).forEach(s),Object.freeze(e)),e),u=(e,t,n,a,i,r={})=>{var o,c,l;return{oscillator:e,frequency:{start:t,end:n},gain:{start:a,end:.001},duration:i,filter:null!=(o=r.filter)?o:null,detune:{base:null!=(c=r.detune)?c:0,variance:null!=(l=r.variance)?l:.06}}},d=(e,t)=>({version:1,actions:{type:e,erase:null!=t?t:e}}),p=s({"mechanical-blue":d([u("triangle",3400,900,.18,.008),u("sine",200,50,.32,.035)],[u("triangle",900,180,.12,.025)]),"mechanical-red":d([u("sine",220,70,.26,.028)],[u("sine",150,55,.12,.022)]),"mechanical-brown":d([u("triangle",240,75,.27,.032)],[u("triangle",170,60,.13,.024)]),"soft-keyboard":d([u("sine",520,280,.08,.018,{filter:{type:"lowpass",frequency:1200,q:.7}})],[u("sine",320,180,.06,.016)]),typewriter:d([u("square",320,90,.17,.022)],[u("square",180,70,.13,.026)]),"terminal-beep":d([u("square",880,880,.08,.025)],[u("square",440,330,.06,.03)]),synth:d([u("sawtooth",950,180,.12,.045)],[u("sawtooth",420,120,.09,.04)]),correction:d([u("triangle",360,110,.1,.035)],[u("triangle",260,80,.14,.045)])}),m=s({clicky:"mechanical-blue",mechanical:"mechanical-red",tactile:"mechanical-brown",teletype:"typewriter",vintage:"typewriter","terminal-8bit":"terminal-beep","8bit":"terminal-beep",beep:"terminal-beep","cyber-synth":"synth",neon:"synth",bubble:"soft-keyboard",pop:"soft-keyboard"});function y(e){if(!e||1!==e.version||!e.actions||"object"!=typeof e.actions)throw new TypeError("TypeFlowWebAudio: preset must use schema version 1.");const t={version:1,actions:{}};for(const n of["type","erase"]){const a=e.actions[n];if(!Array.isArray(a)||0===a.length||a.length>4)throw new TypeError(`TypeFlowWebAudio: actions.${n} must contain 1-4 oscillator layers.`);t.actions[n]=a.map(e=>{var t,n,a,i,r,o,c,l,s;const u=String(null!=(t=e.oscillator)?t:"sine");if(!["sine","square","sawtooth","triangle"].includes(u))throw new RangeError("TypeFlowWebAudio: invalid oscillator type.");const d=Number(e.duration),p=Number(null==(n=e.frequency)?void 0:n.start),m=Number(null==(a=e.frequency)?void 0:a.end),y=Number(null==(i=e.gain)?void 0:i.start),f=Number(null==(r=e.gain)?void 0:r.end);if(!(d>=.001&&d<=2))throw new RangeError("TypeFlowWebAudio: duration must be 0.001-2 seconds.");if(!(p>=20&&p<=2e4&&m>=20&&m<=2e4))throw new RangeError("TypeFlowWebAudio: frequency must be 20-20000 Hz.");if(!(y>=0&&y<=1&&f>0&&f<=1))throw new RangeError("TypeFlowWebAudio: gain must be within safe bounds.");const w=Number(null!=(c=null==(o=e.detune)?void 0:o.variance)?c:0);if(!(w>=0&&w<=.5))throw new RangeError("TypeFlowWebAudio: detune variance must be 0-0.5.");const b=null===e.filter||void 0===e.filter?null:{type:String(null!=(l=e.filter.type)?l:"lowpass"),frequency:Math.max(20,Math.min(2e4,Number(e.filter.frequency)||1e3)),q:Math.max(1e-4,Math.min(1e3,Number(e.filter.q)||1))};if(b&&!["lowpass","highpass","bandpass","notch"].includes(b.type))throw new RangeError("TypeFlowWebAudio: invalid filter type.");return{oscillator:u,frequency:{start:p,end:m},gain:{start:y,end:f},duration:d,filter:b,detune:{base:Number(null==(s=e.detune)?void 0:s.base)||0,variance:w}}})}return s(t)}const f=class e{static getContext(){var t;if("undefined"==typeof window)return null;if(!e.ctx){const n=window.AudioContext||window.webkitAudioContext;if(!n)return null;const a=null==(t=window.navigator)?void 0:t.userActivation;if(a&&!a.isActive&&!a.hasBeenActive){if(!e.unlockHandler){const t=()=>{window.removeEventListener("pointerdown",t),window.removeEventListener("keydown",t),e.unlockHandler=null,e.getContext()};e.unlockHandler=t,window.addEventListener("pointerdown",t,{once:!0}),window.addEventListener("keydown",t,{once:!0})}return null}try{e.ctx=new n}catch(e){return null}e.masterGain=e.ctx.createGain(),e.masterGain.gain.setValueAtTime(e.isMuted?0:e.volume,e.ctx.currentTime),e.masterGain.connect(e.ctx.destination)}return"suspended"===e.ctx.state&&e.ctx.resume().catch(()=>{}),e.ctx}static setVolume(t){e.volume=Math.max(0,Math.min(1,t)),e.masterGain&&e.ctx&&!e.isMuted&&e.masterGain.gain.setValueAtTime(e.volume,e.ctx.currentTime)}static mute(t){e.isMuted=void 0!==t?t:!e.isMuted,e.masterGain&&e.ctx&&e.masterGain.gain.setValueAtTime(e.isMuted?0:e.volume,e.ctx.currentTime)}static registerAudioPreset(t,n,a={}){const i=String(null!=t?t:"").trim().toLowerCase();if(!/^[a-z0-9][a-z0-9-]*$/.test(i))throw new TypeError("TypeFlowWebAudio: invalid preset name.");if(!a.overwrite&&(i in p||i in m||e.customPresets.has(i)))throw new Error(`TypeFlowWebAudio: preset \`${i}\` already exists.`);return e.customPresets.set(i,y(n)),i}static removeAudioPreset(t){return e.customPresets.delete(String(null!=t?t:"").trim().toLowerCase())}static resetAudioPresets(){e.customPresets.clear()}static getAudioPreset(t){var n,a;const i=String(null!=t?t:"").trim().toLowerCase();return e.customPresets.has(i)?e.customPresets.get(i):null!=(a=p[null!=(n=m[i])?n:i])?a:null}static stopVoices(t){Array.from(t).forEach(t=>{var n;try{t.oscillator.stop()}catch(e){}t.nodes.forEach(e=>{try{e.disconnect()}catch(e){}}),null==(n=t.scope)||n.delete(t),e.activeVoices.delete(t)})}static stopAll(){e.stopVoices(e.activeVoices)}static destroy(){return t=this,n=null,a=function*(){var t,n;e.stopAll(),e.unlockHandler&&"undefined"!=typeof window&&(window.removeEventListener("pointerdown",e.unlockHandler),window.removeEventListener("keydown",e.unlockHandler),e.unlockHandler=null);try{null==(t=e.masterGain)||t.disconnect()}catch(e){}e.panners.forEach(e=>{try{e.disconnect()}catch(e){}}),e.panners.clear(),(null==(n=e.ctx)?void 0:n.close)&&(yield e.ctx.close().catch(()=>{})),e.ctx=null,e.masterGain=null},new Promise((e,i)=>{var r=e=>{try{c(a.next(e))}catch(e){i(e)}},o=e=>{try{c(a.throw(e))}catch(e){i(e)}},c=t=>t.done?e(t.value):Promise.resolve(t.value).then(r,o);c((a=a.apply(t,n)).next())});var t,n,a}static playPreset(t,n,a,i){var r,o,c,l;const s="erase"===a.action?"erase":"type",u=t.currentTime;for(const d of n.actions[s]){for(;e.activeVoices.size>=e.maxPolyphony;){const t=e.activeVoices.values().next().value;try{t.oscillator.stop()}catch(e){}t.nodes.forEach(e=>{try{e.disconnect()}catch(e){}}),null==(r=t.scope)||r.delete(t),e.activeVoices.delete(t)}const n=null!=(o=a.pitchVariance)?o:d.detune.variance,s=1+(2*Math.random()-1)*n,p=t.createOscillator(),m=t.createGain(),y=[p,m];p.type=d.oscillator,p.detune.setValueAtTime(d.detune.base,u),p.frequency.setValueAtTime(d.frequency.start*s,u),p.frequency.exponentialRampToValueAtTime(d.frequency.end*s,u+d.duration),m.gain.setValueAtTime(d.gain.start,u),m.gain.exponentialRampToValueAtTime(d.gain.end,u+d.duration),p.connect(m);let f=m;if(d.filter&&"function"==typeof t.createBiquadFilter){const e=t.createBiquadFilter();e.type=d.filter.type,e.frequency.setValueAtTime(d.filter.frequency,u),e.Q.setValueAtTime(d.filter.q,u),m.connect(e),f=e,y.push(e)}f.connect(i);const w={oscillator:p,nodes:y,scope:a.voiceScope};e.activeVoices.add(w),null==(c=a.voiceScope)||c.add(w),null==(l=p.addEventListener)||l.call(p,"ended",()=>{var t;y.forEach(e=>{try{e.disconnect()}catch(e){}}),null==(t=a.voiceScope)||t.delete(w),e.activeVoices.delete(w)},{once:!0}),p.start(u),p.stop(u+d.duration)}}static play(t="mechanical-blue",n={}){var a,i;if(!0===n.respectReducedMotion&&"undefined"!=typeof window&&(null==(a=window.matchMedia)?void 0:a.call(window,"(prefers-reduced-motion: reduce)").matches))return;const r=e.getContext();if(!r||e.isMuted)return;const o=r.currentTime,c=n.char||" ",l=1+(2*Math.random()-1)*(null!=(i=n.pitchVariance)?i:.06);let s=e.masterGain;if("function"==typeof r.createStereoPanner&&"number"==typeof n.pan){const t=Math.max(-1,Math.min(1,n.pan));let a=e.panners.get(t);a||(a=r.createStereoPanner(),a.pan.setValueAtTime(t,o),a.connect(e.masterGain),e.panners.set(t,a)),s=a}const u=String(t).toLowerCase(),d=e.getAudioPreset(u);if(d)e.playPreset(r,d,n,s);else if("mechanical-blue"===u||"clicky"===u){const e=" "===c||"\n"===c,t=e?140:200,n=r.createOscillator(),a=r.createGain();n.type="triangle",n.frequency.setValueAtTime((e?2800:3400)*l,o),a.gain.setValueAtTime(.2,o),a.gain.exponentialRampToValueAtTime(.001,o+.008),n.connect(a),a.connect(s),n.start(o),n.stop(o+.008);const i=r.createOscillator(),u=r.createGain();i.type="sine",i.frequency.setValueAtTime(t*l,o),i.frequency.exponentialRampToValueAtTime(50,o+.035),u.gain.setValueAtTime(.35,o),u.gain.exponentialRampToValueAtTime(.001,o+.035),i.connect(u),u.connect(s),i.start(o),i.stop(o+.035)}else if("mechanical-red"===u||"mechanical-brown"===u||"tactile"===u||"mechanical"===u){const e=r.createOscillator(),t=r.createGain();e.type=u.includes("brown")?"triangle":"sine",e.frequency.setValueAtTime(220*l,o),e.frequency.exponentialRampToValueAtTime(70,o+.028),t.gain.setValueAtTime(.28,o),t.gain.exponentialRampToValueAtTime(.001,o+.028),e.connect(t),t.connect(s),e.start(o),e.stop(o+.028)}else if("teletype"===u||"vintage"===u||"typewriter"===u)if("\n"===c){const e=r.createOscillator(),t=r.createGain();e.type="sine",e.frequency.setValueAtTime(1760,o),t.gain.setValueAtTime(.3,o),t.gain.exponentialRampToValueAtTime(.001,o+.35),e.connect(t),t.connect(s),e.start(o),e.stop(o+.35)}else{const e=r.createOscillator(),t=r.createGain();e.type="square",e.frequency.setValueAtTime(320*l,o),e.frequency.exponentialRampToValueAtTime(90,o+.022),t.gain.setValueAtTime(.18,o),t.gain.exponentialRampToValueAtTime(.001,o+.022),e.connect(t),t.connect(s),e.start(o),e.stop(o+.022)}else if("cyber-synth"===u||"synth"===u||"neon"===u){const e=r.createOscillator(),t=r.createGain();e.type="sawtooth",e.frequency.setValueAtTime(950*l,o),e.frequency.exponentialRampToValueAtTime(180,o+.045),t.gain.setValueAtTime(.12,o),t.gain.exponentialRampToValueAtTime(.001,o+.045),e.connect(t),t.connect(s),e.start(o),e.stop(o+.045)}else if("terminal-8bit"===u||"8bit"===u||"beep"===u){const e=r.createOscillator(),t=r.createGain();e.type="square",e.frequency.setValueAtTime((" "===c?440:880)*l,o),t.gain.setValueAtTime(.08,o),t.gain.exponentialRampToValueAtTime(.001,o+.025),e.connect(t),t.connect(s),e.start(o),e.stop(o+.025)}else if("bubble"===u||"pop"===u){const e=r.createOscillator(),t=r.createGain();e.type="sine",e.frequency.setValueAtTime(450*l,o),e.frequency.exponentialRampToValueAtTime(1100*l,o+.03),t.gain.setValueAtTime(.25,o),t.gain.exponentialRampToValueAtTime(.001,o+.035),e.connect(t),t.connect(s),e.start(o),e.stop(o+.035)}}};l(f,"ctx",null),l(f,"masterGain",null),l(f,"isMuted",!1),l(f,"volume",.25),l(f,"maxPolyphony",24),l(f,"customPresets",new Map),l(f,"activeVoices",new Set),l(f,"panners",new Map),l(f,"unlockHandler",null);let w=f;function b(e="mechanical-blue",t={}){const l=new Set,s=()=>{return w.play(e,(s=((e,t)=>{for(var n in t||(t={}))r.call(t,n)&&c(e,n,t[n]);if(i)for(var n of i(t))o.call(t,n)&&c(e,n,t[n]);return e})({},t),n(s,a({voiceScope:l}))));var s};return s.stop=()=>w.stopVoices(l),s}"undefined"!=typeof window&&(window.TypeFlowWebAudio=w,window.createKeystrokeAudio=b),e.AUDIO_PRESETS=p,e.AUDIO_PRESET_ALIASES=m,e.AUDIO_PRESET_SCHEMA_VERSION=1,e.TypeFlowWebAudio=w,e.createKeystrokeAudio=b,e.validateAudioPreset=y,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
2
+ //# sourceMappingURL=typeflow-webaudio.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeflow-webaudio.min.js","sources":["../../../src/typeflow-audio-presets.js","../../../src/typeflow-webaudio.js"],"sourcesContent":["export const AUDIO_PRESET_SCHEMA_VERSION = 1;\n\nconst freeze = (value) => {\n if (value && typeof value === 'object' && !Object.isFrozen(value)) {\n Object.values(value).forEach(freeze);\n Object.freeze(value);\n }\n return value;\n};\n\nconst layer = (oscillator, start, end, gain, duration, extras = {}) => ({\n oscillator,\n frequency: { start, end },\n gain: { start: gain, end: 0.001 },\n duration,\n filter: extras.filter ?? null,\n detune: { base: extras.detune ?? 0, variance: extras.variance ?? 0.06 },\n});\n\nconst preset = (type, erase) => ({\n version: AUDIO_PRESET_SCHEMA_VERSION,\n actions: { type, erase: erase ?? type },\n});\n\nexport const AUDIO_PRESETS = freeze({\n 'mechanical-blue': preset(\n [layer('triangle', 3400, 900, 0.18, 0.008), layer('sine', 200, 50, 0.32, 0.035)],\n [layer('triangle', 900, 180, 0.12, 0.025)]\n ),\n 'mechanical-red': preset(\n [layer('sine', 220, 70, 0.26, 0.028)],\n [layer('sine', 150, 55, 0.12, 0.022)]\n ),\n 'mechanical-brown': preset(\n [layer('triangle', 240, 75, 0.27, 0.032)],\n [layer('triangle', 170, 60, 0.13, 0.024)]\n ),\n 'soft-keyboard': preset(\n [\n layer('sine', 520, 280, 0.08, 0.018, {\n filter: { type: 'lowpass', frequency: 1200, q: 0.7 },\n }),\n ],\n [layer('sine', 320, 180, 0.06, 0.016)]\n ),\n typewriter: preset(\n [layer('square', 320, 90, 0.17, 0.022)],\n [layer('square', 180, 70, 0.13, 0.026)]\n ),\n 'terminal-beep': preset(\n [layer('square', 880, 880, 0.08, 0.025)],\n [layer('square', 440, 330, 0.06, 0.03)]\n ),\n synth: preset(\n [layer('sawtooth', 950, 180, 0.12, 0.045)],\n [layer('sawtooth', 420, 120, 0.09, 0.04)]\n ),\n correction: preset(\n [layer('triangle', 360, 110, 0.1, 0.035)],\n [layer('triangle', 260, 80, 0.14, 0.045)]\n ),\n});\n\nexport const AUDIO_PRESET_ALIASES = freeze({\n clicky: 'mechanical-blue',\n mechanical: 'mechanical-red',\n tactile: 'mechanical-brown',\n teletype: 'typewriter',\n vintage: 'typewriter',\n 'terminal-8bit': 'terminal-beep',\n '8bit': 'terminal-beep',\n beep: 'terminal-beep',\n 'cyber-synth': 'synth',\n neon: 'synth',\n bubble: 'soft-keyboard',\n pop: 'soft-keyboard',\n});\n\nexport function validateAudioPreset(config) {\n if (\n !config ||\n config.version !== AUDIO_PRESET_SCHEMA_VERSION ||\n !config.actions ||\n typeof config.actions !== 'object'\n ) {\n throw new TypeError(\n `TypeFlowWebAudio: preset must use schema version ${AUDIO_PRESET_SCHEMA_VERSION}.`\n );\n }\n const output = { version: AUDIO_PRESET_SCHEMA_VERSION, actions: {} };\n for (const action of ['type', 'erase']) {\n const layers = config.actions[action];\n if (!Array.isArray(layers) || layers.length === 0 || layers.length > 4) {\n throw new TypeError(\n `TypeFlowWebAudio: actions.${action} must contain 1-4 oscillator layers.`\n );\n }\n output.actions[action] = layers.map((candidate) => {\n const oscillator = String(candidate.oscillator ?? 'sine');\n if (!['sine', 'square', 'sawtooth', 'triangle'].includes(oscillator))\n throw new RangeError('TypeFlowWebAudio: invalid oscillator type.');\n const duration = Number(candidate.duration);\n const start = Number(candidate.frequency?.start);\n const end = Number(candidate.frequency?.end);\n const gainStart = Number(candidate.gain?.start);\n const gainEnd = Number(candidate.gain?.end);\n if (!(duration >= 0.001 && duration <= 2))\n throw new RangeError('TypeFlowWebAudio: duration must be 0.001-2 seconds.');\n if (!(start >= 20 && start <= 20000 && end >= 20 && end <= 20000))\n throw new RangeError('TypeFlowWebAudio: frequency must be 20-20000 Hz.');\n if (!(gainStart >= 0 && gainStart <= 1 && gainEnd > 0 && gainEnd <= 1))\n throw new RangeError('TypeFlowWebAudio: gain must be within safe bounds.');\n const variance = Number(candidate.detune?.variance ?? 0);\n if (!(variance >= 0 && variance <= 0.5))\n throw new RangeError('TypeFlowWebAudio: detune variance must be 0-0.5.');\n const filter =\n candidate.filter === null || candidate.filter === undefined\n ? null\n : {\n type: String(candidate.filter.type ?? 'lowpass'),\n frequency: Math.max(20, Math.min(20000, Number(candidate.filter.frequency) || 1000)),\n q: Math.max(0.0001, Math.min(1000, Number(candidate.filter.q) || 1)),\n };\n if (filter && !['lowpass', 'highpass', 'bandpass', 'notch'].includes(filter.type))\n throw new RangeError('TypeFlowWebAudio: invalid filter type.');\n return {\n oscillator,\n frequency: { start, end },\n gain: { start: gainStart, end: gainEnd },\n duration,\n filter,\n detune: { base: Number(candidate.detune?.base) || 0, variance },\n };\n });\n }\n return freeze(output);\n}\n","/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow WebAudio - Procedural Audio Synthesis for Typing Interfaces.\n * Synthesizes zero-asset, real-time procedural keystrokes including mechanical switches,\n * vintage teletype strikes, retro terminal beeps, and FM cyber synths with spatial panning.\n *\n * @module @staticcanvas/typeflow/webaudio\n */\n\nimport {\n AUDIO_PRESETS,\n AUDIO_PRESET_ALIASES,\n AUDIO_PRESET_SCHEMA_VERSION,\n validateAudioPreset,\n} from './typeflow-audio-presets.js';\nexport { AUDIO_PRESETS, AUDIO_PRESET_ALIASES, AUDIO_PRESET_SCHEMA_VERSION, validateAudioPreset };\n\nexport class TypeFlowWebAudio {\n static ctx = null;\n static masterGain = null;\n static isMuted = false;\n static volume = 0.25;\n static maxPolyphony = 24;\n static customPresets = new Map();\n static activeVoices = new Set();\n static panners = new Map();\n static unlockHandler = null;\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Initializes or returns the shared Web Audio Context singleton.\n * Automatically attaches unlock listeners to resume audio on the first user interaction.\n * @returns {AudioContext | null} The active AudioContext instance or null in non-browser environments.\n *\n * -------------\n *\n * @example\n * const ctx = TypeFlowWebAudio.getContext();\n * console.log('Audio state:', ctx?.state);\n */\n static getContext() {\n if (typeof window === 'undefined') return null;\n\n if (!TypeFlowWebAudio.ctx) {\n const AudioCtx = window.AudioContext || window.webkitAudioContext;\n if (!AudioCtx) return null;\n\n const activation = window.navigator?.userActivation;\n if (activation && !activation.isActive && !activation.hasBeenActive) {\n if (!TypeFlowWebAudio.unlockHandler) {\n const unlock = () => {\n window.removeEventListener('pointerdown', unlock);\n window.removeEventListener('keydown', unlock);\n TypeFlowWebAudio.unlockHandler = null;\n TypeFlowWebAudio.getContext();\n };\n TypeFlowWebAudio.unlockHandler = unlock;\n window.addEventListener('pointerdown', unlock, { once: true });\n window.addEventListener('keydown', unlock, { once: true });\n }\n return null;\n }\n\n try {\n TypeFlowWebAudio.ctx = new AudioCtx();\n } catch {\n return null;\n }\n TypeFlowWebAudio.masterGain = TypeFlowWebAudio.ctx.createGain();\n TypeFlowWebAudio.masterGain.gain.setValueAtTime(\n TypeFlowWebAudio.isMuted ? 0 : TypeFlowWebAudio.volume,\n TypeFlowWebAudio.ctx.currentTime\n );\n TypeFlowWebAudio.masterGain.connect(TypeFlowWebAudio.ctx.destination);\n }\n\n if (TypeFlowWebAudio.ctx.state === 'suspended') {\n TypeFlowWebAudio.ctx.resume().catch(() => {});\n }\n\n return TypeFlowWebAudio.ctx;\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Sets the master volume level for synthesized keystrokes.\n * @param {number} vol - Volume level from 0.0 (silent) to 1.0 (maximum).\n * @returns {void}\n *\n * -------------\n *\n * @Properties\n * @param {number} vol - Floating-point number between 0.0 and 1.0.\n *\n * -------------\n *\n * @example\n * TypeFlowWebAudio.setVolume(0.15); // Set subtle background volume\n */\n static setVolume(vol) {\n TypeFlowWebAudio.volume = Math.max(0, Math.min(1, vol));\n if (TypeFlowWebAudio.masterGain && TypeFlowWebAudio.ctx && !TypeFlowWebAudio.isMuted) {\n TypeFlowWebAudio.masterGain.gain.setValueAtTime(\n TypeFlowWebAudio.volume,\n TypeFlowWebAudio.ctx.currentTime\n );\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Toggles or forces the mute state of procedural keystroke audio.\n * @param {boolean} [force] - Optional boolean to explicitly force muted (true) or unmuted (false).\n * @returns {void}\n *\n * -------------\n *\n * @Properties\n * @param {boolean} [force] - If omitted, flips the current mute state.\n *\n * -------------\n *\n * @example\n * TypeFlowWebAudio.mute(); // Toggle mute\n * TypeFlowWebAudio.mute(true); // Force mute\n * TypeFlowWebAudio.mute(false); // Force unmute\n */\n static mute(force) {\n TypeFlowWebAudio.isMuted = force !== undefined ? force : !TypeFlowWebAudio.isMuted;\n if (TypeFlowWebAudio.masterGain && TypeFlowWebAudio.ctx) {\n TypeFlowWebAudio.masterGain.gain.setValueAtTime(\n TypeFlowWebAudio.isMuted ? 0 : TypeFlowWebAudio.volume,\n TypeFlowWebAudio.ctx.currentTime\n );\n }\n }\n\n /** Registers a validated instance-safe preset. Built-in collisions require `overwrite: true`. */\n static registerAudioPreset(name, config, options = {}) {\n const key = String(name ?? '')\n .trim()\n .toLowerCase();\n if (!/^[a-z0-9][a-z0-9-]*$/.test(key))\n throw new TypeError('TypeFlowWebAudio: invalid preset name.');\n if (\n !options.overwrite &&\n (key in AUDIO_PRESETS ||\n key in AUDIO_PRESET_ALIASES ||\n TypeFlowWebAudio.customPresets.has(key))\n ) {\n throw new Error(`TypeFlowWebAudio: preset \\`${key}\\` already exists.`);\n }\n TypeFlowWebAudio.customPresets.set(key, validateAudioPreset(config));\n return key;\n }\n\n /** Removes a custom preset. Immutable built-ins cannot be removed. */\n static removeAudioPreset(name) {\n return TypeFlowWebAudio.customPresets.delete(\n String(name ?? '')\n .trim()\n .toLowerCase()\n );\n }\n\n /** Removes every custom preset without affecting immutable built-ins. */\n static resetAudioPresets() {\n TypeFlowWebAudio.customPresets.clear();\n }\n\n /** Returns a frozen custom or built-in preset, resolving compatibility aliases. */\n static getAudioPreset(name) {\n const key = String(name ?? '')\n .trim()\n .toLowerCase();\n if (TypeFlowWebAudio.customPresets.has(key)) return TypeFlowWebAudio.customPresets.get(key);\n return AUDIO_PRESETS[AUDIO_PRESET_ALIASES[key] ?? key] ?? null;\n }\n\n /** Stops and disconnects a scoped set of voices. */\n static stopVoices(voices) {\n Array.from(voices).forEach((voice) => {\n try {\n voice.oscillator.stop();\n } catch {\n /* already stopped */\n }\n voice.nodes.forEach((node) => {\n try {\n node.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n voice.scope?.delete(voice);\n TypeFlowWebAudio.activeVoices.delete(voice);\n });\n }\n\n /** Stops and disconnects all voices created by this extension. */\n static stopAll() {\n TypeFlowWebAudio.stopVoices(TypeFlowWebAudio.activeVoices);\n }\n\n /** Tears down voices and the shared AudioContext so repeated setup is clean. */\n static async destroy() {\n TypeFlowWebAudio.stopAll();\n if (TypeFlowWebAudio.unlockHandler && typeof window !== 'undefined') {\n window.removeEventListener('pointerdown', TypeFlowWebAudio.unlockHandler);\n window.removeEventListener('keydown', TypeFlowWebAudio.unlockHandler);\n TypeFlowWebAudio.unlockHandler = null;\n }\n try {\n TypeFlowWebAudio.masterGain?.disconnect();\n } catch {\n /* already disconnected */\n }\n TypeFlowWebAudio.panners.forEach((panner) => {\n try {\n panner.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n TypeFlowWebAudio.panners.clear();\n if (TypeFlowWebAudio.ctx?.close) await TypeFlowWebAudio.ctx.close().catch(() => {});\n TypeFlowWebAudio.ctx = null;\n TypeFlowWebAudio.masterGain = null;\n }\n\n static playPreset(ctx, config, options, destination) {\n const action = options.action === 'erase' ? 'erase' : 'type';\n const now = ctx.currentTime;\n for (const layer of config.actions[action]) {\n while (TypeFlowWebAudio.activeVoices.size >= TypeFlowWebAudio.maxPolyphony) {\n const oldest = TypeFlowWebAudio.activeVoices.values().next().value;\n try {\n oldest.oscillator.stop();\n } catch {\n /* already stopped */\n }\n oldest.nodes.forEach((node) => {\n try {\n node.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n oldest.scope?.delete(oldest);\n TypeFlowWebAudio.activeVoices.delete(oldest);\n }\n const variance = options.pitchVariance ?? layer.detune.variance;\n const jitter = 1 + (Math.random() * 2 - 1) * variance;\n const oscillator = ctx.createOscillator();\n const gain = ctx.createGain();\n const nodes = [oscillator, gain];\n oscillator.type = layer.oscillator;\n oscillator.detune.setValueAtTime(layer.detune.base, now);\n oscillator.frequency.setValueAtTime(layer.frequency.start * jitter, now);\n oscillator.frequency.exponentialRampToValueAtTime(\n layer.frequency.end * jitter,\n now + layer.duration\n );\n gain.gain.setValueAtTime(layer.gain.start, now);\n gain.gain.exponentialRampToValueAtTime(layer.gain.end, now + layer.duration);\n oscillator.connect(gain);\n let tail = gain;\n if (layer.filter && typeof ctx.createBiquadFilter === 'function') {\n const filter = ctx.createBiquadFilter();\n filter.type = layer.filter.type;\n filter.frequency.setValueAtTime(layer.filter.frequency, now);\n filter.Q.setValueAtTime(layer.filter.q, now);\n gain.connect(filter);\n tail = filter;\n nodes.push(filter);\n }\n tail.connect(destination);\n const voice = { oscillator, nodes, scope: options.voiceScope };\n TypeFlowWebAudio.activeVoices.add(voice);\n options.voiceScope?.add(voice);\n oscillator.addEventListener?.(\n 'ended',\n () => {\n nodes.forEach((node) => {\n try {\n node.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n options.voiceScope?.delete(voice);\n TypeFlowWebAudio.activeVoices.delete(voice);\n },\n { once: true }\n );\n oscillator.start(now);\n oscillator.stop(now + layer.duration);\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Synthesizes and plays a procedural audio keystroke sound in real time without audio assets.\n * @param {string} [profile='mechanical-blue'] - Sound profile name ('mechanical-blue', 'mechanical-red', 'mechanical-brown', 'teletype', 'cyber-synth', 'terminal-8bit', 'bubble').\n * @param {Object} [options={}] - Keystroke playback parameters.\n * @returns {void}\n *\n * -------------\n *\n * @Properties\n * @param {string} [profile='mechanical-blue'] - Synthesizer profile.\n * @param {string} [options.char=' '] - Typed character (differentiates spacebar, return bells, normal keys).\n * @param {number} [options.pan=0] - Stereo pan from -1.0 (full left) to +1.0 (full right).\n * @param {number} [options.pitchVariance=0.06] - Organic pitch variation percentage (±0.06 = ±6%).\n *\n * -------------\n *\n * @example\n *\n * // Play a clicky switch sound\n * TypeFlowWebAudio.play('mechanical-blue');\n *\n * // Play a cyber synth keystroke panned slightly to the right\n * TypeFlowWebAudio.play('cyber-synth', { char: 'A', pan: 0.3 });\n *\n * // Play a teletype carriage bell on newline\n * TypeFlowWebAudio.play('teletype', { char: '\\n' });\n */\n static play(profile = 'mechanical-blue', options = {}) {\n if (\n options.respectReducedMotion === true &&\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches\n )\n return;\n const ctx = TypeFlowWebAudio.getContext();\n if (!ctx || TypeFlowWebAudio.isMuted) return;\n\n const t = ctx.currentTime;\n const char = options.char || ' ';\n const jitter = 1 + (Math.random() * 2 - 1) * (options.pitchVariance ?? 0.06);\n\n // Optional spatial stereo panner\n let dest = TypeFlowWebAudio.masterGain;\n if (typeof ctx.createStereoPanner === 'function' && typeof options.pan === 'number') {\n const pan = Math.max(-1, Math.min(1, options.pan));\n let panner = TypeFlowWebAudio.panners.get(pan);\n if (!panner) {\n panner = ctx.createStereoPanner();\n panner.pan.setValueAtTime(pan, t);\n panner.connect(TypeFlowWebAudio.masterGain);\n TypeFlowWebAudio.panners.set(pan, panner);\n }\n dest = panner;\n }\n\n const normProfile = String(profile).toLowerCase();\n const presetConfig = TypeFlowWebAudio.getAudioPreset(normProfile);\n if (presetConfig) {\n TypeFlowWebAudio.playPreset(ctx, presetConfig, options, dest);\n return;\n }\n\n // 1. Mechanical Clicky Switch (Blue style)\n if (normProfile === 'mechanical-blue' || normProfile === 'clicky') {\n const isSpace = char === ' ' || char === '\\n';\n const baseFreq = isSpace ? 140 : 200;\n\n // Click snap (high transient)\n const snapOsc = ctx.createOscillator();\n const snapGain = ctx.createGain();\n snapOsc.type = 'triangle';\n snapOsc.frequency.setValueAtTime((isSpace ? 2800 : 3400) * jitter, t);\n snapGain.gain.setValueAtTime(0.2, t);\n snapGain.gain.exponentialRampToValueAtTime(0.001, t + 0.008);\n snapOsc.connect(snapGain);\n snapGain.connect(dest);\n snapOsc.start(t);\n snapOsc.stop(t + 0.008);\n\n // Bottom-out thud (low resonant body)\n const thudOsc = ctx.createOscillator();\n const thudGain = ctx.createGain();\n thudOsc.type = 'sine';\n thudOsc.frequency.setValueAtTime(baseFreq * jitter, t);\n thudOsc.frequency.exponentialRampToValueAtTime(50, t + 0.035);\n thudGain.gain.setValueAtTime(0.35, t);\n thudGain.gain.exponentialRampToValueAtTime(0.001, t + 0.035);\n thudOsc.connect(thudGain);\n thudGain.connect(dest);\n thudOsc.start(t);\n thudOsc.stop(t + 0.035);\n }\n // 2. Mechanical Linear / Tactile (Red / Brown style)\n else if (\n normProfile === 'mechanical-red' ||\n normProfile === 'mechanical-brown' ||\n normProfile === 'tactile' ||\n normProfile === 'mechanical'\n ) {\n const thudOsc = ctx.createOscillator();\n const thudGain = ctx.createGain();\n thudOsc.type = normProfile.includes('brown') ? 'triangle' : 'sine';\n thudOsc.frequency.setValueAtTime(220 * jitter, t);\n thudOsc.frequency.exponentialRampToValueAtTime(70, t + 0.028);\n thudGain.gain.setValueAtTime(0.28, t);\n thudGain.gain.exponentialRampToValueAtTime(0.001, t + 0.028);\n thudOsc.connect(thudGain);\n thudGain.connect(dest);\n thudOsc.start(t);\n thudOsc.stop(t + 0.028);\n }\n // 3. Vintage Teletype / Solenoid Strike & Carriage Bell\n else if (\n normProfile === 'teletype' ||\n normProfile === 'vintage' ||\n normProfile === 'typewriter'\n ) {\n if (char === '\\n') {\n const bellOsc = ctx.createOscillator();\n const bellGain = ctx.createGain();\n bellOsc.type = 'sine';\n bellOsc.frequency.setValueAtTime(1760, t);\n bellGain.gain.setValueAtTime(0.3, t);\n bellGain.gain.exponentialRampToValueAtTime(0.001, t + 0.35);\n bellOsc.connect(bellGain);\n bellGain.connect(dest);\n bellOsc.start(t);\n bellOsc.stop(t + 0.35);\n } else {\n const clackOsc = ctx.createOscillator();\n const clackGain = ctx.createGain();\n clackOsc.type = 'square';\n clackOsc.frequency.setValueAtTime(320 * jitter, t);\n clackOsc.frequency.exponentialRampToValueAtTime(90, t + 0.022);\n clackGain.gain.setValueAtTime(0.18, t);\n clackGain.gain.exponentialRampToValueAtTime(0.001, t + 0.022);\n clackOsc.connect(clackGain);\n clackGain.connect(dest);\n clackOsc.start(t);\n clackOsc.stop(t + 0.022);\n }\n }\n // 4. Sci-Fi Cyber Synth / Laser Chirp\n else if (normProfile === 'cyber-synth' || normProfile === 'synth' || normProfile === 'neon') {\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sawtooth';\n osc.frequency.setValueAtTime(950 * jitter, t);\n osc.frequency.exponentialRampToValueAtTime(180, t + 0.045);\n gain.gain.setValueAtTime(0.12, t);\n gain.gain.exponentialRampToValueAtTime(0.001, t + 0.045);\n osc.connect(gain);\n gain.connect(dest);\n osc.start(t);\n osc.stop(t + 0.045);\n }\n // 5. Retro 8-bit Terminal Beep\n else if (normProfile === 'terminal-8bit' || normProfile === '8bit' || normProfile === 'beep') {\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'square';\n osc.frequency.setValueAtTime((char === ' ' ? 440 : 880) * jitter, t);\n gain.gain.setValueAtTime(0.08, t);\n gain.gain.exponentialRampToValueAtTime(0.001, t + 0.025);\n osc.connect(gain);\n gain.connect(dest);\n osc.start(t);\n osc.stop(t + 0.025);\n }\n // 6. Liquid Bubble Pop\n else if (normProfile === 'bubble' || normProfile === 'pop') {\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sine';\n osc.frequency.setValueAtTime(450 * jitter, t);\n osc.frequency.exponentialRampToValueAtTime(1100 * jitter, t + 0.03);\n gain.gain.setValueAtTime(0.25, t);\n gain.gain.exponentialRampToValueAtTime(0.001, t + 0.035);\n osc.connect(gain);\n gain.connect(dest);\n osc.start(t);\n osc.stop(t + 0.035);\n }\n }\n}\n\n/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Creates an audio callback function configured for TypeFlow's `audio` option.\n * @param {string} [profile='mechanical-blue'] - Sound profile identifier.\n * @param {Object} [options={}] - Custom audio synthesis options.\n * @returns {() => void} Zero-argument callback function invoked per keystroke.\n *\n * -------------\n *\n * @Properties\n * @param {string} [profile='mechanical-blue'] - Sound profile name.\n * @param {number} [options.pan=0] - Stereo pan (-1.0 to 1.0).\n * @param {number} [options.pitchVariance=0.06] - Random pitch variance.\n *\n * -------------\n *\n * @example\n * import { TypeFlow } from '@staticcanvas/typeflow';\n * import { createKeystrokeAudio } from '@staticcanvas/typeflow/webaudio';\n *\n * TypeFlow.type('#headline', 'Mechanical Sound Effects', {\n * audio: createKeystrokeAudio('mechanical-blue', { pan: 0.1 }),\n * speed: 40,\n * });\n */\nexport function createKeystrokeAudio(profile = 'mechanical-blue', options = {}) {\n const voiceScope = new Set();\n const play = () => TypeFlowWebAudio.play(profile, { ...options, voiceScope });\n play.stop = () => TypeFlowWebAudio.stopVoices(voiceScope);\n return play;\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowWebAudio = TypeFlowWebAudio;\n window.createKeystrokeAudio = createKeystrokeAudio;\n}\n"],"names":["freeze","value","Object","isFrozen","values","forEach","layer","oscillator","start","end","gain","duration","extras","frequency","filter","_a","detune","base","_b","variance","_c","preset","type","erase","version","actions","AUDIO_PRESETS","q","typewriter","synth","correction","AUDIO_PRESET_ALIASES","clicky","mechanical","tactile","teletype","vintage","beep","neon","bubble","pop","validateAudioPreset","config","TypeError","output","action","layers","Array","isArray","length","map","candidate","String","includes","RangeError","Number","gainStart","_d","gainEnd","_e","_g","_f","_h","Math","max","min","_i","_TypeFlowWebAudio","getContext","window","ctx","AudioCtx","AudioContext","webkitAudioContext","activation","navigator","userActivation","isActive","hasBeenActive","unlockHandler","unlock","removeEventListener","addEventListener","once","e","masterGain","createGain","setValueAtTime","isMuted","volume","currentTime","connect","destination","state","resume","catch","setVolume","vol","mute","force","registerAudioPreset","name","options","key","trim","toLowerCase","test","overwrite","customPresets","has","Error","set","removeAudioPreset","delete","resetAudioPresets","clear","getAudioPreset","get","stopVoices","voices","from","voice","stop","nodes","node","disconnect","scope","activeVoices","stopAll","destroy","this","panners","panner","close","playPreset","now","size","maxPolyphony","oldest","next","pitchVariance","jitter","random","createOscillator","exponentialRampToValueAtTime","tail","createBiquadFilter","Q","push","voiceScope","add","call","play","profile","respectReducedMotion","matchMedia","matches","t","char","dest","createStereoPanner","pan","normProfile","presetConfig","isSpace","baseFreq","snapOsc","snapGain","thudOsc","thudGain","bellOsc","bellGain","clackOsc","clackGain","osc","__publicField","Map","Set","TypeFlowWebAudio","createKeystrokeAudio","__spreadValues"],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /*! typeflow-webaudio | MIT */
2
+ var e=Object.defineProperty,t=Object.defineProperties,n=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,i=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,o=(t,n,a)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:a}):t[n]=a,c=(e,t,n)=>o(e,"symbol"!=typeof t?t+"":t,n);const l=1,s=e=>(e&&"object"==typeof e&&!Object.isFrozen(e)&&(Object.values(e).forEach(s),Object.freeze(e)),e),u=(e,t,n,a,i,r={})=>{var o,c,l;return{oscillator:e,frequency:{start:t,end:n},gain:{start:a,end:.001},duration:i,filter:null!=(o=r.filter)?o:null,detune:{base:null!=(c=r.detune)?c:0,variance:null!=(l=r.variance)?l:.06}}},p=(e,t)=>({version:1,actions:{type:e,erase:null!=t?t:e}}),d=s({"mechanical-blue":p([u("triangle",3400,900,.18,.008),u("sine",200,50,.32,.035)],[u("triangle",900,180,.12,.025)]),"mechanical-red":p([u("sine",220,70,.26,.028)],[u("sine",150,55,.12,.022)]),"mechanical-brown":p([u("triangle",240,75,.27,.032)],[u("triangle",170,60,.13,.024)]),"soft-keyboard":p([u("sine",520,280,.08,.018,{filter:{type:"lowpass",frequency:1200,q:.7}})],[u("sine",320,180,.06,.016)]),typewriter:p([u("square",320,90,.17,.022)],[u("square",180,70,.13,.026)]),"terminal-beep":p([u("square",880,880,.08,.025)],[u("square",440,330,.06,.03)]),synth:p([u("sawtooth",950,180,.12,.045)],[u("sawtooth",420,120,.09,.04)]),correction:p([u("triangle",360,110,.1,.035)],[u("triangle",260,80,.14,.045)])}),m=s({clicky:"mechanical-blue",mechanical:"mechanical-red",tactile:"mechanical-brown",teletype:"typewriter",vintage:"typewriter","terminal-8bit":"terminal-beep","8bit":"terminal-beep",beep:"terminal-beep","cyber-synth":"synth",neon:"synth",bubble:"soft-keyboard",pop:"soft-keyboard"});function y(e){if(!e||1!==e.version||!e.actions||"object"!=typeof e.actions)throw new TypeError("TypeFlowWebAudio: preset must use schema version 1.");const t={version:1,actions:{}};for(const n of["type","erase"]){const a=e.actions[n];if(!Array.isArray(a)||0===a.length||a.length>4)throw new TypeError(`TypeFlowWebAudio: actions.${n} must contain 1-4 oscillator layers.`);t.actions[n]=a.map(e=>{var t,n,a,i,r,o,c,l,s;const u=String(null!=(t=e.oscillator)?t:"sine");if(!["sine","square","sawtooth","triangle"].includes(u))throw new RangeError("TypeFlowWebAudio: invalid oscillator type.");const p=Number(e.duration),d=Number(null==(n=e.frequency)?void 0:n.start),m=Number(null==(a=e.frequency)?void 0:a.end),y=Number(null==(i=e.gain)?void 0:i.start),f=Number(null==(r=e.gain)?void 0:r.end);if(!(p>=.001&&p<=2))throw new RangeError("TypeFlowWebAudio: duration must be 0.001-2 seconds.");if(!(d>=20&&d<=2e4&&m>=20&&m<=2e4))throw new RangeError("TypeFlowWebAudio: frequency must be 20-20000 Hz.");if(!(y>=0&&y<=1&&f>0&&f<=1))throw new RangeError("TypeFlowWebAudio: gain must be within safe bounds.");const w=Number(null!=(c=null==(o=e.detune)?void 0:o.variance)?c:0);if(!(w>=0&&w<=.5))throw new RangeError("TypeFlowWebAudio: detune variance must be 0-0.5.");const h=null===e.filter||void 0===e.filter?null:{type:String(null!=(l=e.filter.type)?l:"lowpass"),frequency:Math.max(20,Math.min(2e4,Number(e.filter.frequency)||1e3)),q:Math.max(1e-4,Math.min(1e3,Number(e.filter.q)||1))};if(h&&!["lowpass","highpass","bandpass","notch"].includes(h.type))throw new RangeError("TypeFlowWebAudio: invalid filter type.");return{oscillator:u,frequency:{start:d,end:m},gain:{start:y,end:f},duration:p,filter:h,detune:{base:Number(null==(s=e.detune)?void 0:s.base)||0,variance:w}}})}return s(t)}const f=class e{
3
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
4
+ * @description Initializes or returns the shared Web Audio Context singleton.
5
+ * Automatically attaches unlock listeners to resume audio on the first user interaction.
6
+ * @returns {AudioContext | null} The active AudioContext instance or null in non-browser environments.
7
+ *
8
+ * -------------
9
+ *
10
+ * @example
11
+ * const ctx = TypeFlowWebAudio.getContext();
12
+ * console.log('Audio state:', ctx?.state);
13
+ */
14
+ static getContext(){var t;if("undefined"==typeof window)return null;if(!e.ctx){const n=window.AudioContext||window.webkitAudioContext;if(!n)return null;const a=null==(t=window.navigator)?void 0:t.userActivation;if(a&&!a.isActive&&!a.hasBeenActive){if(!e.unlockHandler){const t=()=>{window.removeEventListener("pointerdown",t),window.removeEventListener("keydown",t),e.unlockHandler=null,e.getContext()};e.unlockHandler=t,window.addEventListener("pointerdown",t,{once:!0}),window.addEventListener("keydown",t,{once:!0})}return null}try{e.ctx=new n}catch(e){return null}e.masterGain=e.ctx.createGain(),e.masterGain.gain.setValueAtTime(e.isMuted?0:e.volume,e.ctx.currentTime),e.masterGain.connect(e.ctx.destination)}return"suspended"===e.ctx.state&&e.ctx.resume().catch(()=>{}),e.ctx}
15
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
16
+ * @description Sets the master volume level for synthesized keystrokes.
17
+ * @param {number} vol - Volume level from 0.0 (silent) to 1.0 (maximum).
18
+ * @returns {void}
19
+ *
20
+ * -------------
21
+ *
22
+ * @Properties
23
+ * @param {number} vol - Floating-point number between 0.0 and 1.0.
24
+ *
25
+ * -------------
26
+ *
27
+ * @example
28
+ * TypeFlowWebAudio.setVolume(0.15); // Set subtle background volume
29
+ */static setVolume(t){e.volume=Math.max(0,Math.min(1,t)),e.masterGain&&e.ctx&&!e.isMuted&&e.masterGain.gain.setValueAtTime(e.volume,e.ctx.currentTime)}
30
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
31
+ * @description Toggles or forces the mute state of procedural keystroke audio.
32
+ * @param {boolean} [force] - Optional boolean to explicitly force muted (true) or unmuted (false).
33
+ * @returns {void}
34
+ *
35
+ * -------------
36
+ *
37
+ * @Properties
38
+ * @param {boolean} [force] - If omitted, flips the current mute state.
39
+ *
40
+ * -------------
41
+ *
42
+ * @example
43
+ * TypeFlowWebAudio.mute(); // Toggle mute
44
+ * TypeFlowWebAudio.mute(true); // Force mute
45
+ * TypeFlowWebAudio.mute(false); // Force unmute
46
+ */static mute(t){e.isMuted=void 0!==t?t:!e.isMuted,e.masterGain&&e.ctx&&e.masterGain.gain.setValueAtTime(e.isMuted?0:e.volume,e.ctx.currentTime)}
47
+ /** Registers a validated instance-safe preset. Built-in collisions require `overwrite: true`. */static registerAudioPreset(t,n,a={}){const i=String(null!=t?t:"").trim().toLowerCase();if(!/^[a-z0-9][a-z0-9-]*$/.test(i))throw new TypeError("TypeFlowWebAudio: invalid preset name.");if(!a.overwrite&&(i in d||i in m||e.customPresets.has(i)))throw new Error(`TypeFlowWebAudio: preset \`${i}\` already exists.`);return e.customPresets.set(i,y(n)),i}
48
+ /** Removes a custom preset. Immutable built-ins cannot be removed. */static removeAudioPreset(t){return e.customPresets.delete(String(null!=t?t:"").trim().toLowerCase())}
49
+ /** Removes every custom preset without affecting immutable built-ins. */static resetAudioPresets(){e.customPresets.clear()}
50
+ /** Returns a frozen custom or built-in preset, resolving compatibility aliases. */static getAudioPreset(t){var n,a;const i=String(null!=t?t:"").trim().toLowerCase();return e.customPresets.has(i)?e.customPresets.get(i):null!=(a=d[null!=(n=m[i])?n:i])?a:null}
51
+ /** Stops and disconnects a scoped set of voices. */static stopVoices(t){Array.from(t).forEach(t=>{var n;try{t.oscillator.stop()}catch(e){}t.nodes.forEach(e=>{try{e.disconnect()}catch(e){}}),null==(n=t.scope)||n.delete(t),e.activeVoices.delete(t)})}
52
+ /** Stops and disconnects all voices created by this extension. */static stopAll(){e.stopVoices(e.activeVoices)}
53
+ /** Tears down voices and the shared AudioContext so repeated setup is clean. */static destroy(){return t=this,n=null,a=function*(){var t,n;e.stopAll(),e.unlockHandler&&"undefined"!=typeof window&&(window.removeEventListener("pointerdown",e.unlockHandler),window.removeEventListener("keydown",e.unlockHandler),e.unlockHandler=null);try{null==(t=e.masterGain)||t.disconnect()}catch(e){}e.panners.forEach(e=>{try{e.disconnect()}catch(e){}}),e.panners.clear(),(null==(n=e.ctx)?void 0:n.close)&&(yield e.ctx.close().catch(()=>{})),e.ctx=null,e.masterGain=null},new Promise((e,i)=>{var r=e=>{try{c(a.next(e))}catch(e){i(e)}},o=e=>{try{c(a.throw(e))}catch(e){i(e)}},c=t=>t.done?e(t.value):Promise.resolve(t.value).then(r,o);c((a=a.apply(t,n)).next())});var t,n,a}static playPreset(t,n,a,i){var r,o,c,l;const s="erase"===a.action?"erase":"type",u=t.currentTime;for(const p of n.actions[s]){for(;e.activeVoices.size>=e.maxPolyphony;){const t=e.activeVoices.values().next().value;try{t.oscillator.stop()}catch(e){}t.nodes.forEach(e=>{try{e.disconnect()}catch(e){}}),null==(r=t.scope)||r.delete(t),e.activeVoices.delete(t)}const n=null!=(o=a.pitchVariance)?o:p.detune.variance,s=1+(2*Math.random()-1)*n,d=t.createOscillator(),m=t.createGain(),y=[d,m];d.type=p.oscillator,d.detune.setValueAtTime(p.detune.base,u),d.frequency.setValueAtTime(p.frequency.start*s,u),d.frequency.exponentialRampToValueAtTime(p.frequency.end*s,u+p.duration),m.gain.setValueAtTime(p.gain.start,u),m.gain.exponentialRampToValueAtTime(p.gain.end,u+p.duration),d.connect(m);let f=m;if(p.filter&&"function"==typeof t.createBiquadFilter){const e=t.createBiquadFilter();e.type=p.filter.type,e.frequency.setValueAtTime(p.filter.frequency,u),e.Q.setValueAtTime(p.filter.q,u),m.connect(e),f=e,y.push(e)}f.connect(i);const w={oscillator:d,nodes:y,scope:a.voiceScope};e.activeVoices.add(w),null==(c=a.voiceScope)||c.add(w),null==(l=d.addEventListener)||l.call(d,"ended",()=>{var t;y.forEach(e=>{try{e.disconnect()}catch(e){}}),null==(t=a.voiceScope)||t.delete(w),e.activeVoices.delete(w)},{once:!0}),d.start(u),d.stop(u+p.duration)}}
54
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
55
+ * @description Synthesizes and plays a procedural audio keystroke sound in real time without audio assets.
56
+ * @param {string} [profile='mechanical-blue'] - Sound profile name ('mechanical-blue', 'mechanical-red', 'mechanical-brown', 'teletype', 'cyber-synth', 'terminal-8bit', 'bubble').
57
+ * @param {Object} [options={}] - Keystroke playback parameters.
58
+ * @returns {void}
59
+ *
60
+ * -------------
61
+ *
62
+ * @Properties
63
+ * @param {string} [profile='mechanical-blue'] - Synthesizer profile.
64
+ * @param {string} [options.char=' '] - Typed character (differentiates spacebar, return bells, normal keys).
65
+ * @param {number} [options.pan=0] - Stereo pan from -1.0 (full left) to +1.0 (full right).
66
+ * @param {number} [options.pitchVariance=0.06] - Organic pitch variation percentage (±0.06 = ±6%).
67
+ *
68
+ * -------------
69
+ *
70
+ * @example
71
+ *
72
+ * // Play a clicky switch sound
73
+ * TypeFlowWebAudio.play('mechanical-blue');
74
+ *
75
+ * // Play a cyber synth keystroke panned slightly to the right
76
+ * TypeFlowWebAudio.play('cyber-synth', { char: 'A', pan: 0.3 });
77
+ *
78
+ * // Play a teletype carriage bell on newline
79
+ * TypeFlowWebAudio.play('teletype', { char: '\n' });
80
+ */static play(t="mechanical-blue",n={}){var a,i;if(!0===n.respectReducedMotion&&"undefined"!=typeof window&&(null==(a=window.matchMedia)?void 0:a.call(window,"(prefers-reduced-motion: reduce)").matches))return;const r=e.getContext();if(!r||e.isMuted)return;const o=r.currentTime,c=n.char||" ",l=1+(2*Math.random()-1)*(null!=(i=n.pitchVariance)?i:.06);let s=e.masterGain;if("function"==typeof r.createStereoPanner&&"number"==typeof n.pan){const t=Math.max(-1,Math.min(1,n.pan));let a=e.panners.get(t);a||(a=r.createStereoPanner(),a.pan.setValueAtTime(t,o),a.connect(e.masterGain),e.panners.set(t,a)),s=a}const u=String(t).toLowerCase(),p=e.getAudioPreset(u);if(p)e.playPreset(r,p,n,s);else if("mechanical-blue"===u||"clicky"===u){const e=" "===c||"\n"===c,t=e?140:200,n=r.createOscillator(),a=r.createGain();n.type="triangle",n.frequency.setValueAtTime((e?2800:3400)*l,o),a.gain.setValueAtTime(.2,o),a.gain.exponentialRampToValueAtTime(.001,o+.008),n.connect(a),a.connect(s),n.start(o),n.stop(o+.008);const i=r.createOscillator(),u=r.createGain();i.type="sine",i.frequency.setValueAtTime(t*l,o),i.frequency.exponentialRampToValueAtTime(50,o+.035),u.gain.setValueAtTime(.35,o),u.gain.exponentialRampToValueAtTime(.001,o+.035),i.connect(u),u.connect(s),i.start(o),i.stop(o+.035)}else if("mechanical-red"===u||"mechanical-brown"===u||"tactile"===u||"mechanical"===u){const e=r.createOscillator(),t=r.createGain();e.type=u.includes("brown")?"triangle":"sine",e.frequency.setValueAtTime(220*l,o),e.frequency.exponentialRampToValueAtTime(70,o+.028),t.gain.setValueAtTime(.28,o),t.gain.exponentialRampToValueAtTime(.001,o+.028),e.connect(t),t.connect(s),e.start(o),e.stop(o+.028)}else if("teletype"===u||"vintage"===u||"typewriter"===u)if("\n"===c){const e=r.createOscillator(),t=r.createGain();e.type="sine",e.frequency.setValueAtTime(1760,o),t.gain.setValueAtTime(.3,o),t.gain.exponentialRampToValueAtTime(.001,o+.35),e.connect(t),t.connect(s),e.start(o),e.stop(o+.35)}else{const e=r.createOscillator(),t=r.createGain();e.type="square",e.frequency.setValueAtTime(320*l,o),e.frequency.exponentialRampToValueAtTime(90,o+.022),t.gain.setValueAtTime(.18,o),t.gain.exponentialRampToValueAtTime(.001,o+.022),e.connect(t),t.connect(s),e.start(o),e.stop(o+.022)}else if("cyber-synth"===u||"synth"===u||"neon"===u){const e=r.createOscillator(),t=r.createGain();e.type="sawtooth",e.frequency.setValueAtTime(950*l,o),e.frequency.exponentialRampToValueAtTime(180,o+.045),t.gain.setValueAtTime(.12,o),t.gain.exponentialRampToValueAtTime(.001,o+.045),e.connect(t),t.connect(s),e.start(o),e.stop(o+.045)}else if("terminal-8bit"===u||"8bit"===u||"beep"===u){const e=r.createOscillator(),t=r.createGain();e.type="square",e.frequency.setValueAtTime((" "===c?440:880)*l,o),t.gain.setValueAtTime(.08,o),t.gain.exponentialRampToValueAtTime(.001,o+.025),e.connect(t),t.connect(s),e.start(o),e.stop(o+.025)}else if("bubble"===u||"pop"===u){const e=r.createOscillator(),t=r.createGain();e.type="sine",e.frequency.setValueAtTime(450*l,o),e.frequency.exponentialRampToValueAtTime(1100*l,o+.03),t.gain.setValueAtTime(.25,o),t.gain.exponentialRampToValueAtTime(.001,o+.035),e.connect(t),t.connect(s),e.start(o),e.stop(o+.035)}}};c(f,"ctx",null),c(f,"masterGain",null),c(f,"isMuted",!1),c(f,"volume",.25),c(f,"maxPolyphony",24),c(f,"customPresets",new Map),c(f,"activeVoices",new Set),c(f,"panners",new Map),c(f,"unlockHandler",null);let w=f;function h(e="mechanical-blue",c={}){const l=new Set,s=()=>{return w.play(e,(s=((e,t)=>{for(var n in t||(t={}))i.call(t,n)&&o(e,n,t[n]);if(a)for(var n of a(t))r.call(t,n)&&o(e,n,t[n]);return e})({},c),t(s,n({voiceScope:l}))));var s};return s.stop=()=>w.stopVoices(l),s}"undefined"!=typeof window&&(window.TypeFlowWebAudio=w,window.createKeystrokeAudio=h);export{d as AUDIO_PRESETS,m as AUDIO_PRESET_ALIASES,l as AUDIO_PRESET_SCHEMA_VERSION,w as TypeFlowWebAudio,h as createKeystrokeAudio,y as validateAudioPreset};
81
+ //# sourceMappingURL=typeflow-webaudio.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeflow-webaudio.mjs","sources":["../../../src/typeflow-audio-presets.js","../../../src/typeflow-webaudio.js"],"sourcesContent":["export const AUDIO_PRESET_SCHEMA_VERSION = 1;\n\nconst freeze = (value) => {\n if (value && typeof value === 'object' && !Object.isFrozen(value)) {\n Object.values(value).forEach(freeze);\n Object.freeze(value);\n }\n return value;\n};\n\nconst layer = (oscillator, start, end, gain, duration, extras = {}) => ({\n oscillator,\n frequency: { start, end },\n gain: { start: gain, end: 0.001 },\n duration,\n filter: extras.filter ?? null,\n detune: { base: extras.detune ?? 0, variance: extras.variance ?? 0.06 },\n});\n\nconst preset = (type, erase) => ({\n version: AUDIO_PRESET_SCHEMA_VERSION,\n actions: { type, erase: erase ?? type },\n});\n\nexport const AUDIO_PRESETS = freeze({\n 'mechanical-blue': preset(\n [layer('triangle', 3400, 900, 0.18, 0.008), layer('sine', 200, 50, 0.32, 0.035)],\n [layer('triangle', 900, 180, 0.12, 0.025)]\n ),\n 'mechanical-red': preset(\n [layer('sine', 220, 70, 0.26, 0.028)],\n [layer('sine', 150, 55, 0.12, 0.022)]\n ),\n 'mechanical-brown': preset(\n [layer('triangle', 240, 75, 0.27, 0.032)],\n [layer('triangle', 170, 60, 0.13, 0.024)]\n ),\n 'soft-keyboard': preset(\n [\n layer('sine', 520, 280, 0.08, 0.018, {\n filter: { type: 'lowpass', frequency: 1200, q: 0.7 },\n }),\n ],\n [layer('sine', 320, 180, 0.06, 0.016)]\n ),\n typewriter: preset(\n [layer('square', 320, 90, 0.17, 0.022)],\n [layer('square', 180, 70, 0.13, 0.026)]\n ),\n 'terminal-beep': preset(\n [layer('square', 880, 880, 0.08, 0.025)],\n [layer('square', 440, 330, 0.06, 0.03)]\n ),\n synth: preset(\n [layer('sawtooth', 950, 180, 0.12, 0.045)],\n [layer('sawtooth', 420, 120, 0.09, 0.04)]\n ),\n correction: preset(\n [layer('triangle', 360, 110, 0.1, 0.035)],\n [layer('triangle', 260, 80, 0.14, 0.045)]\n ),\n});\n\nexport const AUDIO_PRESET_ALIASES = freeze({\n clicky: 'mechanical-blue',\n mechanical: 'mechanical-red',\n tactile: 'mechanical-brown',\n teletype: 'typewriter',\n vintage: 'typewriter',\n 'terminal-8bit': 'terminal-beep',\n '8bit': 'terminal-beep',\n beep: 'terminal-beep',\n 'cyber-synth': 'synth',\n neon: 'synth',\n bubble: 'soft-keyboard',\n pop: 'soft-keyboard',\n});\n\nexport function validateAudioPreset(config) {\n if (\n !config ||\n config.version !== AUDIO_PRESET_SCHEMA_VERSION ||\n !config.actions ||\n typeof config.actions !== 'object'\n ) {\n throw new TypeError(\n `TypeFlowWebAudio: preset must use schema version ${AUDIO_PRESET_SCHEMA_VERSION}.`\n );\n }\n const output = { version: AUDIO_PRESET_SCHEMA_VERSION, actions: {} };\n for (const action of ['type', 'erase']) {\n const layers = config.actions[action];\n if (!Array.isArray(layers) || layers.length === 0 || layers.length > 4) {\n throw new TypeError(\n `TypeFlowWebAudio: actions.${action} must contain 1-4 oscillator layers.`\n );\n }\n output.actions[action] = layers.map((candidate) => {\n const oscillator = String(candidate.oscillator ?? 'sine');\n if (!['sine', 'square', 'sawtooth', 'triangle'].includes(oscillator))\n throw new RangeError('TypeFlowWebAudio: invalid oscillator type.');\n const duration = Number(candidate.duration);\n const start = Number(candidate.frequency?.start);\n const end = Number(candidate.frequency?.end);\n const gainStart = Number(candidate.gain?.start);\n const gainEnd = Number(candidate.gain?.end);\n if (!(duration >= 0.001 && duration <= 2))\n throw new RangeError('TypeFlowWebAudio: duration must be 0.001-2 seconds.');\n if (!(start >= 20 && start <= 20000 && end >= 20 && end <= 20000))\n throw new RangeError('TypeFlowWebAudio: frequency must be 20-20000 Hz.');\n if (!(gainStart >= 0 && gainStart <= 1 && gainEnd > 0 && gainEnd <= 1))\n throw new RangeError('TypeFlowWebAudio: gain must be within safe bounds.');\n const variance = Number(candidate.detune?.variance ?? 0);\n if (!(variance >= 0 && variance <= 0.5))\n throw new RangeError('TypeFlowWebAudio: detune variance must be 0-0.5.');\n const filter =\n candidate.filter === null || candidate.filter === undefined\n ? null\n : {\n type: String(candidate.filter.type ?? 'lowpass'),\n frequency: Math.max(20, Math.min(20000, Number(candidate.filter.frequency) || 1000)),\n q: Math.max(0.0001, Math.min(1000, Number(candidate.filter.q) || 1)),\n };\n if (filter && !['lowpass', 'highpass', 'bandpass', 'notch'].includes(filter.type))\n throw new RangeError('TypeFlowWebAudio: invalid filter type.');\n return {\n oscillator,\n frequency: { start, end },\n gain: { start: gainStart, end: gainEnd },\n duration,\n filter,\n detune: { base: Number(candidate.detune?.base) || 0, variance },\n };\n });\n }\n return freeze(output);\n}\n","/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow WebAudio - Procedural Audio Synthesis for Typing Interfaces.\n * Synthesizes zero-asset, real-time procedural keystrokes including mechanical switches,\n * vintage teletype strikes, retro terminal beeps, and FM cyber synths with spatial panning.\n *\n * @module @staticcanvas/typeflow/webaudio\n */\n\nimport {\n AUDIO_PRESETS,\n AUDIO_PRESET_ALIASES,\n AUDIO_PRESET_SCHEMA_VERSION,\n validateAudioPreset,\n} from './typeflow-audio-presets.js';\nexport { AUDIO_PRESETS, AUDIO_PRESET_ALIASES, AUDIO_PRESET_SCHEMA_VERSION, validateAudioPreset };\n\nexport class TypeFlowWebAudio {\n static ctx = null;\n static masterGain = null;\n static isMuted = false;\n static volume = 0.25;\n static maxPolyphony = 24;\n static customPresets = new Map();\n static activeVoices = new Set();\n static panners = new Map();\n static unlockHandler = null;\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Initializes or returns the shared Web Audio Context singleton.\n * Automatically attaches unlock listeners to resume audio on the first user interaction.\n * @returns {AudioContext | null} The active AudioContext instance or null in non-browser environments.\n *\n * -------------\n *\n * @example\n * const ctx = TypeFlowWebAudio.getContext();\n * console.log('Audio state:', ctx?.state);\n */\n static getContext() {\n if (typeof window === 'undefined') return null;\n\n if (!TypeFlowWebAudio.ctx) {\n const AudioCtx = window.AudioContext || window.webkitAudioContext;\n if (!AudioCtx) return null;\n\n const activation = window.navigator?.userActivation;\n if (activation && !activation.isActive && !activation.hasBeenActive) {\n if (!TypeFlowWebAudio.unlockHandler) {\n const unlock = () => {\n window.removeEventListener('pointerdown', unlock);\n window.removeEventListener('keydown', unlock);\n TypeFlowWebAudio.unlockHandler = null;\n TypeFlowWebAudio.getContext();\n };\n TypeFlowWebAudio.unlockHandler = unlock;\n window.addEventListener('pointerdown', unlock, { once: true });\n window.addEventListener('keydown', unlock, { once: true });\n }\n return null;\n }\n\n try {\n TypeFlowWebAudio.ctx = new AudioCtx();\n } catch {\n return null;\n }\n TypeFlowWebAudio.masterGain = TypeFlowWebAudio.ctx.createGain();\n TypeFlowWebAudio.masterGain.gain.setValueAtTime(\n TypeFlowWebAudio.isMuted ? 0 : TypeFlowWebAudio.volume,\n TypeFlowWebAudio.ctx.currentTime\n );\n TypeFlowWebAudio.masterGain.connect(TypeFlowWebAudio.ctx.destination);\n }\n\n if (TypeFlowWebAudio.ctx.state === 'suspended') {\n TypeFlowWebAudio.ctx.resume().catch(() => {});\n }\n\n return TypeFlowWebAudio.ctx;\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Sets the master volume level for synthesized keystrokes.\n * @param {number} vol - Volume level from 0.0 (silent) to 1.0 (maximum).\n * @returns {void}\n *\n * -------------\n *\n * @Properties\n * @param {number} vol - Floating-point number between 0.0 and 1.0.\n *\n * -------------\n *\n * @example\n * TypeFlowWebAudio.setVolume(0.15); // Set subtle background volume\n */\n static setVolume(vol) {\n TypeFlowWebAudio.volume = Math.max(0, Math.min(1, vol));\n if (TypeFlowWebAudio.masterGain && TypeFlowWebAudio.ctx && !TypeFlowWebAudio.isMuted) {\n TypeFlowWebAudio.masterGain.gain.setValueAtTime(\n TypeFlowWebAudio.volume,\n TypeFlowWebAudio.ctx.currentTime\n );\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Toggles or forces the mute state of procedural keystroke audio.\n * @param {boolean} [force] - Optional boolean to explicitly force muted (true) or unmuted (false).\n * @returns {void}\n *\n * -------------\n *\n * @Properties\n * @param {boolean} [force] - If omitted, flips the current mute state.\n *\n * -------------\n *\n * @example\n * TypeFlowWebAudio.mute(); // Toggle mute\n * TypeFlowWebAudio.mute(true); // Force mute\n * TypeFlowWebAudio.mute(false); // Force unmute\n */\n static mute(force) {\n TypeFlowWebAudio.isMuted = force !== undefined ? force : !TypeFlowWebAudio.isMuted;\n if (TypeFlowWebAudio.masterGain && TypeFlowWebAudio.ctx) {\n TypeFlowWebAudio.masterGain.gain.setValueAtTime(\n TypeFlowWebAudio.isMuted ? 0 : TypeFlowWebAudio.volume,\n TypeFlowWebAudio.ctx.currentTime\n );\n }\n }\n\n /** Registers a validated instance-safe preset. Built-in collisions require `overwrite: true`. */\n static registerAudioPreset(name, config, options = {}) {\n const key = String(name ?? '')\n .trim()\n .toLowerCase();\n if (!/^[a-z0-9][a-z0-9-]*$/.test(key))\n throw new TypeError('TypeFlowWebAudio: invalid preset name.');\n if (\n !options.overwrite &&\n (key in AUDIO_PRESETS ||\n key in AUDIO_PRESET_ALIASES ||\n TypeFlowWebAudio.customPresets.has(key))\n ) {\n throw new Error(`TypeFlowWebAudio: preset \\`${key}\\` already exists.`);\n }\n TypeFlowWebAudio.customPresets.set(key, validateAudioPreset(config));\n return key;\n }\n\n /** Removes a custom preset. Immutable built-ins cannot be removed. */\n static removeAudioPreset(name) {\n return TypeFlowWebAudio.customPresets.delete(\n String(name ?? '')\n .trim()\n .toLowerCase()\n );\n }\n\n /** Removes every custom preset without affecting immutable built-ins. */\n static resetAudioPresets() {\n TypeFlowWebAudio.customPresets.clear();\n }\n\n /** Returns a frozen custom or built-in preset, resolving compatibility aliases. */\n static getAudioPreset(name) {\n const key = String(name ?? '')\n .trim()\n .toLowerCase();\n if (TypeFlowWebAudio.customPresets.has(key)) return TypeFlowWebAudio.customPresets.get(key);\n return AUDIO_PRESETS[AUDIO_PRESET_ALIASES[key] ?? key] ?? null;\n }\n\n /** Stops and disconnects a scoped set of voices. */\n static stopVoices(voices) {\n Array.from(voices).forEach((voice) => {\n try {\n voice.oscillator.stop();\n } catch {\n /* already stopped */\n }\n voice.nodes.forEach((node) => {\n try {\n node.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n voice.scope?.delete(voice);\n TypeFlowWebAudio.activeVoices.delete(voice);\n });\n }\n\n /** Stops and disconnects all voices created by this extension. */\n static stopAll() {\n TypeFlowWebAudio.stopVoices(TypeFlowWebAudio.activeVoices);\n }\n\n /** Tears down voices and the shared AudioContext so repeated setup is clean. */\n static async destroy() {\n TypeFlowWebAudio.stopAll();\n if (TypeFlowWebAudio.unlockHandler && typeof window !== 'undefined') {\n window.removeEventListener('pointerdown', TypeFlowWebAudio.unlockHandler);\n window.removeEventListener('keydown', TypeFlowWebAudio.unlockHandler);\n TypeFlowWebAudio.unlockHandler = null;\n }\n try {\n TypeFlowWebAudio.masterGain?.disconnect();\n } catch {\n /* already disconnected */\n }\n TypeFlowWebAudio.panners.forEach((panner) => {\n try {\n panner.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n TypeFlowWebAudio.panners.clear();\n if (TypeFlowWebAudio.ctx?.close) await TypeFlowWebAudio.ctx.close().catch(() => {});\n TypeFlowWebAudio.ctx = null;\n TypeFlowWebAudio.masterGain = null;\n }\n\n static playPreset(ctx, config, options, destination) {\n const action = options.action === 'erase' ? 'erase' : 'type';\n const now = ctx.currentTime;\n for (const layer of config.actions[action]) {\n while (TypeFlowWebAudio.activeVoices.size >= TypeFlowWebAudio.maxPolyphony) {\n const oldest = TypeFlowWebAudio.activeVoices.values().next().value;\n try {\n oldest.oscillator.stop();\n } catch {\n /* already stopped */\n }\n oldest.nodes.forEach((node) => {\n try {\n node.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n oldest.scope?.delete(oldest);\n TypeFlowWebAudio.activeVoices.delete(oldest);\n }\n const variance = options.pitchVariance ?? layer.detune.variance;\n const jitter = 1 + (Math.random() * 2 - 1) * variance;\n const oscillator = ctx.createOscillator();\n const gain = ctx.createGain();\n const nodes = [oscillator, gain];\n oscillator.type = layer.oscillator;\n oscillator.detune.setValueAtTime(layer.detune.base, now);\n oscillator.frequency.setValueAtTime(layer.frequency.start * jitter, now);\n oscillator.frequency.exponentialRampToValueAtTime(\n layer.frequency.end * jitter,\n now + layer.duration\n );\n gain.gain.setValueAtTime(layer.gain.start, now);\n gain.gain.exponentialRampToValueAtTime(layer.gain.end, now + layer.duration);\n oscillator.connect(gain);\n let tail = gain;\n if (layer.filter && typeof ctx.createBiquadFilter === 'function') {\n const filter = ctx.createBiquadFilter();\n filter.type = layer.filter.type;\n filter.frequency.setValueAtTime(layer.filter.frequency, now);\n filter.Q.setValueAtTime(layer.filter.q, now);\n gain.connect(filter);\n tail = filter;\n nodes.push(filter);\n }\n tail.connect(destination);\n const voice = { oscillator, nodes, scope: options.voiceScope };\n TypeFlowWebAudio.activeVoices.add(voice);\n options.voiceScope?.add(voice);\n oscillator.addEventListener?.(\n 'ended',\n () => {\n nodes.forEach((node) => {\n try {\n node.disconnect();\n } catch {\n /* already disconnected */\n }\n });\n options.voiceScope?.delete(voice);\n TypeFlowWebAudio.activeVoices.delete(voice);\n },\n { once: true }\n );\n oscillator.start(now);\n oscillator.stop(now + layer.duration);\n }\n }\n\n /** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Synthesizes and plays a procedural audio keystroke sound in real time without audio assets.\n * @param {string} [profile='mechanical-blue'] - Sound profile name ('mechanical-blue', 'mechanical-red', 'mechanical-brown', 'teletype', 'cyber-synth', 'terminal-8bit', 'bubble').\n * @param {Object} [options={}] - Keystroke playback parameters.\n * @returns {void}\n *\n * -------------\n *\n * @Properties\n * @param {string} [profile='mechanical-blue'] - Synthesizer profile.\n * @param {string} [options.char=' '] - Typed character (differentiates spacebar, return bells, normal keys).\n * @param {number} [options.pan=0] - Stereo pan from -1.0 (full left) to +1.0 (full right).\n * @param {number} [options.pitchVariance=0.06] - Organic pitch variation percentage (±0.06 = ±6%).\n *\n * -------------\n *\n * @example\n *\n * // Play a clicky switch sound\n * TypeFlowWebAudio.play('mechanical-blue');\n *\n * // Play a cyber synth keystroke panned slightly to the right\n * TypeFlowWebAudio.play('cyber-synth', { char: 'A', pan: 0.3 });\n *\n * // Play a teletype carriage bell on newline\n * TypeFlowWebAudio.play('teletype', { char: '\\n' });\n */\n static play(profile = 'mechanical-blue', options = {}) {\n if (\n options.respectReducedMotion === true &&\n typeof window !== 'undefined' &&\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches\n )\n return;\n const ctx = TypeFlowWebAudio.getContext();\n if (!ctx || TypeFlowWebAudio.isMuted) return;\n\n const t = ctx.currentTime;\n const char = options.char || ' ';\n const jitter = 1 + (Math.random() * 2 - 1) * (options.pitchVariance ?? 0.06);\n\n // Optional spatial stereo panner\n let dest = TypeFlowWebAudio.masterGain;\n if (typeof ctx.createStereoPanner === 'function' && typeof options.pan === 'number') {\n const pan = Math.max(-1, Math.min(1, options.pan));\n let panner = TypeFlowWebAudio.panners.get(pan);\n if (!panner) {\n panner = ctx.createStereoPanner();\n panner.pan.setValueAtTime(pan, t);\n panner.connect(TypeFlowWebAudio.masterGain);\n TypeFlowWebAudio.panners.set(pan, panner);\n }\n dest = panner;\n }\n\n const normProfile = String(profile).toLowerCase();\n const presetConfig = TypeFlowWebAudio.getAudioPreset(normProfile);\n if (presetConfig) {\n TypeFlowWebAudio.playPreset(ctx, presetConfig, options, dest);\n return;\n }\n\n // 1. Mechanical Clicky Switch (Blue style)\n if (normProfile === 'mechanical-blue' || normProfile === 'clicky') {\n const isSpace = char === ' ' || char === '\\n';\n const baseFreq = isSpace ? 140 : 200;\n\n // Click snap (high transient)\n const snapOsc = ctx.createOscillator();\n const snapGain = ctx.createGain();\n snapOsc.type = 'triangle';\n snapOsc.frequency.setValueAtTime((isSpace ? 2800 : 3400) * jitter, t);\n snapGain.gain.setValueAtTime(0.2, t);\n snapGain.gain.exponentialRampToValueAtTime(0.001, t + 0.008);\n snapOsc.connect(snapGain);\n snapGain.connect(dest);\n snapOsc.start(t);\n snapOsc.stop(t + 0.008);\n\n // Bottom-out thud (low resonant body)\n const thudOsc = ctx.createOscillator();\n const thudGain = ctx.createGain();\n thudOsc.type = 'sine';\n thudOsc.frequency.setValueAtTime(baseFreq * jitter, t);\n thudOsc.frequency.exponentialRampToValueAtTime(50, t + 0.035);\n thudGain.gain.setValueAtTime(0.35, t);\n thudGain.gain.exponentialRampToValueAtTime(0.001, t + 0.035);\n thudOsc.connect(thudGain);\n thudGain.connect(dest);\n thudOsc.start(t);\n thudOsc.stop(t + 0.035);\n }\n // 2. Mechanical Linear / Tactile (Red / Brown style)\n else if (\n normProfile === 'mechanical-red' ||\n normProfile === 'mechanical-brown' ||\n normProfile === 'tactile' ||\n normProfile === 'mechanical'\n ) {\n const thudOsc = ctx.createOscillator();\n const thudGain = ctx.createGain();\n thudOsc.type = normProfile.includes('brown') ? 'triangle' : 'sine';\n thudOsc.frequency.setValueAtTime(220 * jitter, t);\n thudOsc.frequency.exponentialRampToValueAtTime(70, t + 0.028);\n thudGain.gain.setValueAtTime(0.28, t);\n thudGain.gain.exponentialRampToValueAtTime(0.001, t + 0.028);\n thudOsc.connect(thudGain);\n thudGain.connect(dest);\n thudOsc.start(t);\n thudOsc.stop(t + 0.028);\n }\n // 3. Vintage Teletype / Solenoid Strike & Carriage Bell\n else if (\n normProfile === 'teletype' ||\n normProfile === 'vintage' ||\n normProfile === 'typewriter'\n ) {\n if (char === '\\n') {\n const bellOsc = ctx.createOscillator();\n const bellGain = ctx.createGain();\n bellOsc.type = 'sine';\n bellOsc.frequency.setValueAtTime(1760, t);\n bellGain.gain.setValueAtTime(0.3, t);\n bellGain.gain.exponentialRampToValueAtTime(0.001, t + 0.35);\n bellOsc.connect(bellGain);\n bellGain.connect(dest);\n bellOsc.start(t);\n bellOsc.stop(t + 0.35);\n } else {\n const clackOsc = ctx.createOscillator();\n const clackGain = ctx.createGain();\n clackOsc.type = 'square';\n clackOsc.frequency.setValueAtTime(320 * jitter, t);\n clackOsc.frequency.exponentialRampToValueAtTime(90, t + 0.022);\n clackGain.gain.setValueAtTime(0.18, t);\n clackGain.gain.exponentialRampToValueAtTime(0.001, t + 0.022);\n clackOsc.connect(clackGain);\n clackGain.connect(dest);\n clackOsc.start(t);\n clackOsc.stop(t + 0.022);\n }\n }\n // 4. Sci-Fi Cyber Synth / Laser Chirp\n else if (normProfile === 'cyber-synth' || normProfile === 'synth' || normProfile === 'neon') {\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sawtooth';\n osc.frequency.setValueAtTime(950 * jitter, t);\n osc.frequency.exponentialRampToValueAtTime(180, t + 0.045);\n gain.gain.setValueAtTime(0.12, t);\n gain.gain.exponentialRampToValueAtTime(0.001, t + 0.045);\n osc.connect(gain);\n gain.connect(dest);\n osc.start(t);\n osc.stop(t + 0.045);\n }\n // 5. Retro 8-bit Terminal Beep\n else if (normProfile === 'terminal-8bit' || normProfile === '8bit' || normProfile === 'beep') {\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'square';\n osc.frequency.setValueAtTime((char === ' ' ? 440 : 880) * jitter, t);\n gain.gain.setValueAtTime(0.08, t);\n gain.gain.exponentialRampToValueAtTime(0.001, t + 0.025);\n osc.connect(gain);\n gain.connect(dest);\n osc.start(t);\n osc.stop(t + 0.025);\n }\n // 6. Liquid Bubble Pop\n else if (normProfile === 'bubble' || normProfile === 'pop') {\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.type = 'sine';\n osc.frequency.setValueAtTime(450 * jitter, t);\n osc.frequency.exponentialRampToValueAtTime(1100 * jitter, t + 0.03);\n gain.gain.setValueAtTime(0.25, t);\n gain.gain.exponentialRampToValueAtTime(0.001, t + 0.035);\n osc.connect(gain);\n gain.connect(dest);\n osc.start(t);\n osc.stop(t + 0.035);\n }\n }\n}\n\n/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description Creates an audio callback function configured for TypeFlow's `audio` option.\n * @param {string} [profile='mechanical-blue'] - Sound profile identifier.\n * @param {Object} [options={}] - Custom audio synthesis options.\n * @returns {() => void} Zero-argument callback function invoked per keystroke.\n *\n * -------------\n *\n * @Properties\n * @param {string} [profile='mechanical-blue'] - Sound profile name.\n * @param {number} [options.pan=0] - Stereo pan (-1.0 to 1.0).\n * @param {number} [options.pitchVariance=0.06] - Random pitch variance.\n *\n * -------------\n *\n * @example\n * import { TypeFlow } from '@staticcanvas/typeflow';\n * import { createKeystrokeAudio } from '@staticcanvas/typeflow/webaudio';\n *\n * TypeFlow.type('#headline', 'Mechanical Sound Effects', {\n * audio: createKeystrokeAudio('mechanical-blue', { pan: 0.1 }),\n * speed: 40,\n * });\n */\nexport function createKeystrokeAudio(profile = 'mechanical-blue', options = {}) {\n const voiceScope = new Set();\n const play = () => TypeFlowWebAudio.play(profile, { ...options, voiceScope });\n play.stop = () => TypeFlowWebAudio.stopVoices(voiceScope);\n return play;\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowWebAudio = TypeFlowWebAudio;\n window.createKeystrokeAudio = createKeystrokeAudio;\n}\n"],"names":["AUDIO_PRESET_SCHEMA_VERSION","freeze","value","Object","isFrozen","values","forEach","layer","oscillator","start","end","gain","duration","extras","_a","_b","_c","frequency","filter","detune","base","variance","preset","type","erase","version","actions","AUDIO_PRESETS","q","typewriter","synth","correction","AUDIO_PRESET_ALIASES","clicky","mechanical","tactile","teletype","vintage","beep","neon","bubble","pop","validateAudioPreset","config","TypeError","output","action","layers","Array","isArray","length","map","candidate","_d","_e","_f","_g","_h","_i","String","includes","RangeError","Number","gainStart","gainEnd","Math","max","min","_TypeFlowWebAudio","getContext","window","ctx","AudioCtx","AudioContext","webkitAudioContext","activation","navigator","userActivation","isActive","hasBeenActive","unlockHandler","unlock","removeEventListener","addEventListener","once","e","masterGain","createGain","setValueAtTime","isMuted","volume","currentTime","connect","destination","state","resume","catch","setVolume","vol","mute","force","registerAudioPreset","name","options","key","trim","toLowerCase","test","overwrite","customPresets","has","Error","set","removeAudioPreset","delete","resetAudioPresets","clear","getAudioPreset","get","stopVoices","voices","from","voice","stop","nodes","node","disconnect","scope","activeVoices","stopAll","destroy","this","panners","panner","close","playPreset","now","size","maxPolyphony","oldest","next","pitchVariance","jitter","random","createOscillator","exponentialRampToValueAtTime","tail","createBiquadFilter","Q","push","voiceScope","add","call","play","profile","respectReducedMotion","matchMedia","matches","t","char","dest","createStereoPanner","pan","normProfile","presetConfig","isSpace","baseFreq","snapOsc","snapGain","thudOsc","thudGain","bellOsc","bellGain","clackOsc","clackGain","osc","__publicField","Map","Set","TypeFlowWebAudio","createKeystrokeAudio","__spreadValues"],"mappings":"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"}