@woosh/meep-engine 3.46.0 → 3.47.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 (230) hide show
  1. package/editor/actions/concrete/TransformModifyAction.d.ts.map +1 -1
  2. package/editor/actions/concrete/TransformModifyAction.js +10 -2
  3. package/editor/ecs/component/FieldDescriptor.js +15 -0
  4. package/editor/ecs/component/createObjectEditor.js +41 -12
  5. package/editor/ecs/component/editors/ecs/Transform64Editor.d.ts.map +1 -1
  6. package/editor/ecs/component/editors/ecs/Transform64Editor.js +77 -70
  7. package/editor/tools/TransformToolV2.d.ts.map +1 -1
  8. package/editor/tools/TransformToolV2.js +4 -0
  9. package/editor/tools/v2/TransformControls.d.ts.map +1 -1
  10. package/editor/tools/v2/TransformControls.js +7 -0
  11. package/editor/view/ecs/EntityEditor.d.ts.map +1 -1
  12. package/editor/view/ecs/EntityEditor.js +4 -2
  13. package/package.json +1 -1
  14. package/src/core/collection/array/array_insert_at.d.ts +16 -0
  15. package/src/core/collection/array/array_insert_at.d.ts.map +1 -0
  16. package/src/core/collection/array/array_insert_at.js +32 -0
  17. package/src/core/collection/array/array_remove_at.d.ts +15 -0
  18. package/src/core/collection/array/array_remove_at.d.ts.map +1 -0
  19. package/src/core/collection/array/array_remove_at.js +30 -0
  20. package/src/core/geom/3d/quaternion/quat3_look_rotation.d.ts +37 -0
  21. package/src/core/geom/3d/quaternion/quat3_look_rotation.d.ts.map +1 -0
  22. package/src/core/geom/3d/quaternion/quat3_look_rotation.js +172 -0
  23. package/src/core/geom/Quaternion.d.ts +1 -0
  24. package/src/core/geom/Quaternion.d.ts.map +1 -1
  25. package/src/core/geom/Quaternion.js +12 -48
  26. package/src/engine/Engine.d.ts.map +1 -1
  27. package/src/engine/Engine.js +9 -4
  28. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  29. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +10 -0
  30. package/src/engine/ecs/transform/t64_look_rotation.d.ts +4 -4
  31. package/src/engine/ecs/transform/t64_look_rotation.d.ts.map +1 -1
  32. package/src/engine/ecs/transform/t64_look_rotation.js +7 -18
  33. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
  34. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +30 -1
  35. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +6 -0
  36. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
  37. package/src/engine/graphics3/GPUParticleEmitterSystem.js +72 -29
  38. package/src/engine/input/devices/KeyboardDevice.d.ts +1 -0
  39. package/src/engine/input/devices/KeyboardDevice.d.ts.map +1 -1
  40. package/src/engine/input/devices/KeyboardDevice.js +7 -6
  41. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.d.ts.map +1 -1
  42. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.js +15 -2
  43. package/src/engine/network/CONGESTION_CONTROL.md +9 -9
  44. package/src/engine/network/MESSAGE_TRANSPORT.md +364 -0
  45. package/src/engine/network/NetworkSession.d.ts +31 -11
  46. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  47. package/src/engine/network/NetworkSession.js +453 -286
  48. package/src/engine/network/README.md +77 -27
  49. package/src/engine/network/SMOOTHNESS_DESIGN.md +13 -5
  50. package/src/engine/network/diagnostics/BandwidthMeter.d.ts +1 -1
  51. package/src/engine/network/diagnostics/BandwidthMeter.js +1 -1
  52. package/src/engine/network/orchestrator/HostJoin.d.ts +111 -0
  53. package/src/engine/network/orchestrator/HostJoin.d.ts.map +1 -0
  54. package/src/engine/network/orchestrator/HostJoin.js +271 -0
  55. package/src/engine/network/orchestrator/NetworkPeer.d.ts +151 -112
  56. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  57. package/src/engine/network/orchestrator/NetworkPeer.js +450 -499
  58. package/src/engine/network/orchestrator/PendingSlices.d.ts +30 -0
  59. package/src/engine/network/orchestrator/PendingSlices.d.ts.map +1 -0
  60. package/src/engine/network/orchestrator/PendingSlices.js +83 -0
  61. package/src/engine/network/orchestrator/ReliableFrames.d.ts +34 -0
  62. package/src/engine/network/orchestrator/ReliableFrames.d.ts.map +1 -0
  63. package/src/engine/network/orchestrator/ReliableFrames.js +96 -0
  64. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +77 -10
  65. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  66. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +236 -61
  67. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +20 -6
  68. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
  69. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +33 -8
  70. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  71. package/src/engine/network/replication/Replicator.js +7 -1
  72. package/src/engine/network/sim/ActionLog.d.ts +32 -5
  73. package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
  74. package/src/engine/network/sim/ActionLog.js +47 -5
  75. package/src/engine/network/sim/SimActionExecutor.d.ts +42 -2
  76. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  77. package/src/engine/network/sim/SimActionExecutor.js +100 -4
  78. package/src/engine/network/sim/Snapshotter.d.ts +12 -0
  79. package/src/engine/network/sim/Snapshotter.d.ts.map +1 -1
  80. package/src/engine/network/sim/Snapshotter.js +43 -0
  81. package/src/engine/network/state/Baseline.d.ts +1 -1
  82. package/src/engine/network/state/Baseline.js +1 -1
  83. package/src/engine/network/state/FrameBufferRing.d.ts +25 -6
  84. package/src/engine/network/state/FrameBufferRing.d.ts.map +1 -1
  85. package/src/engine/network/state/FrameBufferRing.js +51 -6
  86. package/src/engine/network/state/InputRing.d.ts +4 -0
  87. package/src/engine/network/state/InputRing.d.ts.map +1 -1
  88. package/src/engine/network/state/InputRing.js +7 -0
  89. package/src/engine/network/transport/DeferredTransport.d.ts.map +1 -1
  90. package/src/engine/network/transport/DeferredTransport.js +1 -0
  91. package/src/engine/network/transport/LoopbackTransport.d.ts.map +1 -1
  92. package/src/engine/network/transport/LoopbackTransport.js +4 -2
  93. package/src/engine/network/transport/Transport.d.ts +7 -0
  94. package/src/engine/network/transport/Transport.d.ts.map +1 -1
  95. package/src/engine/network/transport/Transport.js +8 -0
  96. package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts.map +1 -1
  97. package/src/engine/network/transport/adapters/NodeUDPTransport.js +20 -5
  98. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.d.ts.map +1 -1
  99. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +8 -0
  100. package/src/engine/network/transport/adapters/WebTransportTransport.d.ts.map +1 -1
  101. package/src/engine/network/transport/adapters/WebTransportTransport.js +6 -0
  102. package/src/engine/network/transport/connection/Connection.d.ts +191 -0
  103. package/src/engine/network/transport/connection/Connection.d.ts.map +1 -0
  104. package/src/engine/network/transport/connection/Connection.js +1262 -0
  105. package/src/engine/network/transport/connection/RangeSet.d.ts +94 -0
  106. package/src/engine/network/transport/connection/RangeSet.d.ts.map +1 -0
  107. package/src/engine/network/transport/connection/RangeSet.js +260 -0
  108. package/src/engine/network/transport/connection/RxStream.d.ts +48 -0
  109. package/src/engine/network/transport/connection/RxStream.d.ts.map +1 -0
  110. package/src/engine/network/transport/connection/RxStream.js +211 -0
  111. package/src/engine/network/transport/connection/TxStream.d.ts +90 -0
  112. package/src/engine/network/transport/connection/TxStream.d.ts.map +1 -0
  113. package/src/engine/network/transport/connection/TxStream.js +243 -0
  114. package/src/engine/network/transport/connection/bytes_copy.d.ts +17 -0
  115. package/src/engine/network/transport/connection/bytes_copy.d.ts.map +1 -0
  116. package/src/engine/network/transport/connection/bytes_copy.js +34 -0
  117. package/src/engine/network/transport/connection/varint.d.ts +46 -0
  118. package/src/engine/network/transport/connection/varint.d.ts.map +1 -0
  119. package/src/engine/network/transport/connection/varint.js +107 -0
  120. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  121. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts +38 -7
  122. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts.map +1 -1
  123. package/src/engine/sound/ecs/audio/AudioEmitter.js +35 -7
  124. package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.d.ts.map +1 -1
  125. package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.js +5 -1
  126. package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.d.ts +18 -0
  127. package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.d.ts.map +1 -0
  128. package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.js +48 -0
  129. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +12 -2
  130. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts.map +1 -1
  131. package/src/engine/sound/ecs/audio/AudioEmitterSystem.js +173 -9
  132. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts +9 -3
  133. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts.map +1 -1
  134. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +28 -8
  135. package/src/engine/sound/ecs/trigger/AudioEventTrigger.d.ts +8 -0
  136. package/src/engine/sound/ecs/trigger/AudioEventTrigger.d.ts.map +1 -1
  137. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationAdapter.d.ts.map +1 -1
  138. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationAdapter.js +5 -1
  139. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.d.ts +18 -0
  140. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.d.ts.map +1 -0
  141. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.js +51 -0
  142. package/src/engine/sound/ecs/trigger/AudioEventTriggerSystem.d.ts.map +1 -1
  143. package/src/engine/sound/ecs/trigger/AudioEventTriggerSystem.js +15 -1
  144. package/src/engine/sound/sopra/README.md +768 -644
  145. package/src/engine/sound/sopra/SopraEngine.d.ts +104 -8
  146. package/src/engine/sound/sopra/SopraEngine.d.ts.map +1 -1
  147. package/src/engine/sound/sopra/SopraEngine.js +327 -12
  148. package/src/engine/sound/sopra/definition/EventDescription.d.ts +28 -2
  149. package/src/engine/sound/sopra/definition/EventDescription.d.ts.map +1 -1
  150. package/src/engine/sound/sopra/definition/EventDescription.js +55 -5
  151. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts +10 -0
  152. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -1
  153. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +77 -55
  154. package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.d.ts +31 -0
  155. package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.d.ts.map +1 -0
  156. package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.js +75 -0
  157. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts +38 -9
  158. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts.map +1 -1
  159. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +34 -5
  160. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts +7 -2
  161. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts.map +1 -1
  162. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +137 -122
  163. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts +3 -1
  164. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts.map +1 -1
  165. package/src/engine/sound/sopra/definition/effect/EqEffect.js +146 -131
  166. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts +1 -0
  167. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts.map +1 -1
  168. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +6 -1
  169. package/src/engine/sound/sopra/runtime/BusGraph.d.ts +44 -2
  170. package/src/engine/sound/sopra/runtime/BusGraph.d.ts.map +1 -1
  171. package/src/engine/sound/sopra/runtime/BusGraph.js +83 -43
  172. package/src/engine/sound/sopra/runtime/EffectChain.d.ts +96 -0
  173. package/src/engine/sound/sopra/runtime/EffectChain.d.ts.map +1 -0
  174. package/src/engine/sound/sopra/runtime/EffectChain.js +286 -0
  175. package/src/engine/sound/sopra/runtime/EffectParamOverrides.d.ts +123 -0
  176. package/src/engine/sound/sopra/runtime/EffectParamOverrides.d.ts.map +1 -0
  177. package/src/engine/sound/sopra/runtime/EffectParamOverrides.js +320 -0
  178. package/src/engine/sound/sopra/runtime/EventInstance.d.ts +55 -5
  179. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
  180. package/src/engine/sound/sopra/runtime/EventInstance.js +201 -26
  181. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts +2 -1
  182. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts.map +1 -1
  183. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +6 -1
  184. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts +15 -0
  185. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts.map +1 -1
  186. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +32 -0
  187. package/src/engine/sound/sopra/util/MockAudioContext.d.ts.map +1 -1
  188. package/src/engine/sound/sopra/util/MockAudioContext.js +3 -0
  189. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts +13 -3
  190. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts.map +1 -1
  191. package/src/engine/sound/sopra/util/fadeOutAndStop.js +25 -13
  192. package/src/shade/RENDERER_CONTRACT.md +6 -3
  193. package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts +24 -0
  194. package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts.map +1 -0
  195. package/src/shade/device/SHADE_DEVICE_FLOOR.js +34 -0
  196. package/src/shade/device/ShadeDeviceFailure.d.ts +12 -5
  197. package/src/shade/device/ShadeDeviceFailure.d.ts.map +1 -1
  198. package/src/shade/device/ShadeDeviceFailure.js +17 -8
  199. package/src/shade/device/ShadeDeviceFailureReason.d.ts +1 -1
  200. package/src/shade/device/ShadeDeviceFailureReason.js +2 -2
  201. package/src/shade/device/shade_device_floor_unmet.d.ts +13 -0
  202. package/src/shade/device/shade_device_floor_unmet.d.ts.map +1 -0
  203. package/src/shade/device/shade_device_floor_unmet.js +32 -0
  204. package/src/shade/renderer/Renderer.d.ts +2 -0
  205. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  206. package/src/shade/renderer/Renderer.js +29 -55
  207. package/src/shade/renderer/buffer/GPUBufferAllocator.d.ts.map +1 -1
  208. package/src/shade/renderer/buffer/GPUBufferAllocator.js +9 -0
  209. package/src/shade/renderer/buffer/buffer_pool_ops.d.ts +21 -12
  210. package/src/shade/renderer/buffer/buffer_pool_ops.d.ts.map +1 -1
  211. package/src/shade/renderer/buffer/buffer_pool_ops.js +18 -19
  212. package/src/shade/renderer/gpu_primitive/test/gpu_test.d.ts.map +1 -1
  213. package/src/shade/renderer/gpu_primitive/test/gpu_test.js +8 -11
  214. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  215. package/src/shade/renderer/pool/TransientResourcePool.d.ts +24 -0
  216. package/src/shade/renderer/pool/TransientResourcePool.d.ts.map +1 -1
  217. package/src/shade/renderer/pool/TransientResourcePool.js +41 -10
  218. package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
  219. package/src/shade/renderer/shader/ComputeShader.js +18 -2
  220. package/src/shade/renderer/shader/ImageShader.d.ts.map +1 -1
  221. package/src/shade/renderer/shader/ImageShader.js +230 -218
  222. package/src/view/graphics/GraphicsContextFailureView.d.ts +1 -2
  223. package/src/view/graphics/GraphicsContextFailureView.d.ts.map +1 -1
  224. package/src/view/graphics/GraphicsContextFailureView.js +1 -2
  225. package/src/engine/network/transport/Channel.js +0 -336
  226. package/src/engine/network/transport/ReliableCommandPipeline.js +0 -335
  227. package/src/engine/network/transport/fragments/FragmentAssembler.js +0 -350
  228. package/src/engine/network/transport/fragments/FragmentRetention.js +0 -191
  229. package/src/engine/network/transport/fragments/fragment_send.js +0 -157
  230. package/src/engine/network/transport/fragments/packet_size.js +0 -107
