@codexo/exojs 0.8.4 → 0.9.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 (317) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +26 -5
  3. package/dist/esm/animation/Tween.d.ts +20 -3
  4. package/dist/esm/animation/Tween.js +16 -8
  5. package/dist/esm/animation/Tween.js.map +1 -1
  6. package/dist/esm/animation/TweenManager.d.ts +16 -1
  7. package/dist/esm/animation/TweenManager.js +27 -2
  8. package/dist/esm/animation/TweenManager.js.map +1 -1
  9. package/dist/esm/audio/AudioAnalyser.d.ts +2 -0
  10. package/dist/esm/audio/AudioAnalyser.js +24 -9
  11. package/dist/esm/audio/AudioAnalyser.js.map +1 -1
  12. package/dist/esm/audio/AudioBus.d.ts +1 -0
  13. package/dist/esm/audio/AudioBus.js +8 -4
  14. package/dist/esm/audio/AudioBus.js.map +1 -1
  15. package/dist/esm/audio/AudioListener.d.ts +1 -0
  16. package/dist/esm/audio/AudioListener.js +7 -5
  17. package/dist/esm/audio/AudioListener.js.map +1 -1
  18. package/dist/esm/audio/AudioManager.d.ts +8 -2
  19. package/dist/esm/audio/AudioManager.js +14 -1
  20. package/dist/esm/audio/AudioManager.js.map +1 -1
  21. package/dist/esm/audio/BeatDetector.d.ts +2 -0
  22. package/dist/esm/audio/BeatDetector.js +27 -661
  23. package/dist/esm/audio/BeatDetector.js.map +1 -1
  24. package/dist/esm/audio/Music.d.ts +1 -0
  25. package/dist/esm/audio/Music.js +7 -3
  26. package/dist/esm/audio/Music.js.map +1 -1
  27. package/dist/esm/audio/OscillatorSound.d.ts +1 -0
  28. package/dist/esm/audio/OscillatorSound.js +7 -3
  29. package/dist/esm/audio/OscillatorSound.js.map +1 -1
  30. package/dist/esm/audio/Sound.d.ts +1 -0
  31. package/dist/esm/audio/Sound.js +7 -3
  32. package/dist/esm/audio/Sound.js.map +1 -1
  33. package/dist/esm/audio/audio-context.d.ts +2 -2
  34. package/dist/esm/audio/audio-context.js +4 -4
  35. package/dist/esm/audio/audio-context.js.map +1 -1
  36. package/dist/esm/audio/filters/ChorusFilter.d.ts +1 -0
  37. package/dist/esm/audio/filters/ChorusFilter.js +7 -3
  38. package/dist/esm/audio/filters/ChorusFilter.js.map +1 -1
  39. package/dist/esm/audio/filters/CompressorFilter.d.ts +1 -0
  40. package/dist/esm/audio/filters/CompressorFilter.js +7 -3
  41. package/dist/esm/audio/filters/CompressorFilter.js.map +1 -1
  42. package/dist/esm/audio/filters/DelayFilter.d.ts +1 -0
  43. package/dist/esm/audio/filters/DelayFilter.js +7 -3
  44. package/dist/esm/audio/filters/DelayFilter.js.map +1 -1
  45. package/dist/esm/audio/filters/EqualizerFilter.d.ts +1 -0
  46. package/dist/esm/audio/filters/EqualizerFilter.js +7 -3
  47. package/dist/esm/audio/filters/EqualizerFilter.js.map +1 -1
  48. package/dist/esm/audio/filters/GranularFilter.js +1 -86
  49. package/dist/esm/audio/filters/GranularFilter.js.map +1 -1
  50. package/dist/esm/audio/filters/HighpassFilter.d.ts +1 -0
  51. package/dist/esm/audio/filters/HighpassFilter.js +7 -3
  52. package/dist/esm/audio/filters/HighpassFilter.js.map +1 -1
  53. package/dist/esm/audio/filters/LowpassFilter.d.ts +1 -0
  54. package/dist/esm/audio/filters/LowpassFilter.js +7 -3
  55. package/dist/esm/audio/filters/LowpassFilter.js.map +1 -1
  56. package/dist/esm/audio/filters/PitchShiftFilter.js +1 -71
  57. package/dist/esm/audio/filters/PitchShiftFilter.js.map +1 -1
  58. package/dist/esm/audio/filters/ReverbFilter.d.ts +1 -0
  59. package/dist/esm/audio/filters/ReverbFilter.js +7 -3
  60. package/dist/esm/audio/filters/ReverbFilter.js.map +1 -1
  61. package/dist/esm/audio/filters/VocoderFilter.js +1 -89
  62. package/dist/esm/audio/filters/VocoderFilter.js.map +1 -1
  63. package/dist/esm/audio/filters/WorkletFilter.d.ts +2 -0
  64. package/dist/esm/audio/filters/WorkletFilter.js +10 -5
  65. package/dist/esm/audio/filters/WorkletFilter.js.map +1 -1
  66. package/dist/esm/audio/index.d.ts +15 -10
  67. package/dist/esm/audio/worklet/registerWorklet.d.ts +1 -1
  68. package/dist/esm/audio/worklet/registerWorklet.js +2 -2
  69. package/dist/esm/audio/worklet/registerWorklet.js.map +1 -1
  70. package/dist/esm/audio/worklets/beat-detector.worklet.d.ts +1 -0
  71. package/dist/esm/audio/worklets/beat-detector.worklet.js +647 -0
  72. package/dist/esm/audio/worklets/beat-detector.worklet.js.map +1 -0
  73. package/dist/esm/audio/worklets/granular.worklet.d.ts +1 -0
  74. package/dist/esm/audio/worklets/granular.worklet.js +89 -0
  75. package/dist/esm/audio/worklets/granular.worklet.js.map +1 -0
  76. package/dist/esm/audio/worklets/pitch-shift.worklet.d.ts +1 -0
  77. package/dist/esm/audio/worklets/pitch-shift.worklet.js +74 -0
  78. package/dist/esm/audio/worklets/pitch-shift.worklet.js.map +1 -0
  79. package/dist/esm/audio/worklets/vocoder.worklet.d.ts +1 -0
  80. package/dist/esm/audio/worklets/vocoder.worklet.js +92 -0
  81. package/dist/esm/audio/worklets/vocoder.worklet.js.map +1 -0
  82. package/dist/esm/core/Application.d.ts +62 -24
  83. package/dist/esm/core/Application.js +116 -40
  84. package/dist/esm/core/Application.js.map +1 -1
  85. package/dist/esm/core/Color.d.ts +0 -8
  86. package/dist/esm/core/Color.js +0 -24
  87. package/dist/esm/core/Color.js.map +1 -1
  88. package/dist/esm/core/Scene.d.ts +32 -72
  89. package/dist/esm/core/Scene.js +56 -36
  90. package/dist/esm/core/Scene.js.map +1 -1
  91. package/dist/esm/core/SceneManager.d.ts +11 -25
  92. package/dist/esm/core/SceneManager.js +37 -100
  93. package/dist/esm/core/SceneManager.js.map +1 -1
  94. package/dist/esm/core/SceneNode.d.ts +28 -14
  95. package/dist/esm/core/SceneNode.js +66 -33
  96. package/dist/esm/core/SceneNode.js.map +1 -1
  97. package/dist/esm/core/Signal.d.ts +24 -28
  98. package/dist/esm/core/Signal.js +64 -50
  99. package/dist/esm/core/Signal.js.map +1 -1
  100. package/dist/esm/core/Timer.d.ts +1 -0
  101. package/dist/esm/core/Timer.js +3 -0
  102. package/dist/esm/core/Timer.js.map +1 -1
  103. package/dist/esm/debug/BoundingBoxesLayer.js +1 -1
  104. package/dist/esm/debug/BoundingBoxesLayer.js.map +1 -1
  105. package/dist/esm/debug/HitTestLayer.js +1 -1
  106. package/dist/esm/debug/HitTestLayer.js.map +1 -1
  107. package/dist/esm/debug/PerformanceLayer.js +1 -2
  108. package/dist/esm/debug/PerformanceLayer.js.map +1 -1
  109. package/dist/esm/debug/PointerStackLayer.js +1 -2
  110. package/dist/esm/debug/PointerStackLayer.js.map +1 -1
  111. package/dist/esm/debug/RenderPassInspectorLayer.js +1 -2
  112. package/dist/esm/debug/RenderPassInspectorLayer.js.map +1 -1
  113. package/dist/esm/index.js +23 -7
  114. package/dist/esm/index.js.map +1 -1
  115. package/dist/esm/input/InputManager.d.ts +1 -0
  116. package/dist/esm/input/InputManager.js +30 -3
  117. package/dist/esm/input/InputManager.js.map +1 -1
  118. package/dist/esm/input/InteractionManager.d.ts +5 -2
  119. package/dist/esm/input/InteractionManager.js +29 -18
  120. package/dist/esm/input/InteractionManager.js.map +1 -1
  121. package/dist/esm/input/Pointer.js +3 -2
  122. package/dist/esm/input/Pointer.js.map +1 -1
  123. package/dist/esm/input/internal/interactionManagerRegistry.d.ts +9 -0
  124. package/dist/esm/input/internal/interactionManagerRegistry.js +10 -0
  125. package/dist/esm/input/internal/interactionManagerRegistry.js.map +1 -0
  126. package/dist/esm/math/AbstractVector.d.ts +1 -7
  127. package/dist/esm/math/AbstractVector.js +6 -20
  128. package/dist/esm/math/AbstractVector.js.map +1 -1
  129. package/dist/esm/math/Circle.js +0 -2
  130. package/dist/esm/math/Circle.js.map +1 -1
  131. package/dist/esm/math/Collision.d.ts +9 -3
  132. package/dist/esm/math/Ellipse.d.ts +2 -5
  133. package/dist/esm/math/Ellipse.js +10 -7
  134. package/dist/esm/math/Ellipse.js.map +1 -1
  135. package/dist/esm/math/ObservableVector.d.ts +1 -1
  136. package/dist/esm/math/ObservableVector.js +3 -3
  137. package/dist/esm/math/ObservableVector.js.map +1 -1
  138. package/dist/esm/math/Polygon.d.ts +0 -2
  139. package/dist/esm/math/Polygon.js +1 -9
  140. package/dist/esm/math/Polygon.js.map +1 -1
  141. package/dist/esm/math/Rectangle.js +0 -2
  142. package/dist/esm/math/Rectangle.js.map +1 -1
  143. package/dist/esm/math/collision-detection.d.ts +19 -4
  144. package/dist/esm/math/collision-detection.js +61 -4
  145. package/dist/esm/math/collision-detection.js.map +1 -1
  146. package/dist/esm/math/swept-collision.d.ts +16 -12
  147. package/dist/esm/math/swept-collision.js +109 -19
  148. package/dist/esm/math/swept-collision.js.map +1 -1
  149. package/dist/esm/particles/ParticleSystem.js +1 -1
  150. package/dist/esm/particles/ParticleSystem.js.map +1 -1
  151. package/dist/esm/particles/index.d.ts +1 -0
  152. package/dist/esm/rendering/CallbackRenderPass.d.ts +1 -0
  153. package/dist/esm/rendering/CallbackRenderPass.js +1 -0
  154. package/dist/esm/rendering/CallbackRenderPass.js.map +1 -1
  155. package/dist/esm/rendering/Container.d.ts +2 -1
  156. package/dist/esm/rendering/Container.js +17 -16
  157. package/dist/esm/rendering/Container.js.map +1 -1
  158. package/dist/esm/rendering/RenderBackend.d.ts +1 -0
  159. package/dist/esm/rendering/RenderNode.d.ts +1 -3
  160. package/dist/esm/rendering/RenderNode.js +12 -19
  161. package/dist/esm/rendering/RenderNode.js.map +1 -1
  162. package/dist/esm/rendering/RenderPass.d.ts +1 -0
  163. package/dist/esm/rendering/RenderStats.d.ts +9 -0
  164. package/dist/esm/rendering/RenderStats.js +2 -0
  165. package/dist/esm/rendering/RenderStats.js.map +1 -1
  166. package/dist/esm/rendering/RendererRegistry.d.ts +1 -0
  167. package/dist/esm/rendering/RendererRegistry.js +1 -0
  168. package/dist/esm/rendering/RendererRegistry.js.map +1 -1
  169. package/dist/esm/rendering/View.d.ts +23 -0
  170. package/dist/esm/rendering/View.js +42 -0
  171. package/dist/esm/rendering/View.js.map +1 -1
  172. package/dist/esm/rendering/index.d.ts +89 -59
  173. package/dist/esm/rendering/mesh/Mesh.js +1 -1
  174. package/dist/esm/rendering/mesh/Mesh.js.map +1 -1
  175. package/dist/esm/rendering/mesh/MeshShader.d.ts +1 -0
  176. package/dist/esm/rendering/mesh/MeshShader.js +1 -0
  177. package/dist/esm/rendering/mesh/MeshShader.js.map +1 -1
  178. package/dist/esm/rendering/shader/Shader.d.ts +1 -0
  179. package/dist/esm/rendering/shader/Shader.js +1 -0
  180. package/dist/esm/rendering/shader/Shader.js.map +1 -1
  181. package/dist/esm/rendering/shader/ShaderUniform.d.ts +1 -0
  182. package/dist/esm/rendering/shader/ShaderUniform.js +1 -0
  183. package/dist/esm/rendering/shader/ShaderUniform.js.map +1 -1
  184. package/dist/esm/rendering/sprite/Sprite.d.ts +3 -2
  185. package/dist/esm/rendering/sprite/Sprite.js +15 -13
  186. package/dist/esm/rendering/sprite/Sprite.js.map +1 -1
  187. package/dist/esm/rendering/text/AbstractText.d.ts +36 -0
  188. package/dist/esm/rendering/text/AbstractText.js +49 -0
  189. package/dist/esm/rendering/text/AbstractText.js.map +1 -0
  190. package/dist/esm/rendering/text/BitmapText.d.ts +97 -0
  191. package/dist/esm/rendering/text/BitmapText.js +220 -0
  192. package/dist/esm/rendering/text/BitmapText.js.map +1 -0
  193. package/dist/esm/rendering/text/BmFont.d.ts +50 -0
  194. package/dist/esm/rendering/text/BmFont.js +24 -0
  195. package/dist/esm/rendering/text/BmFont.js.map +1 -0
  196. package/dist/esm/rendering/text/GlyphAtlas.d.ts +104 -0
  197. package/dist/esm/rendering/text/GlyphAtlas.js +347 -0
  198. package/dist/esm/rendering/text/GlyphAtlas.js.map +1 -0
  199. package/dist/esm/rendering/text/GlyphAtlasPool.d.ts +40 -0
  200. package/dist/esm/rendering/text/GlyphAtlasPool.js +67 -0
  201. package/dist/esm/rendering/text/GlyphAtlasPool.js.map +1 -0
  202. package/dist/esm/rendering/text/GlyphSdf.d.ts +92 -0
  203. package/dist/esm/rendering/text/GlyphSdf.js +220 -0
  204. package/dist/esm/rendering/text/GlyphSdf.js.map +1 -0
  205. package/dist/esm/rendering/text/HTMLText.d.ts +107 -0
  206. package/dist/esm/rendering/text/HTMLText.js +284 -0
  207. package/dist/esm/rendering/text/HTMLText.js.map +1 -0
  208. package/dist/esm/rendering/text/LayoutOptions.d.ts +30 -0
  209. package/dist/esm/rendering/text/Text.d.ts +89 -20
  210. package/dist/esm/rendering/text/Text.js +176 -101
  211. package/dist/esm/rendering/text/Text.js.map +1 -1
  212. package/dist/esm/rendering/text/TextLayout.d.ts +20 -8
  213. package/dist/esm/rendering/text/TextLayout.js +234 -25
  214. package/dist/esm/rendering/text/TextLayout.js.map +1 -1
  215. package/dist/esm/rendering/text/TextStyle.d.ts +154 -87
  216. package/dist/esm/rendering/text/TextStyle.js +257 -203
  217. package/dist/esm/rendering/text/TextStyle.js.map +1 -1
  218. package/dist/esm/rendering/text/types.d.ts +73 -13
  219. package/dist/esm/rendering/texture/Texture.d.ts +1 -0
  220. package/dist/esm/rendering/texture/Texture.js +1 -0
  221. package/dist/esm/rendering/texture/Texture.js.map +1 -1
  222. package/dist/esm/rendering/types.d.ts +3 -1
  223. package/dist/esm/rendering/types.js +2 -0
  224. package/dist/esm/rendering/types.js.map +1 -1
  225. package/dist/esm/rendering/video/Video.d.ts +2 -1
  226. package/dist/esm/rendering/video/Video.js +9 -5
  227. package/dist/esm/rendering/video/Video.js.map +1 -1
  228. package/dist/esm/rendering/webgl2/WebGl2Backend.js +41 -5
  229. package/dist/esm/rendering/webgl2/WebGl2Backend.js.map +1 -1
  230. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.js +4 -4
  231. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.js.map +1 -1
  232. package/dist/esm/rendering/webgl2/WebGl2TextRenderer.d.ts +56 -0
  233. package/dist/esm/rendering/webgl2/WebGl2TextRenderer.js +482 -0
  234. package/dist/esm/rendering/webgl2/WebGl2TextRenderer.js.map +1 -0
  235. package/dist/esm/rendering/webgl2/glsl/text-color.frag.js +4 -0
  236. package/dist/esm/rendering/webgl2/glsl/text-color.frag.js.map +1 -0
  237. package/dist/esm/rendering/webgl2/glsl/text-msdf.frag.js +4 -0
  238. package/dist/esm/rendering/webgl2/glsl/text-msdf.frag.js.map +1 -0
  239. package/dist/esm/rendering/webgl2/glsl/text-sdf.frag.js +4 -0
  240. package/dist/esm/rendering/webgl2/glsl/text-sdf.frag.js.map +1 -0
  241. package/dist/esm/rendering/webgl2/glsl/text.vert.js +4 -0
  242. package/dist/esm/rendering/webgl2/glsl/text.vert.js.map +1 -0
  243. package/dist/esm/rendering/webgpu/WebGpuBackend.js +16 -8
  244. package/dist/esm/rendering/webgpu/WebGpuBackend.js.map +1 -1
  245. package/dist/esm/rendering/webgpu/WebGpuBlendState.js +26 -0
  246. package/dist/esm/rendering/webgpu/WebGpuBlendState.js.map +1 -1
  247. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.d.ts +2 -2
  248. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.js +23 -15
  249. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.js.map +1 -1
  250. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js +9 -1
  251. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js.map +1 -1
  252. package/dist/esm/rendering/webgpu/WebGpuTextRenderer.d.ts +70 -0
  253. package/dist/esm/rendering/webgpu/WebGpuTextRenderer.js +773 -0
  254. package/dist/esm/rendering/webgpu/WebGpuTextRenderer.js.map +1 -0
  255. package/dist/esm/rendering/webgpu/compute/WebGpuComputePipeline.js +96 -0
  256. package/dist/esm/rendering/webgpu/compute/WebGpuComputePipeline.js.map +1 -0
  257. package/dist/esm/rendering/webgpu/compute/WebGpuStorageBuffer.js +68 -0
  258. package/dist/esm/rendering/webgpu/compute/WebGpuStorageBuffer.js.map +1 -0
  259. package/dist/esm/resources/Asset.d.ts +23 -0
  260. package/dist/esm/resources/Asset.js +23 -0
  261. package/dist/esm/resources/Asset.js.map +1 -0
  262. package/dist/esm/resources/AssetDefinitions.d.ts +137 -0
  263. package/dist/esm/resources/Assets.d.ts +35 -0
  264. package/dist/esm/resources/Assets.js +32 -0
  265. package/dist/esm/resources/Assets.js.map +1 -0
  266. package/dist/esm/resources/IndexedDbDatabase.js +17 -1
  267. package/dist/esm/resources/IndexedDbDatabase.js.map +1 -1
  268. package/dist/esm/resources/IndexedDbStore.js +17 -1
  269. package/dist/esm/resources/IndexedDbStore.js.map +1 -1
  270. package/dist/esm/resources/Loader.d.ts +244 -18
  271. package/dist/esm/resources/Loader.js +456 -50
  272. package/dist/esm/resources/Loader.js.map +1 -1
  273. package/dist/esm/resources/LoadingQueue.d.ts +28 -0
  274. package/dist/esm/resources/LoadingQueue.js +59 -0
  275. package/dist/esm/resources/LoadingQueue.js.map +1 -0
  276. package/dist/esm/resources/factories/BmFontFactory.d.ts +25 -0
  277. package/dist/esm/resources/factories/BmFontFactory.js +96 -0
  278. package/dist/esm/resources/factories/BmFontFactory.js.map +1 -0
  279. package/dist/esm/resources/factories/CsvFactory.d.ts +35 -0
  280. package/dist/esm/resources/factories/CsvFactory.js +87 -0
  281. package/dist/esm/resources/factories/CsvFactory.js.map +1 -0
  282. package/dist/esm/resources/factories/MusicFactory.d.ts +8 -2
  283. package/dist/esm/resources/factories/MusicFactory.js +25 -14
  284. package/dist/esm/resources/factories/MusicFactory.js.map +1 -1
  285. package/dist/esm/resources/factories/SoundFactory.d.ts +2 -2
  286. package/dist/esm/resources/factories/SoundFactory.js.map +1 -1
  287. package/dist/esm/resources/factories/SubtitleFactory.d.ts +28 -0
  288. package/dist/esm/resources/factories/SubtitleFactory.js +203 -0
  289. package/dist/esm/resources/factories/SubtitleFactory.js.map +1 -0
  290. package/dist/esm/resources/factories/SvgFactory.d.ts +18 -1
  291. package/dist/esm/resources/factories/SvgFactory.js +21 -2
  292. package/dist/esm/resources/factories/SvgFactory.js.map +1 -1
  293. package/dist/esm/resources/factories/VideoFactory.d.ts +8 -2
  294. package/dist/esm/resources/factories/VideoFactory.js +27 -20
  295. package/dist/esm/resources/factories/VideoFactory.js.map +1 -1
  296. package/dist/esm/resources/factories/XmlFactory.d.ts +24 -0
  297. package/dist/esm/resources/factories/XmlFactory.js +37 -0
  298. package/dist/esm/resources/factories/XmlFactory.js.map +1 -0
  299. package/dist/esm/resources/index.d.ts +8 -1
  300. package/dist/esm/resources/tokens.d.ts +49 -3
  301. package/dist/esm/resources/tokens.js +50 -4
  302. package/dist/esm/resources/tokens.js.map +1 -1
  303. package/dist/exo.esm.js +6635 -2550
  304. package/dist/exo.esm.js.map +1 -1
  305. package/package.json +16 -4
  306. package/dist/esm/input/interaction-hooks.d.ts +0 -34
  307. package/dist/esm/input/interaction-hooks.js +0 -35
  308. package/dist/esm/input/interaction-hooks.js.map +0 -1
  309. package/dist/esm/rendering/text/DynamicGlyphAtlas.d.ts +0 -33
  310. package/dist/esm/rendering/text/DynamicGlyphAtlas.js +0 -134
  311. package/dist/esm/rendering/text/DynamicGlyphAtlas.js.map +0 -1
  312. package/dist/esm/rendering/text/atlas-singleton.d.ts +0 -7
  313. package/dist/esm/rendering/text/atlas-singleton.js +0 -17
  314. package/dist/esm/rendering/text/atlas-singleton.js.map +0 -1
  315. package/dist/esm/resources/factories/VttFactory.d.ts +0 -24
  316. package/dist/esm/resources/factories/VttFactory.js +0 -158
  317. package/dist/esm/resources/factories/VttFactory.js.map +0 -1
