@vib3code/sdk 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/DOCS/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +34 -0
  3. package/DOCS/CI_TESTING.md +38 -0
  4. package/DOCS/CLI_ONBOARDING.md +75 -0
  5. package/DOCS/CONTROL_REFERENCE.md +64 -0
  6. package/DOCS/DEV_TRACK_ANALYSIS.md +77 -0
  7. package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +42 -0
  8. package/DOCS/DEV_TRACK_SESSION_2026-01-31.md +220 -0
  9. package/DOCS/ENV_SETUP.md +189 -0
  10. package/DOCS/EXPORT_FORMATS.md +417 -0
  11. package/DOCS/GPU_DISPOSAL_GUIDE.md +21 -0
  12. package/DOCS/LICENSING_TIERS.md +275 -0
  13. package/DOCS/MASTER_PLAN_2026-01-31.md +570 -0
  14. package/DOCS/OBS_SETUP_GUIDE.md +98 -0
  15. package/DOCS/PROJECT_SETUP.md +66 -0
  16. package/DOCS/RENDERER_LIFECYCLE.md +40 -0
  17. package/DOCS/REPO_MANIFEST.md +121 -0
  18. package/DOCS/SESSION_014_PLAN.md +195 -0
  19. package/DOCS/SESSION_LOG_2026-01-07.md +56 -0
  20. package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +72 -0
  21. package/DOCS/SYSTEM_AUDIT_2026-01-30.md +738 -0
  22. package/DOCS/SYSTEM_INVENTORY.md +520 -0
  23. package/DOCS/TELEMETRY_EXPORTS.md +34 -0
  24. package/DOCS/WEBGPU_STATUS.md +38 -0
  25. package/DOCS/XR_BENCHMARKS.md +608 -0
  26. package/LICENSE +21 -0
  27. package/README.md +426 -0
  28. package/docs/.nojekyll +0 -0
  29. package/docs/01-dissolution_of_euclidean_hegemony.html +346 -0
  30. package/docs/02-hyperspatial_ego_death.html +346 -0
  31. package/docs/03-post_cartesian_sublime.html +346 -0
  32. package/docs/04-crystalline_void_meditation.html +346 -0
  33. package/docs/05-quantum_decoherence_ballet.html +346 -0
  34. package/docs/06-dissolution_of_euclidean_hegemony.html +346 -0
  35. package/docs/07-hyperspatial_ego_death.html +346 -0
  36. package/docs/08-post_cartesian_sublime.html +346 -0
  37. package/docs/09-crystalline_void_meditation.html +346 -0
  38. package/docs/10-quantum_decoherence_ballet.html +346 -0
  39. package/docs/11-dissolution_of_euclidean_hegemony.html +346 -0
  40. package/docs/12-hyperspatial_ego_death.html +346 -0
  41. package/docs/13-post_cartesian_sublime.html +346 -0
  42. package/docs/index.html +794 -0
  43. package/docs/test-hub.html +441 -0
  44. package/docs/url-state.js +102 -0
  45. package/docs/vib3-exports/01-quantum-quantum-tetrahedron-lattice.html +489 -0
  46. package/docs/vib3-exports/02-quantum-quantum-hypersphere-matrix.html +489 -0
  47. package/docs/vib3-exports/03-quantum-quantum-hypertetra-fractal.html +489 -0
  48. package/docs/vib3-exports/04-faceted-faceted-crystal-structure.html +407 -0
  49. package/docs/vib3-exports/05-faceted-faceted-klein-bottle.html +407 -0
  50. package/docs/vib3-exports/06-faceted-faceted-hypertetra-torus.html +407 -0
  51. package/docs/vib3-exports/07-holographic-holographic-wave-field.html +457 -0
  52. package/docs/vib3-exports/08-holographic-holographic-hypersphere-sphere.html +457 -0
  53. package/docs/vib3-exports/09-holographic-holographic-hypertetra-crystal.html +457 -0
  54. package/docs/vib3-exports/index.html +238 -0
  55. package/docs/webgpu-live.html +702 -0
  56. package/package.json +367 -0
  57. package/src/advanced/AIPresetGenerator.js +777 -0
  58. package/src/advanced/MIDIController.js +703 -0
  59. package/src/advanced/OffscreenWorker.js +1051 -0
  60. package/src/advanced/WebGPUCompute.js +1051 -0
  61. package/src/advanced/WebXRRenderer.js +680 -0
  62. package/src/agent/cli/AgentCLI.js +615 -0
  63. package/src/agent/cli/index.js +14 -0
  64. package/src/agent/index.js +73 -0
  65. package/src/agent/mcp/MCPServer.js +950 -0
  66. package/src/agent/mcp/index.js +9 -0
  67. package/src/agent/mcp/tools.js +548 -0
  68. package/src/agent/telemetry/EventStream.js +669 -0
  69. package/src/agent/telemetry/Instrumentation.js +618 -0
  70. package/src/agent/telemetry/TelemetryExporters.js +427 -0
  71. package/src/agent/telemetry/TelemetryService.js +464 -0
  72. package/src/agent/telemetry/index.js +52 -0
  73. package/src/benchmarks/BenchmarkRunner.js +381 -0
  74. package/src/benchmarks/MetricsCollector.js +299 -0
  75. package/src/benchmarks/index.js +9 -0
  76. package/src/benchmarks/scenes.js +259 -0
  77. package/src/cli/index.js +675 -0
  78. package/src/config/ApiConfig.js +88 -0
  79. package/src/core/CanvasManager.js +217 -0
  80. package/src/core/ErrorReporter.js +117 -0
  81. package/src/core/ParameterMapper.js +333 -0
  82. package/src/core/Parameters.js +396 -0
  83. package/src/core/RendererContracts.js +200 -0
  84. package/src/core/UnifiedResourceManager.js +370 -0
  85. package/src/core/VIB3Engine.js +636 -0
  86. package/src/core/renderers/FacetedRendererAdapter.js +32 -0
  87. package/src/core/renderers/HolographicRendererAdapter.js +29 -0
  88. package/src/core/renderers/QuantumRendererAdapter.js +29 -0
  89. package/src/core/renderers/RendererLifecycleManager.js +63 -0
  90. package/src/creative/ColorPresetsSystem.js +980 -0
  91. package/src/creative/ParameterTimeline.js +1061 -0
  92. package/src/creative/PostProcessingPipeline.js +1113 -0
  93. package/src/creative/TransitionAnimator.js +683 -0
  94. package/src/export/CSSExporter.js +226 -0
  95. package/src/export/CardGeneratorBase.js +279 -0
  96. package/src/export/ExportManager.js +580 -0
  97. package/src/export/FacetedCardGenerator.js +279 -0
  98. package/src/export/HolographicCardGenerator.js +543 -0
  99. package/src/export/LottieExporter.js +552 -0
  100. package/src/export/QuantumCardGenerator.js +315 -0
  101. package/src/export/SVGExporter.js +519 -0
  102. package/src/export/ShaderExporter.js +903 -0
  103. package/src/export/TradingCardGenerator.js +3055 -0
  104. package/src/export/TradingCardManager.js +181 -0
  105. package/src/export/VIB3PackageExporter.js +559 -0
  106. package/src/export/index.js +14 -0
  107. package/src/export/systems/TradingCardSystemFaceted.js +494 -0
  108. package/src/export/systems/TradingCardSystemHolographic.js +452 -0
  109. package/src/export/systems/TradingCardSystemQuantum.js +411 -0
  110. package/src/faceted/FacetedSystem.js +963 -0
  111. package/src/features/CollectionManager.js +433 -0
  112. package/src/gallery/CollectionManager.js +240 -0
  113. package/src/gallery/GallerySystem.js +485 -0
  114. package/src/geometry/GeometryFactory.js +314 -0
  115. package/src/geometry/GeometryLibrary.js +72 -0
  116. package/src/geometry/buffers/BufferBuilder.js +338 -0
  117. package/src/geometry/buffers/index.js +18 -0
  118. package/src/geometry/generators/Crystal.js +420 -0
  119. package/src/geometry/generators/Fractal.js +298 -0
  120. package/src/geometry/generators/KleinBottle.js +197 -0
  121. package/src/geometry/generators/Sphere.js +192 -0
  122. package/src/geometry/generators/Tesseract.js +160 -0
  123. package/src/geometry/generators/Tetrahedron.js +225 -0
  124. package/src/geometry/generators/Torus.js +304 -0
  125. package/src/geometry/generators/Wave.js +341 -0
  126. package/src/geometry/index.js +142 -0
  127. package/src/geometry/warp/HypersphereCore.js +211 -0
  128. package/src/geometry/warp/HypertetraCore.js +386 -0
  129. package/src/geometry/warp/index.js +57 -0
  130. package/src/holograms/HolographicVisualizer.js +1073 -0
  131. package/src/holograms/RealHolographicSystem.js +966 -0
  132. package/src/holograms/variantRegistry.js +69 -0
  133. package/src/integrations/FigmaPlugin.js +854 -0
  134. package/src/integrations/OBSMode.js +754 -0
  135. package/src/integrations/ThreeJsPackage.js +660 -0
  136. package/src/integrations/TouchDesignerExport.js +552 -0
  137. package/src/integrations/frameworks/Vib3React.js +591 -0
  138. package/src/integrations/frameworks/Vib3Svelte.js +654 -0
  139. package/src/integrations/frameworks/Vib3Vue.js +628 -0
  140. package/src/llm/LLMParameterInterface.js +240 -0
  141. package/src/llm/LLMParameterUI.js +577 -0
  142. package/src/math/Mat4x4.js +708 -0
  143. package/src/math/Projection.js +341 -0
  144. package/src/math/Rotor4D.js +637 -0
  145. package/src/math/Vec4.js +476 -0
  146. package/src/math/constants.js +164 -0
  147. package/src/math/index.js +68 -0
  148. package/src/math/projections.js +54 -0
  149. package/src/math/rotations.js +196 -0
  150. package/src/quantum/QuantumEngine.js +906 -0
  151. package/src/quantum/QuantumVisualizer.js +1103 -0
  152. package/src/reactivity/ReactivityConfig.js +499 -0
  153. package/src/reactivity/ReactivityManager.js +586 -0
  154. package/src/reactivity/SpatialInputSystem.js +1783 -0
  155. package/src/reactivity/index.js +93 -0
  156. package/src/render/CommandBuffer.js +465 -0
  157. package/src/render/MultiCanvasBridge.js +340 -0
  158. package/src/render/RenderCommand.js +514 -0
  159. package/src/render/RenderResourceRegistry.js +523 -0
  160. package/src/render/RenderState.js +552 -0
  161. package/src/render/RenderTarget.js +512 -0
  162. package/src/render/ShaderLoader.js +253 -0
  163. package/src/render/ShaderProgram.js +599 -0
  164. package/src/render/UnifiedRenderBridge.js +496 -0
  165. package/src/render/backends/WebGLBackend.js +1108 -0
  166. package/src/render/backends/WebGPUBackend.js +1409 -0
  167. package/src/render/commands/CommandBufferExecutor.js +607 -0
  168. package/src/render/commands/RenderCommandBuffer.js +661 -0
  169. package/src/render/commands/index.js +17 -0
  170. package/src/render/index.js +367 -0
  171. package/src/scene/Disposable.js +498 -0
  172. package/src/scene/MemoryPool.js +618 -0
  173. package/src/scene/Node4D.js +697 -0
  174. package/src/scene/ResourceManager.js +599 -0
  175. package/src/scene/Scene4D.js +540 -0
  176. package/src/scene/index.js +98 -0
  177. package/src/schemas/error.schema.json +84 -0
  178. package/src/schemas/extension.schema.json +88 -0
  179. package/src/schemas/index.js +214 -0
  180. package/src/schemas/parameters.schema.json +142 -0
  181. package/src/schemas/tool-pack.schema.json +44 -0
  182. package/src/schemas/tool-response.schema.json +127 -0
  183. package/src/shaders/common/fullscreen.vert.glsl +5 -0
  184. package/src/shaders/common/fullscreen.vert.wgsl +17 -0
  185. package/src/shaders/common/geometry24.glsl +65 -0
  186. package/src/shaders/common/geometry24.wgsl +54 -0
  187. package/src/shaders/common/rotation4d.glsl +85 -0
  188. package/src/shaders/common/rotation4d.wgsl +86 -0
  189. package/src/shaders/common/uniforms.glsl +44 -0
  190. package/src/shaders/common/uniforms.wgsl +48 -0
  191. package/src/shaders/faceted/faceted.frag.glsl +129 -0
  192. package/src/shaders/faceted/faceted.frag.wgsl +164 -0
  193. package/src/shaders/holographic/holographic.frag.glsl +406 -0
  194. package/src/shaders/holographic/holographic.frag.wgsl +185 -0
  195. package/src/shaders/quantum/quantum.frag.glsl +513 -0
  196. package/src/shaders/quantum/quantum.frag.wgsl +361 -0
  197. package/src/testing/ParallelTestFramework.js +519 -0
  198. package/src/testing/__snapshots__/exportFormats.test.js.snap +24 -0
  199. package/src/testing/exportFormats.test.js +8 -0
  200. package/src/testing/projections.test.js +14 -0
  201. package/src/testing/rotations.test.js +37 -0
  202. package/src/ui/InteractivityMenu.js +516 -0
  203. package/src/ui/StatusManager.js +96 -0
  204. package/src/ui/adaptive/renderers/webgpu/BufferLayout.ts +252 -0
  205. package/src/ui/adaptive/renderers/webgpu/PolytopeInstanceBuffer.ts +144 -0
  206. package/src/ui/adaptive/renderers/webgpu/TripleBufferedUniform.ts +170 -0
  207. package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +735 -0
  208. package/src/ui/adaptive/renderers/webgpu/index.ts +112 -0
  209. package/src/variations/VariationManager.js +431 -0
  210. package/src/viewer/AudioReactivity.js +505 -0
  211. package/src/viewer/CardBending.js +481 -0
  212. package/src/viewer/GalleryUI.js +832 -0
  213. package/src/viewer/ReactivityManager.js +590 -0
  214. package/src/viewer/TradingCardExporter.js +600 -0
  215. package/src/viewer/ViewerPortal.js +374 -0
  216. package/src/viewer/index.js +12 -0
  217. package/src/wasm/WasmLoader.js +296 -0
  218. package/src/wasm/index.js +132 -0
  219. package/tools/agentic/mcpTools.js +88 -0
  220. package/tools/cli/agent-cli.js +92 -0
  221. package/tools/export/formats.js +24 -0
  222. package/tools/math/rotation-baseline.mjs +64 -0
  223. package/tools/shader-sync-verify.js +937 -0
  224. package/tools/telemetry/manifestPipeline.js +141 -0
  225. package/tools/telemetry/telemetryEvents.js +35 -0
  226. package/types/adaptive-sdk.d.ts +185 -0
  227. package/types/advanced/AIPresetGenerator.d.ts +81 -0
  228. package/types/advanced/MIDIController.d.ts +100 -0
  229. package/types/advanced/OffscreenWorker.d.ts +82 -0
  230. package/types/advanced/WebGPUCompute.d.ts +52 -0
  231. package/types/advanced/WebXRRenderer.d.ts +77 -0
  232. package/types/advanced/index.d.ts +46 -0
  233. package/types/core/ErrorReporter.d.ts +50 -0
  234. package/types/core/VIB3Engine.d.ts +204 -0
  235. package/types/creative/ColorPresetsSystem.d.ts +91 -0
  236. package/types/creative/ParameterTimeline.d.ts +74 -0
  237. package/types/creative/PostProcessingPipeline.d.ts +109 -0
  238. package/types/creative/TransitionAnimator.d.ts +71 -0
  239. package/types/creative/index.d.ts +35 -0
  240. package/types/integrations/FigmaPlugin.d.ts +46 -0
  241. package/types/integrations/OBSMode.d.ts +74 -0
  242. package/types/integrations/ThreeJsPackage.d.ts +62 -0
  243. package/types/integrations/TouchDesignerExport.d.ts +36 -0
  244. package/types/integrations/Vib3React.d.ts +74 -0
  245. package/types/integrations/Vib3Svelte.d.ts +63 -0
  246. package/types/integrations/Vib3Vue.d.ts +55 -0
  247. package/types/integrations/index.d.ts +52 -0
  248. package/types/reactivity/SpatialInputSystem.d.ts +173 -0
  249. package/types/reactivity/index.d.ts +394 -0
  250. package/types/render/CommandBuffer.d.ts +169 -0
  251. package/types/render/RenderCommand.d.ts +312 -0
  252. package/types/render/RenderState.d.ts +279 -0
  253. package/types/render/RenderTarget.d.ts +254 -0
  254. package/types/render/ShaderProgram.d.ts +277 -0
  255. package/types/render/UnifiedRenderBridge.d.ts +143 -0
  256. package/types/render/WebGLBackend.d.ts +168 -0
  257. package/types/render/WebGPUBackend.d.ts +186 -0
  258. package/types/render/index.d.ts +141 -0