@@ -12,6 +12,10 @@ export const BUS_ROOT_PARENT: string;
12
12
  *
13
13
  * Each bus is wired `input → effect[0] → … → effect[n] → gain(output)`; the output then connects to
14
14
  * its parent bus's input, or to the engine destination for a root bus (parentId === "").
15
+ *
16
+ * A bus's inserts are an {@link EffectChain}, and their parameters move through {@link setEffectParam} /
17
+ * {@link approachEffectParam} — the same two verbs as the fader's {@link setVolume} / {@link approachVolume}.
18
+ * The graph's nodes stay its own.
15
19
  */
16
20
  export class BusGraph {
17
21
  /**
@@ -20,13 +24,16 @@ export class BusGraph {
20
24
  */
21
25
  constructor(ctx: BaseAudioContext, destination: AudioNode);
22
26
  /**
23
- * id -> { definition, input, output }
24
- * @type {Map<string, {definition: BusDefinition, input: AudioNode, output: GainNode}>}
27
+ * id -> the bus's live record
28
+ * @type {Map<string, {definition: BusDefinition, input: AudioNode, output: GainNode, nominal: number, effects: EffectChain|null, sends: GainNode[]}>}
25
29
  */
26
30
  buses: Map<string, {
27
31
  definition: BusDefinition;
28
32
  input: AudioNode;
29
33
  output: GainNode;
34
+ nominal: number;
35
+ effects: EffectChain | null;
36
+ sends: GainNode[];
30
37
  }>;
31
38
  /**
32
39
  * Build the graph from a flat list of bus definitions. Order-independent: all nodes are created
@@ -69,6 +76,40 @@ export class BusGraph {
69
76
  * @returns {AudioNode}
70
77
  */
71
78
  getOutput(id: string): AudioNode;
79
+ /**
80
+ * Move a parameter of one of a bus's insert effects, now. See {@link EffectChain#setParam}.
81
+ * @param {string} id bus
82
+ * @param {number} effectIndex position in the bus definition's `effects`
83
+ * @param {string} name one of that effect class's `parameters` (`'frequency'` on an `EqEffect`)
84
+ * @param {number} value in the parameter's own units
85
+ */
86
+ setEffectParam(id: string, effectIndex: number, name: string, value: number): void;
87
+ /**
88
+ * Glide a parameter of one of a bus's insert effects toward `value`. See {@link EffectChain#approachParam}.
89
+ * @param {string} id bus
90
+ * @param {number} effectIndex
91
+ * @param {string} name
92
+ * @param {number} value
93
+ * @param {number} timeConstant seconds, above 0
94
+ */
95
+ approachEffectParam(id: string, effectIndex: number, name: string, value: number, timeConstant: number): void;
96
+ /**
97
+ * Where a bus effect's parameter was last sent (a glide's target, not where it has got to).
98
+ * @param {string} id bus
99
+ * @param {number} effectIndex
100
+ * @param {string} name
101
+ * @returns {number}
102
+ */
103
+ getEffectParam(id: string, effectIndex: number, name: string): number;
104
+ /**
105
+ * Whether this graph has bus `id`, with an insert at `effectIndex` exposing a parameter `name` — an
106
+ * address {@link setEffectParam} would take. Asks; never throws.
107
+ * @param {string} id bus
108
+ * @param {number} effectIndex
109
+ * @param {string} name
110
+ * @returns {boolean}
111
+ */
112
+ hasEffectParam(id: string, effectIndex: number, name: string): boolean;
72
113
  /**
73
114
  * @param {string} id
74
115
  * @returns {number} linear gain
@@ -116,4 +157,5 @@ export class BusGraph {
116
157
  #private;
117
158
  }
118
159
  export type BusDefinition = import("../definition/BusDefinition.js").BusDefinition;
160
+ import { EffectChain } from "./EffectChain.js";
119
161
  //# sourceMappingURL=BusGraph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BusGraph.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/sound/sopra/runtime/BusGraph.js"],"names":[],"mappings":"AAIA;;GAEG;AAEH;;;;GAIG;AACH,8BAFU,MAAM,CAEkB;AAElC;;;;;GAKG;AACH;IAkBI;;;OAGG;IACH,iBAHW,gBAAgB,eAChB,SAAS,EAKnB;IAvBD;;;OAGG;IACH,OAFU,IAAI,MAAM,EAAE;QAAC,UAAU,EAAE,aAAa,CAAC;QAAC,KAAK,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,QAAQ,CAAA;KAAC,CAAC,CAEpE;IAqBlB;;;;OAIG;IACH,mBAFW,aAAa,EAAE,QA4FzB;IAED;;;;;;;;;;;;;;;OAeG;IACH,gBAcC;IAED;;;OAGG;IACH,QAHW,MAAM,GACJ,OAAO,CAInB;IAoBD;;;;OAIG;IACH,aAHW,MAAM,GACJ,SAAS,CAIrB;IAED;;;;;OAKG;IACH,cAHW,MAAM,GACJ,SAAS,CAIrB;IAED;;;OAGG;IACH,cAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;;;;;;;;;OAeG;IACH,cAHW,MAAM,UACN,MAAM,QAkBhB;IAED;;;OAGG;IACH,gBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;OAOG;IACH,eALW,MAAM,UACN,MAAM,YACN,MAAM,eACN,MAAM,QAOhB;IAED;;;;;;OAMG;IACH,mBAJW,MAAM,UACN,MAAM,gBACN,MAAM,QAQhB;;CACJ;4BAxSY,OAAO,gCAAgC,EAAE,aAAa"}
1
+ {"version":3,"file":"BusGraph.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/sound/sopra/runtime/BusGraph.js"],"names":[],"mappings":"AAKA;;GAEG;AAEH;;;;GAIG;AACH,8BAFU,MAAM,CAEkB;AAElC;;;;;;;;;GASG;AACH;IAkBI;;;OAGG;IACH,iBAHW,gBAAgB,eAChB,SAAS,EAKnB;IAvBD;;;OAGG;IACH,OAFU,IAAI,MAAM,EAAE;QAAC,UAAU,EAAE,aAAa,CAAC;QAAC,KAAK,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,WAAW,GAAC,IAAI,CAAC;QAAC,KAAK,EAAE,QAAQ,EAAE,CAAA;KAAC,CAAC,CAEnI;IAqBlB;;;;OAIG;IACH,mBAFW,aAAa,EAAE,QAqEzB;IAED;;;;;;;;;;;;;;;OAeG;IACH,gBAcC;IAED;;;OAGG;IACH,QAHW,MAAM,GACJ,OAAO,CAInB;IAoBD;;;;OAIG;IACH,aAHW,MAAM,GACJ,SAAS,CAIrB;IAED;;;;;OAKG;IACH,cAHW,MAAM,GACJ,SAAS,CAIrB;IAkBD;;;;;;OAMG;IACH,mBALW,MAAM,eACN,MAAM,QACN,MAAM,SACN,MAAM,QAIhB;IAED;;;;;;;OAOG;IACH,wBANW,MAAM,eACN,MAAM,QACN,MAAM,SACN,MAAM,gBACN,MAAM,QAIhB;IAED;;;;;;OAMG;IACH,mBALW,MAAM,eACN,MAAM,QACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;OAOG;IACH,mBALW,MAAM,eACN,MAAM,QACN,MAAM,GACJ,OAAO,CAMnB;IAED;;;OAGG;IACH,cAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;;;;;;;;;OAeG;IACH,cAHW,MAAM,UACN,MAAM,QAYhB;IAED;;;OAGG;IACH,gBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;OAOG;IACH,eALW,MAAM,UACN,MAAM,YACN,MAAM,eACN,MAAM,QAOhB;IAED;;;;;;OAMG;IACH,mBAJW,MAAM,UACN,MAAM,gBACN,MAAM,QAQhB;;CACJ;4BA/UY,OAAO,gCAAgC,EAAE,aAAa;4BAHvC,kBAAkB"}
@@ -1,6 +1,7 @@
1
1
  import { dB2Volume } from "../../dB2Volume.js";
2
2
  import { volume2dB } from "../../volume2dB.js";
3
- import { rampGain } from "../util/fadeOutAndStop.js";
3
+ import { cancelAndHold, rampGain } from "../util/fadeOutAndStop.js";
4
+ import { EffectChain } from "./EffectChain.js";
4
5
 
5
6
  /**
6
7
  * @typedef {import("../definition/BusDefinition.js").BusDefinition} BusDefinition
@@ -18,12 +19,16 @@ export const BUS_ROOT_PARENT = "";
18
19
  *
19
20
  * Each bus is wired `input → effect[0] → … → effect[n] → gain(output)`; the output then connects to
20
21
  * its parent bus's input, or to the engine destination for a root bus (parentId === "").
22
+ *
23
+ * A bus's inserts are an {@link EffectChain}, and their parameters move through {@link setEffectParam} /
24
+ * {@link approachEffectParam} — the same two verbs as the fader's {@link setVolume} / {@link approachVolume}.
25
+ * The graph's nodes stay its own.
21
26
  */
22
27
  export class BusGraph {
23
28
 
24
29
  /**
25
- * id -> { definition, input, output }
26
- * @type {Map<string, {definition: BusDefinition, input: AudioNode, output: GainNode}>}
30
+ * id -> the bus's live record
31
+ * @type {Map<string, {definition: BusDefinition, input: AudioNode, output: GainNode, nominal: number, effects: EffectChain|null, sends: GainNode[]}>}
27
32
  */
28
33
  buses = new Map();
29
34
 
@@ -63,44 +68,21 @@ export class BusGraph {
63
68
  gain.gain.setValueAtTime(nominal, 0);
64
69
 
65
70
  let input = gain;
71
+ let effects = null;
66
72
 
67
- // every node this bus owns, so {@link dispose} can unwire the lot rather than only the
68
- // ends of the chain
69
- const chain = [];
70
-
71
- const effects = def.effects;
72
-
73
- if (effects.length > 0) {
74
- let previousOutput = null;
75
-
76
- for (let e = 0; e < effects.length; e++) {
77
- const built = effects[e].build(ctx);
78
-
79
- chain.push(built.input);
73
+ if (def.effects.length > 0) {
74
+ effects = new EffectChain(ctx, def.effects);
80
75
 
81
- if (built.output !== built.input) {
82
- chain.push(built.output);
83
- }
84
-
85
- if (previousOutput === null) {
86
- // first effect is the chain head
87
- input = built.input;
88
- } else {
89
- previousOutput.connect(built.input);
90
- }
91
-
92
- previousOutput = built.output;
93
- }
94
-
95
- // last effect feeds the bus gain (output)
96
- previousOutput.connect(gain);
76
+ // the inserts' tail feeds the bus gain (output); their head is the bus input
77
+ effects.output.connect(gain);
78
+ input = effects.input;
97
79
  }
98
80
 
99
81
  // `nominal` is the authoritative logical fader gain (linear), tracked separately from the
100
82
  // node's instantaneous `.value` — which WebAudio does NOT update under ramps/setTargetAtTime.
101
83
  // get/setVolume read/write `nominal`; ducking (approachVolume) is a transient modulation that
102
84
  // deliberately does NOT touch it, so the duck can capture + restore the true nominal.
103
- this.buses.set(def.id, { definition: def, input, output: gain, nominal, chain, sends: [] });
85
+ this.buses.set(def.id, { definition: def, input, output: gain, nominal, effects, sends: [] });
104
86
  }
105
87
 
106
88
  // pass 2: route each bus output to its parent's input (or the engine destination)
@@ -163,8 +145,8 @@ export class BusGraph {
163
145
  for (const bus of this.buses.values()) {
164
146
  bus.output.disconnect();
165
147
 
166
- for (let i = 0; i < bus.chain.length; i++) {
167
- bus.chain[i].disconnect();
148
+ if (bus.effects !== null) {
149
+ bus.effects.disconnect();
168
150
  }
169
151
 
170
152
  for (let i = 0; i < bus.sends.length; i++) {
@@ -189,7 +171,7 @@ export class BusGraph {
189
171
  * factored here instead of being repeated verbatim in each accessor — one place owns the message and
190
172
  * the "unknown bus is fatal" policy.
191
173
  * @param {string} id
192
- * @returns {{definition: BusDefinition, input: AudioNode, output: GainNode}}
174
+ * @returns {{definition: BusDefinition, input: AudioNode, output: GainNode, nominal: number, effects: EffectChain|null, sends: GainNode[]}}
193
175
  */
194
176
  #bus(id) {
195
177
  const bus = this.buses.get(id);
@@ -220,6 +202,70 @@ export class BusGraph {
220
202
  return this.#bus(id).output;
221
203
  }
222
204
 
205
+ /**
206
+ * The bus's inserts — throws if it has none, since addressing an effect on a bus that defines no
207
+ * effects is the same programming error as addressing a bus that does not exist.
208
+ * @param {string} id
209
+ * @returns {EffectChain}
210
+ */
211
+ #effects(id) {
212
+ const effects = this.#bus(id).effects;
213
+
214
+ if (effects === null) {
215
+ throw new Error(`sopra: bus '${id}' has no effects`);
216
+ }
217
+
218
+ return effects;
219
+ }
220
+
221
+ /**
222
+ * Move a parameter of one of a bus's insert effects, now. See {@link EffectChain#setParam}.
223
+ * @param {string} id bus
224
+ * @param {number} effectIndex position in the bus definition's `effects`
225
+ * @param {string} name one of that effect class's `parameters` (`'frequency'` on an `EqEffect`)
226
+ * @param {number} value in the parameter's own units
227
+ */
228
+ setEffectParam(id, effectIndex, name, value) {
229
+ this.#effects(id).setParam(effectIndex, name, value);
230
+ }
231
+
232
+ /**
233
+ * Glide a parameter of one of a bus's insert effects toward `value`. See {@link EffectChain#approachParam}.
234
+ * @param {string} id bus
235
+ * @param {number} effectIndex
236
+ * @param {string} name
237
+ * @param {number} value
238
+ * @param {number} timeConstant seconds, above 0
239
+ */
240
+ approachEffectParam(id, effectIndex, name, value, timeConstant) {
241
+ this.#effects(id).approachParam(effectIndex, name, value, timeConstant);
242
+ }
243
+
244
+ /**
245
+ * Where a bus effect's parameter was last sent (a glide's target, not where it has got to).
246
+ * @param {string} id bus
247
+ * @param {number} effectIndex
248
+ * @param {string} name
249
+ * @returns {number}
250
+ */
251
+ getEffectParam(id, effectIndex, name) {
252
+ return this.#effects(id).getParam(effectIndex, name);
253
+ }
254
+
255
+ /**
256
+ * Whether this graph has bus `id`, with an insert at `effectIndex` exposing a parameter `name` — an
257
+ * address {@link setEffectParam} would take. Asks; never throws.
258
+ * @param {string} id bus
259
+ * @param {number} effectIndex
260
+ * @param {string} name
261
+ * @returns {boolean}
262
+ */
263
+ hasEffectParam(id, effectIndex, name) {
264
+ const bus = this.buses.get(id);
265
+
266
+ return bus !== undefined && bus.effects !== null && bus.effects.hasParam(effectIndex, name);
267
+ }
268
+
223
269
  /**
224
270
  * @param {string} id
225
271
  * @returns {number} linear gain
@@ -252,13 +298,7 @@ export class BusGraph {
252
298
  const param = bus.output.gain;
253
299
  const now = this.#ctx.currentTime;
254
300
 
255
- if (typeof param.cancelAndHoldAtTime === "function") {
256
- param.cancelAndHoldAtTime(now);
257
- } else {
258
- // portable fallback (Firefox), as in `rampGain`
259
- param.cancelScheduledValues(now);
260
- }
261
-
301
+ cancelAndHold(param, now);
262
302
  param.setValueAtTime(linear, now);
263
303
  }
264
304
 
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Resolve an effect-parameter address against a list of effect definitions: the position of `name` among
3
+ * the class `parameters` of the effect at `index`, or throw naming what the list does have. The one
4
+ * place an `(effect index, parameter name)` address is checked — a chain resolves through it, and so does
5
+ * anything that holds addresses for a chain not yet built (`EffectParamOverrides`).
6
+ * @param {AbstractAudioEffect[]} effects
7
+ * @param {number} index
8
+ * @param {string} name
9
+ * @returns {number}
10
+ */
11
+ export function effectParamIndex(effects: AbstractAudioEffect[], index: number, name: string): number;
12
+ /**
13
+ * @typedef {import("../definition/effect/AbstractAudioEffect.js").AbstractAudioEffect} AbstractAudioEffect
14
+ */
15
+ /**
16
+ * The live nodes of an ordered list of insert effects, wired `effect[0] → … → effect[n]`, and the one place
17
+ * their parameters are moved at runtime.
18
+ *
19
+ * Shared by a bus's inserts ({@link BusGraph}) and a single play's inserts ({@link EventInstance}), so both
20
+ * address a parameter the same way: by the effect's index in the list it was built from and the parameter's
21
+ * name, one of the effect class's {@link AbstractAudioEffect.parameters} — the name of the definition field
22
+ * it starts from (`EqEffect.frequency` → `'frequency'`).
23
+ *
24
+ * Each parameter's NOMINAL value — where it was last sent — is tracked here rather than read back from the
25
+ * node, because WebAudio does not move `AudioParam.value` under scheduled automation: a glide's target is
26
+ * only visible in the automation list.
27
+ *
28
+ * Built once per bus and once per playing instance, so it is kept small: the parameter names are the
29
+ * effect classes' own static lists (read through the definitions, never copied), and the per-chain state is
30
+ * three flat arrays.
31
+ */
32
+ export class EffectChain {
33
+ /**
34
+ * @param {BaseAudioContext} ctx
35
+ * @param {AbstractAudioEffect[]} effects at least one
36
+ */
37
+ constructor(ctx: BaseAudioContext, effects: AbstractAudioEffect[]);
38
+ /**
39
+ * Head of the chain: what feeds it connects here.
40
+ * @type {AudioNode}
41
+ */
42
+ input: AudioNode;
43
+ /**
44
+ * Tail of the chain: connect this onward.
45
+ * @type {AudioNode}
46
+ */
47
+ output: AudioNode;
48
+ /**
49
+ * @returns {number} how many effects the chain holds
50
+ */
51
+ get length(): number;
52
+ /**
53
+ * Whether the chain has an effect at `index` exposing a parameter called `name` — an address
54
+ * {@link setParam} would take. Asks; never throws.
55
+ * @param {number} index
56
+ * @param {string} name
57
+ * @returns {boolean}
58
+ */
59
+ hasParam(index: number, name: string): boolean;
60
+ /**
61
+ * Move an effect's parameter to `value`, now. Whatever automation was in flight on it is cancelled.
62
+ *
63
+ * @param {number} index effect's position in the list the chain was built from
64
+ * @param {string} name one of the effect class's `parameters`
65
+ * @param {number} value in the parameter's own units (Hz, dB, ...)
66
+ */
67
+ setParam(index: number, name: string, value: number): void;
68
+ /**
69
+ * Glide an effect's parameter toward `value` exponentially, covering ~63% of the way per
70
+ * `timeConstant` — the WebAudio `setTargetAtTime` glide, and the counterpart of
71
+ * {@link BusGraph#approachVolume}. Whatever was in flight is cancelled and held first, so a value sent
72
+ * every frame (a filter tracking a game value) retargets from wherever the last glide had got to, and
73
+ * the automation list never grows.
74
+ *
75
+ * @param {number} index
76
+ * @param {string} name
77
+ * @param {number} value
78
+ * @param {number} timeConstant seconds, above 0
79
+ */
80
+ approachParam(index: number, name: string, value: number, timeConstant: number): void;
81
+ /**
82
+ * Where an effect's parameter was last sent — the target of a glide still in flight, not the value it
83
+ * has reached — or its defined value if it has not been moved.
84
+ * @param {number} index
85
+ * @param {string} name
86
+ * @returns {number}
87
+ */
88
+ getParam(index: number, name: string): number;
89
+ /**
90
+ * Unwire every node the chain owns. Idempotent.
91
+ */
92
+ disconnect(): void;
93
+ #private;
94
+ }
95
+ export type AbstractAudioEffect = import("../definition/effect/AbstractAudioEffect.js").AbstractAudioEffect;
96
+ //# sourceMappingURL=EffectChain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EffectChain.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/sound/sopra/runtime/EffectChain.js"],"names":[],"mappings":"AAoQA;;;;;;;;;GASG;AACH,0CALW,mBAAmB,EAAE,SACrB,MAAM,QACN,MAAM,GACJ,MAAM,CAiBlB;AA1RD;;GAEG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH;IA4CI;;;OAGG;IACH,iBAHW,gBAAgB,WAChB,mBAAmB,EAAE,EA6D/B;IAzGD;;;OAGG;IACH,OAFU,SAAS,CAEb;IAEN;;;OAGG;IACH,QAFU,SAAS,CAEZ;IAiGP;;OAEG;IACH,qBAEC;IAED;;;;;;OAMG;IACH,gBAJW,MAAM,QACN,MAAM,GACJ,OAAO,CAMnB;IAoBD;;;;;;OAMG;IACH,gBAJW,MAAM,QACN,MAAM,SACN,MAAM,QAchB;IAED;;;;;;;;;;;OAWG;IACH,qBALW,MAAM,QACN,MAAM,SACN,MAAM,gBACN,MAAM,QAehB;IAED;;;;;;OAMG;IACH,gBAJW,MAAM,QACN,MAAM,GACJ,MAAM,CAIlB;IAED;;OAEG;IACH,mBAMC;;CACJ;kCA5OY,OAAO,6CAA6C,EAAE,mBAAmB"}
@@ -0,0 +1,286 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { cancelAndHold } from "../util/fadeOutAndStop.js";
3
+
4
+ /**
5
+ * @typedef {import("../definition/effect/AbstractAudioEffect.js").AbstractAudioEffect} AbstractAudioEffect
6
+ */
7
+
8
+ /**
9
+ * The live nodes of an ordered list of insert effects, wired `effect[0] → … → effect[n]`, and the one place
10
+ * their parameters are moved at runtime.
11
+ *
12
+ * Shared by a bus's inserts ({@link BusGraph}) and a single play's inserts ({@link EventInstance}), so both
13
+ * address a parameter the same way: by the effect's index in the list it was built from and the parameter's
14
+ * name, one of the effect class's {@link AbstractAudioEffect.parameters} — the name of the definition field
15
+ * it starts from (`EqEffect.frequency` → `'frequency'`).
16
+ *
17
+ * Each parameter's NOMINAL value — where it was last sent — is tracked here rather than read back from the
18
+ * node, because WebAudio does not move `AudioParam.value` under scheduled automation: a glide's target is
19
+ * only visible in the automation list.
20
+ *
21
+ * Built once per bus and once per playing instance, so it is kept small: the parameter names are the
22
+ * effect classes' own static lists (read through the definitions, never copied), and the per-chain state is
23
+ * three flat arrays.
24
+ */
25
+ export class EffectChain {
26
+
27
+ /**
28
+ * Head of the chain: what feeds it connects here.
29
+ * @type {AudioNode}
30
+ */
31
+ input;
32
+
33
+ /**
34
+ * Tail of the chain: connect this onward.
35
+ * @type {AudioNode}
36
+ */
37
+ output;
38
+
39
+ /**
40
+ * @type {BaseAudioContext}
41
+ */
42
+ #ctx;
43
+
44
+ /**
45
+ * The definitions the chain was built from — the source of each effect's parameter names. Shared with
46
+ * the owner (a bus or event definition); never written.
47
+ * @type {AbstractAudioEffect[]}
48
+ */
49
+ #effects;
50
+
51
+ /**
52
+ * Each effect's input and output node, in chain order: `[in0, out0, in1, out1, …]`. For {@link disconnect}.
53
+ * @type {AudioNode[]}
54
+ */
55
+ #nodes;
56
+
57
+ /**
58
+ * Every effect's live params, effect by effect, each effect's in its class's `parameters` order.
59
+ * @type {AudioParam[]}
60
+ */
61
+ #params;
62
+
63
+ /**
64
+ * Where each param of {@link #params} was last sent, same layout.
65
+ * @type {Float64Array}
66
+ */
67
+ #nominal;
68
+
69
+ /**
70
+ * @param {BaseAudioContext} ctx
71
+ * @param {AbstractAudioEffect[]} effects at least one
72
+ */
73
+ constructor(ctx, effects) {
74
+ assert.defined(ctx, 'ctx');
75
+ assert.isArray(effects, 'effects');
76
+ assert.greaterThan(effects.length, 0, 'effects.length');
77
+
78
+ this.#ctx = ctx;
79
+ this.#effects = effects;
80
+
81
+ const effectCount = effects.length;
82
+
83
+ let paramCount = 0;
84
+ for (let i = 0; i < effectCount; i++) {
85
+ paramCount += parametersOf(effects[i]).length;
86
+ }
87
+
88
+ const nodes = new Array(effectCount * 2);
89
+ const params = new Array(paramCount);
90
+ const nominal = new Float64Array(paramCount);
91
+
92
+ let slot = 0;
93
+ let previousOutput = null;
94
+
95
+ for (let i = 0; i < effectCount; i++) {
96
+ const effect = effects[i];
97
+ const built = effect.build(ctx);
98
+ const names = parametersOf(effect);
99
+
100
+ nodes[i * 2] = built.input;
101
+ nodes[i * 2 + 1] = built.output;
102
+
103
+ if (previousOutput === null) {
104
+ this.input = built.input;
105
+ } else {
106
+ previousOutput.connect(built.input);
107
+ }
108
+
109
+ previousOutput = built.output;
110
+
111
+ if (names.length > 0) {
112
+ const exposed = built.params;
113
+
114
+ // the class's list is the contract `build` answers to; a mismatch would silently move
115
+ // the wrong param, so it is refused here, where the effect that broke it is known
116
+ if (exposed === undefined || exposed.length !== names.length) {
117
+ throw new Error(`sopra: ${effect.constructor.name}.build returned ${exposed === undefined ? 'no' : exposed.length} params, its class declares ${names.length} (${names.join(', ')})`);
118
+ }
119
+
120
+ for (let p = 0; p < names.length; p++) {
121
+ params[slot] = exposed[p];
122
+ nominal[slot] = exposed[p].value;
123
+ slot++;
124
+ }
125
+ }
126
+ }
127
+
128
+ this.output = previousOutput;
129
+ this.#nodes = nodes;
130
+ this.#params = params;
131
+ this.#nominal = nominal;
132
+ }
133
+
134
+ /**
135
+ * @returns {number} how many effects the chain holds
136
+ */
137
+ get length() {
138
+ return this.#effects.length;
139
+ }
140
+
141
+ /**
142
+ * Whether the chain has an effect at `index` exposing a parameter called `name` — an address
143
+ * {@link setParam} would take. Asks; never throws.
144
+ * @param {number} index
145
+ * @param {string} name
146
+ * @returns {boolean}
147
+ */
148
+ hasParam(index, name) {
149
+ const effect = effectAt(this.#effects, index);
150
+
151
+ return effect !== undefined && parametersOf(effect).indexOf(name) !== -1;
152
+ }
153
+
154
+ /**
155
+ * The flat slot of an effect's parameter, or throw naming what does exist.
156
+ * @param {number} index
157
+ * @param {string} name
158
+ * @returns {number}
159
+ */
160
+ #slot(index, name) {
161
+ const effects = this.#effects;
162
+ const p = effectParamIndex(effects, index, name);
163
+
164
+ let slot = 0;
165
+ for (let i = 0; i < index; i++) {
166
+ slot += parametersOf(effects[i]).length;
167
+ }
168
+
169
+ return slot + p;
170
+ }
171
+
172
+ /**
173
+ * Move an effect's parameter to `value`, now. Whatever automation was in flight on it is cancelled.
174
+ *
175
+ * @param {number} index effect's position in the list the chain was built from
176
+ * @param {string} name one of the effect class's `parameters`
177
+ * @param {number} value in the parameter's own units (Hz, dB, ...)
178
+ */
179
+ setParam(index, name, value) {
180
+ // an AudioParam throws on a non-finite value, so say which call sent it
181
+ assert.isFinite(value, 'value');
182
+
183
+ const slot = this.#slot(index, name);
184
+ const param = this.#params[slot];
185
+ const now = this.#ctx.currentTime;
186
+
187
+ this.#nominal[slot] = value;
188
+
189
+ cancelAndHold(param, now);
190
+ param.setValueAtTime(value, now);
191
+ }
192
+
193
+ /**
194
+ * Glide an effect's parameter toward `value` exponentially, covering ~63% of the way per
195
+ * `timeConstant` — the WebAudio `setTargetAtTime` glide, and the counterpart of
196
+ * {@link BusGraph#approachVolume}. Whatever was in flight is cancelled and held first, so a value sent
197
+ * every frame (a filter tracking a game value) retargets from wherever the last glide had got to, and
198
+ * the automation list never grows.
199
+ *
200
+ * @param {number} index
201
+ * @param {string} name
202
+ * @param {number} value
203
+ * @param {number} timeConstant seconds, above 0
204
+ */
205
+ approachParam(index, name, value, timeConstant) {
206
+ assert.isFinite(value, 'value');
207
+ assert.isFinite(timeConstant, 'timeConstant');
208
+ assert.greaterThan(timeConstant, 0, 'timeConstant');
209
+
210
+ const slot = this.#slot(index, name);
211
+ const param = this.#params[slot];
212
+ const now = this.#ctx.currentTime;
213
+
214
+ this.#nominal[slot] = value;
215
+
216
+ cancelAndHold(param, now);
217
+ param.setTargetAtTime(value, now, timeConstant);
218
+ }
219
+
220
+ /**
221
+ * Where an effect's parameter was last sent — the target of a glide still in flight, not the value it
222
+ * has reached — or its defined value if it has not been moved.
223
+ * @param {number} index
224
+ * @param {string} name
225
+ * @returns {number}
226
+ */
227
+ getParam(index, name) {
228
+ return this.#nominal[this.#slot(index, name)];
229
+ }
230
+
231
+ /**
232
+ * Unwire every node the chain owns. Idempotent.
233
+ */
234
+ disconnect() {
235
+ const nodes = this.#nodes;
236
+
237
+ for (let i = 0; i < nodes.length; i++) {
238
+ nodes[i].disconnect();
239
+ }
240
+ }
241
+ }
242
+
243
+ /**
244
+ * @param {AbstractAudioEffect} effect
245
+ * @returns {string[]}
246
+ */
247
+ function parametersOf(effect) {
248
+ return effect.constructor.parameters;
249
+ }
250
+
251
+ /**
252
+ * @param {AbstractAudioEffect[]} effects
253
+ * @param {number} index
254
+ * @returns {AbstractAudioEffect|undefined} undefined for an index that is no position in the list
255
+ * (negative, fractional, past the end)
256
+ */
257
+ function effectAt(effects, index) {
258
+ return Number.isInteger(index) ? effects[index] : undefined;
259
+ }
260
+
261
+ /**
262
+ * Resolve an effect-parameter address against a list of effect definitions: the position of `name` among
263
+ * the class `parameters` of the effect at `index`, or throw naming what the list does have. The one
264
+ * place an `(effect index, parameter name)` address is checked — a chain resolves through it, and so does
265
+ * anything that holds addresses for a chain not yet built (`EffectParamOverrides`).
266
+ * @param {AbstractAudioEffect[]} effects
267
+ * @param {number} index
268
+ * @param {string} name
269
+ * @returns {number}
270
+ */
271
+ export function effectParamIndex(effects, index, name) {
272
+ const effect = effectAt(effects, index);
273
+
274
+ if (effect === undefined) {
275
+ throw new Error(`sopra: no effect at index ${index} (there are ${effects.length})`);
276
+ }
277
+
278
+ const names = parametersOf(effect);
279
+ const p = names.indexOf(name);
280
+
281
+ if (p === -1) {
282
+ throw new Error(`sopra: effect ${index} has no parameter '${name}' (it exposes: ${names.join(', ') || 'none'})`);
283
+ }
284
+
285
+ return p;
286
+ }