@@ -1,658 +1,9 @@
1
- import { isAudioContextReady, getAudioContext, onAudioContextReady } from './audio-context.js';
2
- import { registerWorkletProcessor } from './worklet/registerWorklet.js';
1
+ import { onAudioContextReady, isAudioContextReady, getAudioContext } from './audio-context.js';
2
+ import { registerAudioWorkletProcessor } from './worklet/registerWorklet.js';
3
+ import { beatDetectorWorkletSource } from './worklets/beat-detector.worklet.js';
3
4
  import { Signal } from '../core/Signal.js';
4
5
 
5
- // ---------------------------------------------------------------------------
6
- // Worklet source (self-contained JavaScript, no imports)
7
- // ---------------------------------------------------------------------------
8
6
  const workletName = 'exojs-beat-detector';
9
- const beatDetectorWorkletSource = `
10
- // ---- Hann window + FFT (radix-2 Cooley-Tukey) ----
11
- function applyHannWindow(real, imag) {
12
- var n = real.length;
13
- for (var i = 0; i < n; i++) {
14
- var w = 0.5 * (1 - Math.cos(2 * Math.PI * i / (n - 1)));
15
- real[i] *= w;
16
- imag[i] = 0;
17
- }
18
- }
19
-
20
- function bitReversePermute(real, imag) {
21
- var n = real.length;
22
- var j = 0;
23
- for (var i = 1; i < n; i++) {
24
- var bit = n >> 1;
25
- for (; j & bit; bit >>= 1) { j ^= bit; }
26
- j ^= bit;
27
- if (i < j) {
28
- var t = real[i]; real[i] = real[j]; real[j] = t;
29
- t = imag[i]; imag[i] = imag[j]; imag[j] = t;
30
- }
31
- }
32
- }
33
-
34
- function fftInPlace(real, imag) {
35
- applyHannWindow(real, imag);
36
- bitReversePermute(real, imag);
37
- var n = real.length;
38
- for (var len = 2; len <= n; len <<= 1) {
39
- var halfLen = len >> 1;
40
- var step = -2 * Math.PI / len;
41
- for (var i = 0; i < n; i += len) {
42
- for (var k = 0; k < halfLen; k++) {
43
- var angle = step * k;
44
- var cos = Math.cos(angle);
45
- var sin = Math.sin(angle);
46
- var re = real[i+k+halfLen]*cos - imag[i+k+halfLen]*sin;
47
- var im = real[i+k+halfLen]*sin + imag[i+k+halfLen]*cos;
48
- real[i+k+halfLen] = real[i+k] - re;
49
- imag[i+k+halfLen] = imag[i+k] - im;
50
- real[i+k] += re;
51
- imag[i+k] += im;
52
- }
53
- }
54
- }
55
- }
56
-
57
- // ---- Mel filterbank ----
58
- function buildMelFilterbank(numBands, fMin, fMax, fftSize, sampleRate) {
59
- var numBins = fftSize >> 1;
60
- var nyquist = sampleRate / 2;
61
- var melMin = 2595 * Math.log10(1 + fMin / 700);
62
- var melMax = 2595 * Math.log10(1 + fMax / 700);
63
- var melPoints = new Float32Array(numBands + 2);
64
- for (var i = 0; i < numBands + 2; i++) {
65
- melPoints[i] = melMin + (melMax - melMin) * i / (numBands + 1);
66
- }
67
- var binPoints = new Float32Array(numBands + 2);
68
- for (var i = 0; i < numBands + 2; i++) {
69
- var hz = 700 * (Math.pow(10, melPoints[i] / 2595) - 1);
70
- binPoints[i] = Math.round(hz / nyquist * (numBins - 1));
71
- }
72
- var bands = [];
73
- for (var b = 0; b < numBands; b++) {
74
- var startBin = Math.max(0, Math.min(numBins - 1, binPoints[b]));
75
- var peakBin = Math.max(0, Math.min(numBins - 1, binPoints[b+1]));
76
- var endBin = Math.max(0, Math.min(numBins - 1, binPoints[b+2]));
77
- var len = endBin - startBin + 1;
78
- var weights = new Float32Array(len);
79
- for (var i = 0; i < len; i++) {
80
- var bin = startBin + i;
81
- if (bin <= peakBin && peakBin > startBin) {
82
- weights[i] = (bin - startBin) / (peakBin - startBin);
83
- } else if (bin > peakBin && endBin > peakBin) {
84
- weights[i] = (endBin - bin) / (endBin - peakBin);
85
- } else {
86
- weights[i] = 1;
87
- }
88
- }
89
- bands.push({ startBin: startBin, peakBin: peakBin, endBin: endBin, weights: weights });
90
- }
91
- return bands;
92
- }
93
-
94
- function computeMelBands(mag, bands, out) {
95
- for (var b = 0; b < bands.length; b++) {
96
- var band = bands[b];
97
- var energy = 0;
98
- for (var i = 0; i < band.weights.length; i++) {
99
- energy += mag[band.startBin + i] * band.weights[i];
100
- }
101
- out[b] = Math.log(1 + energy);
102
- }
103
- }
104
-
105
- // ---- Processor ----
106
- class BeatDetectorProcessor extends AudioWorkletProcessor {
107
- constructor(options) {
108
- super();
109
- var opts = (options && options.processorOptions) || {};
110
- this._sampleRate = sampleRate;
111
- this._fftSize = opts.fftSize || 2048;
112
- this._hopSize = opts.hopSize || 512;
113
- this._minBpm = opts.minBpm || 50;
114
- this._maxBpm = opts.maxBpm || 250;
115
- this._melBands = opts.melBands || 24;
116
- this._settlingMs = opts.settlingMs !== undefined ? opts.settlingMs : 1500;
117
- this._tempoWindowSec = opts.tempoWindowSec || 6;
118
- this._enableTsDetection = opts.enableTimeSignatureDetection !== false;
119
-
120
- var numBins = this._fftSize >> 1;
121
- this._real = new Float32Array(this._fftSize);
122
- this._imag = new Float32Array(this._fftSize);
123
- this._mag = new Float32Array(numBins);
124
- this._ringBuffer = new Float32Array(this._fftSize);
125
- this._ringWritePos = 0;
126
- this._sampleCount = 0;
127
- this._hopAccum = 0;
128
-
129
- this._melBandFilters = buildMelFilterbank(this._melBands, 80, 8000, this._fftSize, this._sampleRate);
130
- this._melOut = new Float32Array(this._melBands);
131
- var fluxWindowLen = Math.ceil(this._tempoWindowSec * this._sampleRate / this._hopSize);
132
- this._fluxWindow = new Float32Array(fluxWindowLen);
133
- this._fluxWritePos = 0;
134
- this._fluxCount = 0;
135
- var LAG_K = 3;
136
- this._prevMelFrames = [];
137
- for (var i = 0; i < LAG_K; i++) {
138
- this._prevMelFrames.push(new Float32Array(this._melBands));
139
- }
140
- this._prevMelFrameIdx = 0;
141
-
142
- // Lag range in hops for BPM range
143
- this._minLag = Math.max(1, Math.round(60 / this._maxBpm * this._sampleRate / this._hopSize));
144
- this._maxLag = Math.round(60 / this._minBpm * this._sampleRate / this._hopSize);
145
-
146
- this._hopsSinceACF = 0;
147
- var ACF_INTERVAL = 15;
148
- this._acfInterval = ACF_INTERVAL;
149
-
150
- // Tempo state
151
- this._bestBpm = 0;
152
- this._bestScore = 0;
153
- this._candidates = [];
154
-
155
- // Phase state
156
- this._lastBeatSample = -1;
157
- this._ibiHistory = new Float32Array(4); // last 4 inter-beat intervals
158
- this._ibiIdx = 0;
159
-
160
- // Bar position state — parallel posteriors for 4/4 and 3/4
161
- this._posterior4 = new Float32Array([0.25, 0.25, 0.25, 0.25]);
162
- this._posterior3 = new Float32Array([1/3, 1/3, 1/3]);
163
- this._ts4Confidence = 0.5;
164
- this._ts3Confidence = 0.5;
165
- this._activeTs = '4/4';
166
- this._sustainCounter = 0;
167
- this._barPosition = 1; // 1-indexed
168
- this._barNumber = 0;
169
- this._beatsSinceStart = 0;
170
-
171
- // Confidence
172
- this._confidence = 0;
173
-
174
- // State snapshot cadence (~20 Hz)
175
- var STATE_INTERVAL_HOPS = Math.round(this._sampleRate / this._hopSize / 20);
176
- this._stateInterval = Math.max(1, STATE_INTERVAL_HOPS);
177
- this._hopsSinceState = 0;
178
-
179
- // Settling
180
- this._startSample = currentFrame;
181
- this._settledSamples = Math.round(this._settlingMs * 0.001 * this._sampleRate);
182
-
183
- // Lookahead
184
- this._lookahead = [];
185
-
186
- // RMS / onset state
187
- this._rms = 0;
188
- this._onsetStrength = 0;
189
-
190
- // Band energy (for state messages)
191
- var LOW_BANDS = Math.round(this._melBands * 0.25);
192
- var MID_BANDS = Math.round(this._melBands * 0.6);
193
- this._lowBandEnd = LOW_BANDS;
194
- this._midBandEnd = MID_BANDS;
195
- }
196
-
197
- process(inputs, _outputs, _parameters) {
198
- var input = inputs[0];
199
- if (!input || input.length === 0) return true;
200
-
201
- var left = input[0] || [];
202
- var right = input[1] || left;
203
- var blockLen = left.length;
204
-
205
- for (var s = 0; s < blockLen; s++) {
206
- // Mono downmix
207
- var mono = (left[s] + right[s]) * 0.5;
208
-
209
- // Accumulate RMS
210
- this._rms += mono * mono;
211
-
212
- // Fill ring buffer
213
- this._ringBuffer[this._ringWritePos] = mono;
214
- this._ringWritePos = (this._ringWritePos + 1) & (this._fftSize - 1);
215
-
216
- this._hopAccum++;
217
- this._sampleCount++;
218
-
219
- if (this._hopAccum >= this._hopSize) {
220
- this._hopAccum = 0;
221
- this._processHop();
222
- }
223
- }
224
-
225
- return true;
226
- }
227
-
228
- _processHop() {
229
- // Read ring buffer into real[] (oldest first)
230
- var rb = this._ringBuffer;
231
- var wp = this._ringWritePos;
232
- var n = this._fftSize;
233
- for (var i = 0; i < n; i++) {
234
- this._real[i] = rb[(wp + i) & (n - 1)];
235
- }
236
-
237
- // FFT
238
- fftInPlace(this._real, this._imag);
239
-
240
- // Magnitude spectrum
241
- var bins = n >> 1;
242
- for (var i = 0; i < bins; i++) {
243
- this._mag[i] = Math.sqrt(this._real[i]*this._real[i] + this._imag[i]*this._imag[i]);
244
- }
245
-
246
- // RMS (from time domain, using ring buffer)
247
- var rmsAccum = 0;
248
- for (var i = 0; i < n; i++) {
249
- rmsAccum += rb[(wp + i) & (n - 1)] * rb[(wp + i) & (n - 1)];
250
- }
251
- this._rms = Math.sqrt(rmsAccum / n);
252
-
253
- // Mel bands
254
- computeMelBands(this._mag, this._melBandFilters, this._melOut);
255
-
256
- // Spectral flux (SuperFlux-lite, lag k=3)
257
- var K = this._prevMelFrames.length;
258
- var flux = 0;
259
- for (var b = 0; b < this._melBands; b++) {
260
- var localMax = -Infinity;
261
- for (var k = 0; k < K; k++) {
262
- var prevVal = this._prevMelFrames[k][b];
263
- if (prevVal > localMax) localMax = prevVal;
264
- }
265
- var diff = this._melOut[b] - localMax;
266
- if (diff > 0) flux += diff;
267
- }
268
- this._onsetStrength = flux;
269
-
270
- // Store current mel frame in circular buffer
271
- var prevFrame = this._prevMelFrames[this._prevMelFrameIdx];
272
- for (var b = 0; b < this._melBands; b++) {
273
- prevFrame[b] = this._melOut[b];
274
- }
275
- this._prevMelFrameIdx = (this._prevMelFrameIdx + 1) % K;
276
-
277
- // Add flux to sliding window
278
- this._fluxWindow[this._fluxWritePos] = flux;
279
- this._fluxWritePos = (this._fluxWritePos + 1) % this._fluxWindow.length;
280
- if (this._fluxCount < this._fluxWindow.length) this._fluxCount++;
281
-
282
- // Tempogram: compute ACF periodically
283
- this._hopsSinceACF++;
284
- if (this._hopsSinceACF >= this._acfInterval && this._fluxCount >= this._maxLag + 1) {
285
- this._hopsSinceACF = 0;
286
- this._computeACFAndCandidates();
287
- }
288
-
289
- // Phase tracker
290
- var settled = (this._sampleCount - this._settledSamples) > 0;
291
- if (this._bestBpm > 0 && settled) {
292
- this._tickPhase(flux);
293
- }
294
-
295
- // State snapshot
296
- this._hopsSinceState++;
297
- if (this._hopsSinceState >= this._stateInterval) {
298
- this._hopsSinceState = 0;
299
- this._sendStateMessage();
300
- }
301
- }
302
-
303
- _computeACFAndCandidates() {
304
- var n = this._fluxCount;
305
- var buf = this._fluxWindow;
306
- var wp = this._fluxWritePos;
307
- var len = buf.length;
308
-
309
- // Compute ACF for lags in [minLag, maxLag]
310
- var numLags = this._maxLag - this._minLag + 1;
311
- var acf = new Float32Array(numLags);
312
- for (var lagIdx = 0; lagIdx < numLags; lagIdx++) {
313
- var lag = this._minLag + lagIdx;
314
- var sum = 0, count = 0;
315
- for (var t = lag; t < n; t++) {
316
- var idxT = (wp - 1 - (n - 1 - t) + len) % len;
317
- var idxTL = (wp - 1 - (n - 1 - (t - lag)) + len) % len;
318
- sum += buf[idxT] * buf[idxTL];
319
- count++;
320
- }
321
- acf[lagIdx] = count > 0 ? sum / count : 0;
322
- }
323
-
324
- // Normalise
325
- var norm = 0;
326
- for (var i = 0; i < numLags; i++) { if (acf[i] > norm) norm = acf[i]; }
327
- if (norm > 0) {
328
- for (var i = 0; i < numLags; i++) acf[i] /= norm;
329
- }
330
-
331
- // Find top peaks
332
- var peaks = [];
333
- for (var i = 1; i < numLags - 1; i++) {
334
- if (acf[i] > acf[i-1] && acf[i] > acf[i+1] && acf[i] > 0) {
335
- var lag2 = this._minLag + i;
336
- var bpm = 60 * this._sampleRate / (lag2 * this._hopSize);
337
- if (bpm >= this._minBpm && bpm <= this._maxBpm) {
338
- peaks.push({ bpm: bpm, score: acf[i], lag: lag2 });
339
- }
340
- }
341
- }
342
- peaks.sort(function(a, b) { return b.score - a.score; });
343
- this._candidates = peaks.slice(0, 3);
344
-
345
- if (this._candidates.length === 0) return;
346
-
347
- var top = this._candidates[0];
348
-
349
- // Hysteresis
350
- if (this._bestBpm <= 0) {
351
- // First lock
352
- this._bestBpm = top.bpm;
353
- this._bestScore = top.score;
354
- } else {
355
- var isOctave = (Math.abs(top.bpm / this._bestBpm - 2) < 0.1) ||
356
- (Math.abs(top.bpm / this._bestBpm - 0.5) < 0.05);
357
- var margin = isOctave ? 1.5 : 1.15;
358
- if (top.score > this._bestScore * margin) {
359
- var oldBpm = this._bestBpm;
360
- this._bestBpm = top.bpm;
361
- this._bestScore = top.score;
362
- // Only fire tempoChange if > 5% different
363
- if (Math.abs(this._bestBpm - oldBpm) / oldBpm > 0.05) {
364
- this.port.postMessage({ type: 'tempoChange', newTempo: this._bestBpm, oldTempo: oldBpm });
365
- }
366
- } else {
367
- this._bestScore = this._bestScore * 0.99 + top.score * 0.01; // slowly decay
368
- }
369
- }
370
-
371
- this._updateConfidence();
372
- }
373
-
374
- _tickPhase(flux) {
375
- if (this._lastBeatSample < 0) {
376
- // Bootstrap: set first beat at current sample
377
- this._lastBeatSample = this._sampleCount;
378
- return;
379
- }
380
-
381
- var beatIntervalSamples = 60 / this._bestBpm * this._sampleRate;
382
- var phase = (this._sampleCount - this._lastBeatSample) / beatIntervalSamples;
383
-
384
- // Snap correction: if novelty peak is strong and phase is 0.7..1.3, snap
385
- if (phase >= 0.7 && phase <= 1.3 && flux > 0) {
386
- // Compute mean recent flux
387
- var recentMean = 0;
388
- var count = Math.min(this._fluxCount, 16);
389
- var wp = this._fluxWritePos;
390
- var len = this._fluxWindow.length;
391
- for (var i = 0; i < count; i++) {
392
- recentMean += this._fluxWindow[(wp - 1 - i + len) % len];
393
- }
394
- recentMean /= count || 1;
395
- if (flux > 1.5 * recentMean && recentMean > 0) {
396
- // Snap to this sample
397
- this._lastBeatSample = this._sampleCount;
398
- phase = 1.0;
399
- }
400
- }
401
-
402
- if (phase >= 1.0) {
403
- var beatTime = (this._lastBeatSample + beatIntervalSamples) / this._sampleRate;
404
- this._lastBeatSample += beatIntervalSamples;
405
-
406
- // Update IBI history
407
- this._ibiHistory[this._ibiIdx] = beatIntervalSamples;
408
- this._ibiIdx = (this._ibiIdx + 1) & 3;
409
-
410
- this._beatsSinceStart++;
411
-
412
- // Update bar position
413
- this._updateBarPosition(flux);
414
-
415
- // Compute confidence
416
- this._updateConfidence();
417
-
418
- // Lookahead
419
- this._updateLookahead(beatTime);
420
-
421
- var isDownbeat = this._barPosition === 1;
422
- var beatInfo = {
423
- type: 'beat',
424
- audioTime: beatTime,
425
- tempo: this._bestBpm,
426
- confidence: this._confidence,
427
- beatPhase: 0,
428
- energy: flux,
429
- isDownbeat: isDownbeat,
430
- beatInBar: this._barPosition,
431
- };
432
- this.port.postMessage(beatInfo);
433
-
434
- if (isDownbeat) {
435
- this.port.postMessage({
436
- type: 'barStart',
437
- audioTime: beatTime,
438
- tempo: this._bestBpm,
439
- confidence: this._confidence,
440
- barNumber: this._barNumber,
441
- });
442
- }
443
- }
444
- }
445
-
446
- _computeBeatLikelihood(flux) {
447
- var count = Math.min(this._fluxCount, 32);
448
- var wp = this._fluxWritePos;
449
- var len = this._fluxWindow.length;
450
- var totalFlux = 0;
451
- for (var i = 0; i < count; i++) {
452
- totalFlux += this._fluxWindow[(wp - 1 - i + len) % len];
453
- }
454
- var mean = count > 0 ? totalFlux / count : 1;
455
- return Math.max(0.5, Math.min(1.5, mean > 0 ? flux / mean : 1));
456
- }
457
-
458
- _updateBarPosition(flux) {
459
- var likelihood = this._computeBeatLikelihood(flux);
460
-
461
- // --- 4/4 posterior ---
462
- var p4 = this._posterior4;
463
- var s4 = new Float32Array(4);
464
- s4[0] = p4[3]; s4[1] = p4[0]; s4[2] = p4[1]; s4[3] = p4[2];
465
- var sum4 = 0;
466
- for (var i = 0; i < 4; i++) {
467
- p4[i] = s4[i] * (likelihood + (i === 0 ? 0.3 : 0));
468
- sum4 += p4[i];
469
- }
470
- if (sum4 > 0) { for (var i = 0; i < 4; i++) p4[i] /= sum4; }
471
-
472
- // --- 3/4 posterior ---
473
- var p3 = this._posterior3;
474
- var s3 = new Float32Array(3);
475
- s3[0] = p3[2]; s3[1] = p3[0]; s3[2] = p3[1];
476
- var sum3 = 0;
477
- for (var i = 0; i < 3; i++) {
478
- p3[i] = s3[i] * (likelihood + (i === 0 ? 0.3 : 0));
479
- sum3 += p3[i];
480
- }
481
- if (sum3 > 0) { for (var i = 0; i < 3; i++) p3[i] /= sum3; }
482
-
483
- // --- Update TS confidences (EMA) ---
484
- var max4 = 0;
485
- for (var i = 0; i < 4; i++) { if (p4[i] > max4) max4 = p4[i]; }
486
- var max3 = 0;
487
- for (var i = 0; i < 3; i++) { if (p3[i] > max3) max3 = p3[i]; }
488
- var alpha = 0.1;
489
- this._ts4Confidence = (1 - alpha) * this._ts4Confidence + alpha * max4;
490
- this._ts3Confidence = (1 - alpha) * this._ts3Confidence + alpha * max3;
491
-
492
- // --- Hysteresis switching ---
493
- if (this._enableTsDetection && this._beatsSinceStart > 8) {
494
- var minSwitchMargin = 1.4;
495
- var minSustainBeats = 12; // ~4 bars * 3 beats
496
- var threeFavored = this._ts3Confidence > this._ts4Confidence * minSwitchMargin;
497
- var fourFavored = this._ts4Confidence > this._ts3Confidence * minSwitchMargin;
498
-
499
- if (this._activeTs === '4/4' && threeFavored) {
500
- this._sustainCounter++;
501
- if (this._sustainCounter >= minSustainBeats) {
502
- this._activeTs = '3/4';
503
- this._sustainCounter = 0;
504
- }
505
- } else if (this._activeTs === '3/4' && fourFavored) {
506
- this._sustainCounter++;
507
- if (this._sustainCounter >= minSustainBeats + 4) { // 16 beats for 4/4
508
- this._activeTs = '4/4';
509
- this._sustainCounter = 0;
510
- }
511
- } else {
512
- this._sustainCounter = 0;
513
- }
514
- }
515
-
516
- // --- Determine bar position from active TS ---
517
- var barLen = this._activeTs === '3/4' ? 3 : 4;
518
- var posterior = this._activeTs === '3/4' ? p3 : p4;
519
-
520
- if (this._beatsSinceStart >= barLen) {
521
- var maxP = -1, maxI = 0;
522
- for (var i = 0; i < barLen; i++) {
523
- if (posterior[i] > maxP) { maxP = posterior[i]; maxI = i; }
524
- }
525
- var newPos = maxI + 1; // 1-indexed
526
- if (newPos === 1 && this._barPosition !== 1) {
527
- this._barNumber++;
528
- }
529
- this._barPosition = newPos;
530
- } else {
531
- // Just advance sequentially
532
- this._barPosition = ((this._barPosition) % barLen) + 1;
533
- if (this._barPosition === 1) this._barNumber++;
534
- }
535
- }
536
-
537
- _updateConfidence() {
538
- if (this._candidates.length === 0) {
539
- this._confidence = 0;
540
- return;
541
- }
542
-
543
- // Peak contrast
544
- var top1 = this._candidates[0] ? this._candidates[0].score : 0;
545
- var top2 = this._candidates[1] ? this._candidates[1].score : top1;
546
- var top3 = this._candidates[2] ? this._candidates[2].score : top2;
547
- var peakContrast = (top2 + top3) > 0 ? top1 / ((top2 + top3) / 2) : 1;
548
-
549
- // Phase consistency from IBI variance
550
- var ibiMean = 0;
551
- for (var i = 0; i < 4; i++) ibiMean += this._ibiHistory[i];
552
- ibiMean /= 4;
553
- var ibiVar = 0;
554
- for (var i = 0; i < 4; i++) {
555
- var d = this._ibiHistory[i] - ibiMean;
556
- ibiVar += d * d;
557
- }
558
- ibiVar /= 4;
559
- var phaseConsistency = ibiMean > 0 ? Math.max(0, 1 - ibiVar / (ibiMean * ibiMean)) : 0;
560
-
561
- // Bar consistency (use the active posterior)
562
- var activePosterior = this._activeTs === '3/4' ? this._posterior3 : this._posterior4;
563
- var activeLen = this._activeTs === '3/4' ? 3 : 4;
564
- var maxP = 0;
565
- for (var i = 0; i < activeLen; i++) {
566
- if (activePosterior[i] > maxP) maxP = activePosterior[i];
567
- }
568
- var barConsistency = maxP;
569
-
570
- var c = Math.sqrt(Math.max(0, peakContrast / 2)) *
571
- Math.sqrt(Math.max(0, phaseConsistency)) *
572
- (0.5 + 0.5 * barConsistency);
573
- this._confidence = Math.max(0, Math.min(1, c));
574
- }
575
-
576
- _updateLookahead(lastBeatTime) {
577
- var lookahead = [];
578
- var beatInterval = 60 / this._bestBpm;
579
- var barPos = this._barPosition;
580
- var barLen = this._activeTs === '3/4' ? 3 : 4;
581
- for (var i = 0; i < 8; i++) {
582
- var t = lastBeatTime + (i + 1) * beatInterval;
583
- var bp = ((barPos - 1 + i) % barLen) + 1;
584
- lookahead.push({
585
- audioTime: t,
586
- tempo: this._bestBpm,
587
- isDownbeat: bp === 1,
588
- beatInBar: bp,
589
- });
590
- }
591
- this._lookahead = lookahead;
592
- }
593
-
594
- _computeBandEnergy() {
595
- var low = 0, mid = 0, high = 0;
596
- for (var b = 0; b < this._lowBandEnd; b++) low += this._melOut[b];
597
- for (var b = this._lowBandEnd; b < this._midBandEnd; b++) mid += this._melOut[b];
598
- for (var b = this._midBandEnd; b < this._melBands; b++) high += this._melOut[b];
599
- var denom = this._lowBandEnd || 1;
600
- return {
601
- low: low / denom,
602
- mid: mid / Math.max(1, this._midBandEnd - this._lowBandEnd),
603
- high: high / Math.max(1, this._melBands - this._midBandEnd),
604
- };
605
- }
606
-
607
- _sendStateMessage() {
608
- var settled = (this._sampleCount - this._settledSamples) > 0;
609
- var beatInterval = this._bestBpm > 0 ? 60 / this._bestBpm : 0;
610
- var currentTime = this._sampleCount / this._sampleRate;
611
- var lastBeatTime = this._lastBeatSample >= 0 ? this._lastBeatSample / this._sampleRate : 0;
612
- var beatPhase = beatInterval > 0
613
- ? Math.min(1, (currentTime - lastBeatTime) / beatInterval)
614
- : 0;
615
- var nextBeatTime = lastBeatTime + beatInterval;
616
-
617
- var nextDownbeatTime = nextBeatTime;
618
- for (var i = 0; i < this._lookahead.length; i++) {
619
- if (this._lookahead[i].isDownbeat) {
620
- nextDownbeatTime = this._lookahead[i].audioTime;
621
- break;
622
- }
623
- }
624
-
625
- var be = this._computeBandEnergy();
626
-
627
- this.port.postMessage({
628
- type: 'state',
629
- tempo: settled ? this._bestBpm : 0,
630
- beatPhase: beatPhase,
631
- confidence: settled ? this._confidence : 0,
632
- gridStability: settled ? this._confidence : 0,
633
- tempoCandidates: settled ? this._candidates.map(function(c) {
634
- return { bpm: c.bpm, score: c.score };
635
- }) : [],
636
- rms: this._rms,
637
- onsetStrength: this._onsetStrength,
638
- bandEnergy: be,
639
- barPosition: this._barPosition,
640
- barLength: this._activeTs === '3/4' ? 3 : 4,
641
- timeSignature: this._activeTs === '3/4'
642
- ? { numerator: 3, denominator: 4 }
643
- : { numerator: 4, denominator: 4 },
644
- lookahead: this._lookahead,
645
- nextBeatTime: settled ? nextBeatTime : 0,
646
- nextDownbeatTime: settled ? nextDownbeatTime : 0,
647
- });
648
- }
649
- }
650
-
651
- registerProcessor('${workletName}', BeatDetectorProcessor);
652
- `;
653
- // ---------------------------------------------------------------------------
654
- // Main-thread BeatDetector class
655
- // ---------------------------------------------------------------------------
656
7
  /**
657
8
  * Real-time tempo + beat tracker. Splits work between the audio-rendering
658
9
  * thread (an AudioWorklet that runs onset detection, tempogram analysis,
@@ -690,6 +41,11 @@ class BeatDetector {
690
41
  _tapSource = null;
691
42
  _streamSource = null;
692
43
  _ready = null;
44
+ _pendingSourceSetup = null;
45
+ _onAudioContextReady = (ctx) => {
46
+ onAudioContextReady.remove(this._onAudioContextReady);
47
+ this._setup(ctx);
48
+ };
693
49
  // ---- Cached state from worklet ----
694
50
  _tempo = 0;
695
51
  _beatPhase = 0;
@@ -738,7 +94,7 @@ class BeatDetector {
738
94
  this._setup(getAudioContext());
739
95
  }
740
96
  else {
741
- onAudioContextReady.once(this._setup, this);
97
+ onAudioContextReady.add(this._onAudioContextReady);
742
98
  }
743
99
  if (options?.source !== undefined && options.source !== null) {
744
100
  this.source = options.source;
@@ -758,14 +114,20 @@ class BeatDetector {
758
114
  if (value === null)
759
115
  return;
760
116
  if (isAudioContextReady()) {
117
+ this._pendingSourceSetup = null;
761
118
  this._connectSource(value, getAudioContext());
762
119
  }
763
120
  else {
764
- onAudioContextReady.once((ctx) => {
765
- if (this._source === value) {
766
- this._connectSource(value, ctx);
767
- }
768
- }, this);
121
+ if (this._pendingSourceSetup !== null) {
122
+ onAudioContextReady.remove(this._pendingSourceSetup);
123
+ }
124
+ const handler = (ctx) => {
125
+ onAudioContextReady.remove(handler);
126
+ this._pendingSourceSetup = null;
127
+ this._connectSource(value, ctx);
128
+ };
129
+ this._pendingSourceSetup = handler;
130
+ onAudioContextReady.add(handler);
769
131
  }
770
132
  }
771
133
  // -----------------------------------------------------------------------
@@ -882,7 +244,11 @@ class BeatDetector {
882
244
  // Lifecycle
883
245
  // -----------------------------------------------------------------------
884
246
  destroy() {
885
- onAudioContextReady.clearByContext(this);
247
+ onAudioContextReady.remove(this._onAudioContextReady);
248
+ if (this._pendingSourceSetup !== null) {
249
+ onAudioContextReady.remove(this._pendingSourceSetup);
250
+ this._pendingSourceSetup = null;
251
+ }
886
252
  this._disconnectTap();
887
253
  this._workletNode?.disconnect();
888
254
  this._workletNode = null;
@@ -898,7 +264,7 @@ class BeatDetector {
898
264
  // -----------------------------------------------------------------------
899
265
  _setup(audioContext) {
900
266
  const opts = this._options;
901
- this._ready = registerWorkletProcessor(audioContext, workletName, beatDetectorWorkletSource).then(() => {
267
+ this._ready = registerAudioWorkletProcessor(audioContext, workletName, beatDetectorWorkletSource).then(() => {
902
268
  const node = new AudioWorkletNode(audioContext, workletName, {
903
269
  numberOfInputs: 1,
904
270
  numberOfOutputs: 0,
@@ -1035,7 +401,7 @@ class BeatDetector {
1035
401
  if (this._source === source && this._workletNode && isAudioContextReady()) {
1036
402
  this._connectSource(source, getAudioContext());
1037
403
  }
1038
- }, this);
404
+ });
1039
405
  }
1040
406
  _disconnectTap() {
1041
407
  if (this._tapSource && this._workletNode) {