@@ -0,0 +1,703 @@
1
+ /**
2
+ * VIB3+ MIDI Controller Integration
3
+ * Maps MIDI input to visualization parameters via the Web MIDI API.
4
+ *
5
+ * Features:
6
+ * - Auto-detection of connected MIDI devices
7
+ * - Default DJ-controller layout mapping
8
+ * - MIDI Learn mode (map any CC/note to any parameter)
9
+ * - Per-mapping curve, scale, and invert options
10
+ * - Note-on/off action triggers (geometry presets, system switch, etc.)
11
+ * - Full serialization/deserialization of mappings
12
+ *
13
+ * @module advanced/MIDIController
14
+ */
15
+
16
+ /**
17
+ * Options for a CC-to-parameter mapping.
18
+ * @typedef {Object} CCMappingOptions
19
+ * @property {number} [min=0] - Output minimum
20
+ * @property {number} [max=1] - Output maximum
21
+ * @property {boolean} [invert=false] - Invert the CC direction
22
+ * @property {'linear'|'exponential'|'logarithmic'|'scurve'} [curve='linear'] - Response curve
23
+ * @property {number} [channel=0] - MIDI channel (0-15, 0 = omni)
24
+ */
25
+
26
+ /**
27
+ * A stored CC mapping entry.
28
+ * @typedef {Object} CCMapping
29
+ * @property {string} param - VIB3 parameter name
30
+ * @property {number} channel - MIDI channel (0 = omni)
31
+ * @property {number} cc - Control Change number (0-127)
32
+ * @property {number} min - Mapped minimum value
33
+ * @property {number} max - Mapped maximum value
34
+ * @property {boolean} invert - Whether input is inverted
35
+ * @property {string} curve - Curve type
36
+ */
37
+
38
+ /**
39
+ * A stored note mapping entry.
40
+ * @typedef {Object} NoteMapping
41
+ * @property {string} action - Action identifier (e.g. 'geometry:5', 'system:quantum')
42
+ * @property {number} channel - MIDI channel (0 = omni)
43
+ * @property {number} note - MIDI note number (0-127)
44
+ * @property {'noteOn'|'noteOff'|'toggle'} trigger - When to fire the action
45
+ */
46
+
47
+ export class MIDIController {
48
+ /**
49
+ * @param {Function} parameterUpdateFn - Callback invoked as parameterUpdateFn(name, value)
50
+ * whenever a MIDI message maps to a parameter change.
51
+ */
52
+ constructor(parameterUpdateFn) {
53
+ /** @type {Function} */
54
+ this.updateParameter = parameterUpdateFn;
55
+
56
+ /** @type {MIDIAccess|null} */
57
+ this.midiAccess = null;
58
+
59
+ /** @type {Map<string, MIDIInput>} Connected inputs keyed by id */
60
+ this.inputs = new Map();
61
+
62
+ /**
63
+ * CC mappings keyed by "channel:cc" (e.g. "0:74").
64
+ * @type {Map<string, CCMapping>}
65
+ */
66
+ this.mappings = new Map();
67
+
68
+ /**
69
+ * Note mappings keyed by "channel:note".
70
+ * @type {Map<string, NoteMapping>}
71
+ */
72
+ this.noteMappings = new Map();
73
+
74
+ /** @type {boolean} Whether MIDI Learn mode is active */
75
+ this.learning = false;
76
+
77
+ /**
78
+ * Callback invoked when a CC is received during Learn mode.
79
+ * @type {Function|null}
80
+ */
81
+ this.learnCallback = null;
82
+
83
+ /** @type {string|null} Parameter name being learned */
84
+ this._learnTarget = null;
85
+
86
+ /**
87
+ * Most recent CC values keyed by "channel:cc".
88
+ * @type {Map<string, number>}
89
+ */
90
+ this.lastValues = new Map();
91
+
92
+ /**
93
+ * Toggle state for note-mapped toggles keyed by "channel:note".
94
+ * @type {Map<string, boolean>}
95
+ */
96
+ this._toggleState = new Map();
97
+
98
+ /** @type {Function|null} External message listener */
99
+ this._onRawMessage = null;
100
+
101
+ /** @type {boolean} */
102
+ this._destroyed = false;
103
+ }
104
+
105
+ // -----------------------------------------------------------------------
106
+ // Initialization
107
+ // -----------------------------------------------------------------------
108
+
109
+ /**
110
+ * Request MIDI access via the Web MIDI API and begin listening for
111
+ * device connections and messages.
112
+ *
113
+ * @returns {Promise<MIDIAccess>}
114
+ * @throws {Error} If Web MIDI API is unavailable or access is denied
115
+ */
116
+ async initialize() {
117
+ if (!navigator.requestMIDIAccess) {
118
+ throw new Error('Web MIDI API is not supported in this browser.');
119
+ }
120
+
121
+ this.midiAccess = await navigator.requestMIDIAccess({ sysex: false });
122
+
123
+ // Register existing inputs
124
+ for (const [id, input] of this.midiAccess.inputs) {
125
+ this._registerInput(id, input);
126
+ }
127
+
128
+ // Listen for hot-plug events
129
+ this.midiAccess.onstatechange = (event) => {
130
+ this._onStateChange(event);
131
+ };
132
+
133
+ return this.midiAccess;
134
+ }
135
+
136
+ /**
137
+ * Register a MIDI input and attach the message handler.
138
+ * @param {string} id
139
+ * @param {MIDIInput} input
140
+ * @private
141
+ */
142
+ _registerInput(id, input) {
143
+ if (this.inputs.has(id)) return;
144
+
145
+ input.onmidimessage = (event) => this.onMIDIMessage(event);
146
+ this.inputs.set(id, input);
147
+ }
148
+
149
+ /**
150
+ * Handle MIDI device connect / disconnect.
151
+ * @param {MIDIConnectionEvent} event
152
+ * @private
153
+ */
154
+ _onStateChange(event) {
155
+ const port = event.port;
156
+ if (port.type !== 'input') return;
157
+
158
+ if (port.state === 'connected') {
159
+ this._registerInput(port.id, port);
160
+ } else if (port.state === 'disconnected') {
161
+ this.inputs.delete(port.id);
162
+ }
163
+ }
164
+
165
+ // -----------------------------------------------------------------------
166
+ // Default Mapping
167
+ // -----------------------------------------------------------------------
168
+
169
+ /**
170
+ * Load a default mapping modeled on a standard DJ controller layout.
171
+ *
172
+ * Channel 0 (omni):
173
+ * - Faders (CC 1-4): hue, saturation, intensity, speed
174
+ * - Knobs (CC 16-19): rot4dXW, rot4dYW, rot4dZW, dimension
175
+ * - Knobs (CC 20-22): rot4dXY, rot4dXZ, rot4dYZ
176
+ * - Pitch bend (CC 74): morphFactor
177
+ * - Mod wheel (CC 1 alt / CC 71): chaos
178
+ * - Knob (CC 23): gridDensity
179
+ *
180
+ * Notes (channel 0):
181
+ * - Pads 36-59: geometry presets 0-23
182
+ * - Note 60: system switch to faceted
183
+ * - Note 61: system switch to quantum
184
+ * - Note 62: system switch to holographic
185
+ * - Note 63: randomize all
186
+ * - Note 64: save to gallery
187
+ */
188
+ loadDefaultMapping() {
189
+ this.mappings.clear();
190
+ this.noteMappings.clear();
191
+
192
+ // -- Faders --
193
+ this.mapCC(0, 1, 'hue', { min: 0, max: 360, curve: 'linear' });
194
+ this.mapCC(0, 2, 'saturation', { min: 0, max: 1, curve: 'linear' });
195
+ this.mapCC(0, 3, 'intensity', { min: 0, max: 1, curve: 'linear' });
196
+ this.mapCC(0, 4, 'speed', { min: 0.1, max: 3, curve: 'exponential' });
197
+
198
+ // -- Knobs: 4D rotations --
199
+ this.mapCC(0, 16, 'rot4dXW', { min: 0, max: Math.PI * 2, curve: 'linear' });
200
+ this.mapCC(0, 17, 'rot4dYW', { min: 0, max: Math.PI * 2, curve: 'linear' });
201
+ this.mapCC(0, 18, 'rot4dZW', { min: 0, max: Math.PI * 2, curve: 'linear' });
202
+ this.mapCC(0, 19, 'dimension', { min: 3.0, max: 4.5, curve: 'linear' });
203
+
204
+ // -- Knobs: 3D rotations --
205
+ this.mapCC(0, 20, 'rot4dXY', { min: 0, max: Math.PI * 2, curve: 'linear' });
206
+ this.mapCC(0, 21, 'rot4dXZ', { min: 0, max: Math.PI * 2, curve: 'linear' });
207
+ this.mapCC(0, 22, 'rot4dYZ', { min: 0, max: Math.PI * 2, curve: 'linear' });
208
+
209
+ // -- Extra knobs --
210
+ this.mapCC(0, 23, 'gridDensity', { min: 4, max: 100, curve: 'logarithmic' });
211
+
212
+ // -- Pitch bend / Mod wheel --
213
+ this.mapCC(0, 74, 'morphFactor', { min: 0, max: 2, curve: 'linear' });
214
+ this.mapCC(0, 71, 'chaos', { min: 0, max: 1, curve: 'scurve' });
215
+
216
+ // -- Pads: geometry presets --
217
+ for (let i = 0; i < 24; i++) {
218
+ this.mapNote(0, 36 + i, `geometry:${i}`);
219
+ }
220
+
221
+ // -- System switch buttons --
222
+ this.mapNote(0, 60, 'system:faceted');
223
+ this.mapNote(0, 61, 'system:quantum');
224
+ this.mapNote(0, 62, 'system:holographic');
225
+ this.mapNote(0, 63, 'action:randomize');
226
+ this.mapNote(0, 64, 'action:save');
227
+ }
228
+
229
+ // -----------------------------------------------------------------------
230
+ // MIDI Learn
231
+ // -----------------------------------------------------------------------
232
+
233
+ /**
234
+ * Enter MIDI Learn mode. The next incoming CC message will be mapped
235
+ * to the specified parameter.
236
+ *
237
+ * @param {string} parameterName - VIB3 parameter name to learn
238
+ * @param {Function} [callback] - Optional callback invoked with the
239
+ * learned mapping details: callback({channel, cc, param})
240
+ */
241
+ startLearn(parameterName) {
242
+ this.learning = true;
243
+ this._learnTarget = parameterName;
244
+ }
245
+
246
+ /**
247
+ * Exit MIDI Learn mode without mapping.
248
+ */
249
+ stopLearn() {
250
+ this.learning = false;
251
+ this._learnTarget = null;
252
+ this.learnCallback = null;
253
+ }
254
+
255
+ // -----------------------------------------------------------------------
256
+ // Manual Mapping
257
+ // -----------------------------------------------------------------------
258
+
259
+ /**
260
+ * Map a MIDI Control Change to a VIB3 parameter.
261
+ *
262
+ * @param {number} channel - MIDI channel (0 = omni, 1-16 = specific)
263
+ * @param {number} cc - Control Change number (0-127)
264
+ * @param {string} param - VIB3 parameter name
265
+ * @param {CCMappingOptions} [options={}]
266
+ */
267
+ mapCC(channel, cc, param, options = {}) {
268
+ const key = `${channel}:${cc}`;
269
+ this.mappings.set(key, {
270
+ param,
271
+ channel: channel || 0,
272
+ cc,
273
+ min: options.min !== undefined ? options.min : 0,
274
+ max: options.max !== undefined ? options.max : 1,
275
+ invert: options.invert || false,
276
+ curve: options.curve || 'linear'
277
+ });
278
+ }
279
+
280
+ /**
281
+ * Map a MIDI note to an action.
282
+ *
283
+ * @param {number} channel - MIDI channel (0 = omni)
284
+ * @param {number} note - MIDI note number (0-127)
285
+ * @param {string} action - Action string (e.g. 'geometry:5', 'system:quantum',
286
+ * 'action:randomize', 'action:save')
287
+ * @param {'noteOn'|'noteOff'|'toggle'} [trigger='noteOn'] - When to fire
288
+ */
289
+ mapNote(channel, note, action, trigger = 'noteOn') {
290
+ const key = `${channel}:${note}`;
291
+ this.noteMappings.set(key, {
292
+ action,
293
+ channel: channel || 0,
294
+ note,
295
+ trigger
296
+ });
297
+ }
298
+
299
+ /**
300
+ * Remove a CC mapping.
301
+ * @param {number} channel
302
+ * @param {number} cc
303
+ */
304
+ unmapCC(channel, cc) {
305
+ this.mappings.delete(`${channel}:${cc}`);
306
+ }
307
+
308
+ /**
309
+ * Remove a note mapping.
310
+ * @param {number} channel
311
+ * @param {number} note
312
+ */
313
+ unmapNote(channel, note) {
314
+ this.noteMappings.delete(`${channel}:${note}`);
315
+ }
316
+
317
+ // -----------------------------------------------------------------------
318
+ // MIDI Message Handler
319
+ // -----------------------------------------------------------------------
320
+
321
+ /**
322
+ * Process an incoming MIDI message. Routes CC messages to parameter
323
+ * updates and note messages to actions.
324
+ *
325
+ * @param {MIDIMessageEvent} event
326
+ */
327
+ onMIDIMessage(event) {
328
+ if (this._destroyed) return;
329
+
330
+ const data = event.data;
331
+ if (!data || data.length < 2) return;
332
+
333
+ // External raw listener
334
+ if (this._onRawMessage) {
335
+ this._onRawMessage(data);
336
+ }
337
+
338
+ const status = data[0] & 0xF0;
339
+ const channel = (data[0] & 0x0F) + 1; // 1-16
340
+
341
+ switch (status) {
342
+ case 0xB0: // Control Change
343
+ this._handleCC(channel, data[1], data[2]);
344
+ break;
345
+
346
+ case 0x90: // Note On (velocity > 0)
347
+ if (data.length >= 3 && data[2] > 0) {
348
+ this._handleNoteOn(channel, data[1], data[2]);
349
+ } else {
350
+ this._handleNoteOff(channel, data[1]);
351
+ }
352
+ break;
353
+
354
+ case 0x80: // Note Off
355
+ this._handleNoteOff(channel, data[1]);
356
+ break;
357
+
358
+ case 0xE0: // Pitch Bend
359
+ if (data.length >= 3) {
360
+ // Convert 14-bit pitch bend to 0-127 range for CC processing
361
+ const pitchValue = ((data[2] << 7) | data[1]);
362
+ const normalized = Math.round(pitchValue / 16383 * 127);
363
+ this._handleCC(channel, 128, normalized); // Virtual CC 128 for pitch bend
364
+ }
365
+ break;
366
+
367
+ default:
368
+ break;
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Handle a Control Change message.
374
+ * @param {number} channel - 1-16
375
+ * @param {number} cc - 0-127
376
+ * @param {number} value - 0-127
377
+ * @private
378
+ */
379
+ _handleCC(channel, cc, value) {
380
+ // MIDI Learn mode
381
+ if (this.learning && this._learnTarget) {
382
+ this.mapCC(0, cc, this._learnTarget, { min: 0, max: 1 });
383
+
384
+ if (this.learnCallback) {
385
+ this.learnCallback({
386
+ channel,
387
+ cc,
388
+ param: this._learnTarget
389
+ });
390
+ }
391
+
392
+ this.learning = false;
393
+ this._learnTarget = null;
394
+ this.learnCallback = null;
395
+ // Fall through to also apply the value
396
+ }
397
+
398
+ // Store raw value
399
+ const rawKey = `${channel}:${cc}`;
400
+ this.lastValues.set(rawKey, value);
401
+
402
+ // Look up mapping (try channel-specific first, then omni)
403
+ const mapping = this.mappings.get(`${channel}:${cc}`)
404
+ || this.mappings.get(`0:${cc}`);
405
+
406
+ if (!mapping) return;
407
+
408
+ // Normalize CC value (0-127 -> 0-1)
409
+ let normalized = value / 127.0;
410
+
411
+ // Invert
412
+ if (mapping.invert) {
413
+ normalized = 1.0 - normalized;
414
+ }
415
+
416
+ // Apply curve
417
+ normalized = this._applyCurve(normalized, mapping.curve);
418
+
419
+ // Scale to output range
420
+ const outputValue = mapping.min + normalized * (mapping.max - mapping.min);
421
+
422
+ // Fire parameter update
423
+ if (this.updateParameter) {
424
+ this.updateParameter(mapping.param, outputValue);
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Handle a Note On message.
430
+ * @param {number} channel - 1-16
431
+ * @param {number} note - 0-127
432
+ * @param {number} velocity - 1-127
433
+ * @private
434
+ */
435
+ _handleNoteOn(channel, note, velocity) {
436
+ const mapping = this.noteMappings.get(`${channel}:${note}`)
437
+ || this.noteMappings.get(`0:${note}`);
438
+
439
+ if (!mapping) return;
440
+
441
+ if (mapping.trigger === 'toggle') {
442
+ const key = `${channel}:${note}`;
443
+ const current = this._toggleState.get(key) || false;
444
+ this._toggleState.set(key, !current);
445
+
446
+ if (!current) {
447
+ this._executeAction(mapping.action, velocity);
448
+ }
449
+ return;
450
+ }
451
+
452
+ if (mapping.trigger === 'noteOn') {
453
+ this._executeAction(mapping.action, velocity);
454
+ }
455
+ }
456
+
457
+ /**
458
+ * Handle a Note Off message.
459
+ * @param {number} channel - 1-16
460
+ * @param {number} note - 0-127
461
+ * @private
462
+ */
463
+ _handleNoteOff(channel, note) {
464
+ const mapping = this.noteMappings.get(`${channel}:${note}`)
465
+ || this.noteMappings.get(`0:${note}`);
466
+
467
+ if (!mapping) return;
468
+
469
+ if (mapping.trigger === 'noteOff') {
470
+ this._executeAction(mapping.action, 0);
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Execute a mapped action string.
476
+ *
477
+ * Action format: "type:value"
478
+ * - "geometry:N" -> Set geometry to N
479
+ * - "system:name" -> Switch to system
480
+ * - "action:randomize" -> Randomize all parameters
481
+ * - "action:save" -> Save to gallery
482
+ * - "param:name:value" -> Set arbitrary parameter
483
+ *
484
+ * @param {string} action
485
+ * @param {number} velocity - Note velocity (0-127)
486
+ * @private
487
+ */
488
+ _executeAction(action, velocity) {
489
+ const parts = action.split(':');
490
+ const type = parts[0];
491
+ const value = parts.slice(1).join(':');
492
+
493
+ switch (type) {
494
+ case 'geometry':
495
+ if (this.updateParameter) {
496
+ const geom = parseInt(value, 10);
497
+ if (!isNaN(geom) && geom >= 0 && geom <= 23) {
498
+ this.updateParameter('geometry', geom);
499
+ }
500
+ }
501
+ break;
502
+
503
+ case 'system':
504
+ if (this.updateParameter) {
505
+ this.updateParameter('system', value);
506
+ }
507
+ break;
508
+
509
+ case 'action':
510
+ if (value === 'randomize' && this.updateParameter) {
511
+ this.updateParameter('action', 'randomize');
512
+ } else if (value === 'save' && this.updateParameter) {
513
+ this.updateParameter('action', 'save');
514
+ }
515
+ break;
516
+
517
+ case 'param':
518
+ if (parts.length >= 3 && this.updateParameter) {
519
+ const paramName = parts[1];
520
+ const paramValue = parseFloat(parts[2]);
521
+ if (!isNaN(paramValue)) {
522
+ this.updateParameter(paramName, paramValue);
523
+ }
524
+ }
525
+ break;
526
+
527
+ default:
528
+ break;
529
+ }
530
+ }
531
+
532
+ // -----------------------------------------------------------------------
533
+ // Curve Functions
534
+ // -----------------------------------------------------------------------
535
+
536
+ /**
537
+ * Apply a response curve to a normalized (0-1) value.
538
+ *
539
+ * @param {number} value - Normalized input (0-1)
540
+ * @param {string} curve - Curve type
541
+ * @returns {number} Curved output (0-1)
542
+ * @private
543
+ */
544
+ _applyCurve(value, curve) {
545
+ switch (curve) {
546
+ case 'exponential':
547
+ // Quadratic curve for smoother low-end response
548
+ return value * value;
549
+
550
+ case 'logarithmic':
551
+ // Inverse quadratic for more control at high end
552
+ return Math.sqrt(value);
553
+
554
+ case 'scurve':
555
+ // S-curve (sigmoid-like) for smooth center transition
556
+ return value * value * (3.0 - 2.0 * value);
557
+
558
+ case 'linear':
559
+ default:
560
+ return value;
561
+ }
562
+ }
563
+
564
+ // -----------------------------------------------------------------------
565
+ // Serialization
566
+ // -----------------------------------------------------------------------
567
+
568
+ /**
569
+ * Export all current mappings as a JSON-serializable object.
570
+ *
571
+ * @returns {{version: number, cc: CCMapping[], notes: NoteMapping[]}}
572
+ */
573
+ exportMapping() {
574
+ return {
575
+ version: 1,
576
+ cc: Array.from(this.mappings.values()),
577
+ notes: Array.from(this.noteMappings.values())
578
+ };
579
+ }
580
+
581
+ /**
582
+ * Import mappings from a previously exported object. Replaces all
583
+ * current mappings.
584
+ *
585
+ * @param {{version: number, cc: CCMapping[], notes: NoteMapping[]}} data
586
+ * @throws {Error} If data format is invalid
587
+ */
588
+ importMapping(data) {
589
+ if (!data || typeof data !== 'object') {
590
+ throw new Error('Invalid mapping data: must be an object.');
591
+ }
592
+
593
+ if (!Array.isArray(data.cc) || !Array.isArray(data.notes)) {
594
+ throw new Error('Invalid mapping data: missing cc or notes arrays.');
595
+ }
596
+
597
+ this.mappings.clear();
598
+ this.noteMappings.clear();
599
+
600
+ for (const mapping of data.cc) {
601
+ if (mapping && typeof mapping.cc === 'number' && typeof mapping.param === 'string') {
602
+ const key = `${mapping.channel || 0}:${mapping.cc}`;
603
+ this.mappings.set(key, {
604
+ param: mapping.param,
605
+ channel: mapping.channel || 0,
606
+ cc: mapping.cc,
607
+ min: mapping.min !== undefined ? mapping.min : 0,
608
+ max: mapping.max !== undefined ? mapping.max : 1,
609
+ invert: mapping.invert || false,
610
+ curve: mapping.curve || 'linear'
611
+ });
612
+ }
613
+ }
614
+
615
+ for (const mapping of data.notes) {
616
+ if (mapping && typeof mapping.note === 'number' && typeof mapping.action === 'string') {
617
+ const key = `${mapping.channel || 0}:${mapping.note}`;
618
+ this.noteMappings.set(key, {
619
+ action: mapping.action,
620
+ channel: mapping.channel || 0,
621
+ note: mapping.note,
622
+ trigger: mapping.trigger || 'noteOn'
623
+ });
624
+ }
625
+ }
626
+ }
627
+
628
+ // -----------------------------------------------------------------------
629
+ // Utilities
630
+ // -----------------------------------------------------------------------
631
+
632
+ /**
633
+ * Get a list of all currently connected MIDI input devices.
634
+ *
635
+ * @returns {{id: string, name: string, manufacturer: string, state: string}[]}
636
+ */
637
+ getConnectedDevices() {
638
+ const devices = [];
639
+ for (const [id, input] of this.inputs) {
640
+ devices.push({
641
+ id,
642
+ name: input.name || 'Unknown Device',
643
+ manufacturer: input.manufacturer || 'Unknown',
644
+ state: input.state || 'unknown'
645
+ });
646
+ }
647
+ return devices;
648
+ }
649
+
650
+ /**
651
+ * Get all current CC mappings as an array.
652
+ * @returns {CCMapping[]}
653
+ */
654
+ getMappings() {
655
+ return Array.from(this.mappings.values());
656
+ }
657
+
658
+ /**
659
+ * Get all current note mappings as an array.
660
+ * @returns {NoteMapping[]}
661
+ */
662
+ getNoteMappings() {
663
+ return Array.from(this.noteMappings.values());
664
+ }
665
+
666
+ /**
667
+ * Register a listener for raw MIDI messages (for debugging or displays).
668
+ * @param {Function} callback - Receives Uint8Array of MIDI data
669
+ */
670
+ onRawMessage(callback) {
671
+ this._onRawMessage = callback;
672
+ }
673
+
674
+ /**
675
+ * Destroy the controller. Removes all listeners and clears state.
676
+ */
677
+ destroy() {
678
+ this._destroyed = true;
679
+
680
+ // Remove message handlers from all inputs
681
+ for (const [, input] of this.inputs) {
682
+ try {
683
+ input.onmidimessage = null;
684
+ } catch (_e) {
685
+ // Ignore
686
+ }
687
+ }
688
+
689
+ if (this.midiAccess) {
690
+ this.midiAccess.onstatechange = null;
691
+ }
692
+
693
+ this.inputs.clear();
694
+ this.mappings.clear();
695
+ this.noteMappings.clear();
696
+ this.lastValues.clear();
697
+ this._toggleState.clear();
698
+ this.midiAccess = null;
699
+ this.updateParameter = null;
700
+ this.learnCallback = null;
701
+ this._onRawMessage = null;
702
+ }
703
+ }