@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.
- package/CHANGELOG.md +118 -0
- package/DOCS/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +34 -0
- package/DOCS/CI_TESTING.md +38 -0
- package/DOCS/CLI_ONBOARDING.md +75 -0
- package/DOCS/CONTROL_REFERENCE.md +64 -0
- package/DOCS/DEV_TRACK_ANALYSIS.md +77 -0
- package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +42 -0
- package/DOCS/DEV_TRACK_SESSION_2026-01-31.md +220 -0
- package/DOCS/ENV_SETUP.md +189 -0
- package/DOCS/EXPORT_FORMATS.md +417 -0
- package/DOCS/GPU_DISPOSAL_GUIDE.md +21 -0
- package/DOCS/LICENSING_TIERS.md +275 -0
- package/DOCS/MASTER_PLAN_2026-01-31.md +570 -0
- package/DOCS/OBS_SETUP_GUIDE.md +98 -0
- package/DOCS/PROJECT_SETUP.md +66 -0
- package/DOCS/RENDERER_LIFECYCLE.md +40 -0
- package/DOCS/REPO_MANIFEST.md +121 -0
- package/DOCS/SESSION_014_PLAN.md +195 -0
- package/DOCS/SESSION_LOG_2026-01-07.md +56 -0
- package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +72 -0
- package/DOCS/SYSTEM_AUDIT_2026-01-30.md +738 -0
- package/DOCS/SYSTEM_INVENTORY.md +520 -0
- package/DOCS/TELEMETRY_EXPORTS.md +34 -0
- package/DOCS/WEBGPU_STATUS.md +38 -0
- package/DOCS/XR_BENCHMARKS.md +608 -0
- package/LICENSE +21 -0
- package/README.md +426 -0
- package/docs/.nojekyll +0 -0
- package/docs/01-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/02-hyperspatial_ego_death.html +346 -0
- package/docs/03-post_cartesian_sublime.html +346 -0
- package/docs/04-crystalline_void_meditation.html +346 -0
- package/docs/05-quantum_decoherence_ballet.html +346 -0
- package/docs/06-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/07-hyperspatial_ego_death.html +346 -0
- package/docs/08-post_cartesian_sublime.html +346 -0
- package/docs/09-crystalline_void_meditation.html +346 -0
- package/docs/10-quantum_decoherence_ballet.html +346 -0
- package/docs/11-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/12-hyperspatial_ego_death.html +346 -0
- package/docs/13-post_cartesian_sublime.html +346 -0
- package/docs/index.html +794 -0
- package/docs/test-hub.html +441 -0
- package/docs/url-state.js +102 -0
- package/docs/vib3-exports/01-quantum-quantum-tetrahedron-lattice.html +489 -0
- package/docs/vib3-exports/02-quantum-quantum-hypersphere-matrix.html +489 -0
- package/docs/vib3-exports/03-quantum-quantum-hypertetra-fractal.html +489 -0
- package/docs/vib3-exports/04-faceted-faceted-crystal-structure.html +407 -0
- package/docs/vib3-exports/05-faceted-faceted-klein-bottle.html +407 -0
- package/docs/vib3-exports/06-faceted-faceted-hypertetra-torus.html +407 -0
- package/docs/vib3-exports/07-holographic-holographic-wave-field.html +457 -0
- package/docs/vib3-exports/08-holographic-holographic-hypersphere-sphere.html +457 -0
- package/docs/vib3-exports/09-holographic-holographic-hypertetra-crystal.html +457 -0
- package/docs/vib3-exports/index.html +238 -0
- package/docs/webgpu-live.html +702 -0
- package/package.json +367 -0
- package/src/advanced/AIPresetGenerator.js +777 -0
- package/src/advanced/MIDIController.js +703 -0
- package/src/advanced/OffscreenWorker.js +1051 -0
- package/src/advanced/WebGPUCompute.js +1051 -0
- package/src/advanced/WebXRRenderer.js +680 -0
- package/src/agent/cli/AgentCLI.js +615 -0
- package/src/agent/cli/index.js +14 -0
- package/src/agent/index.js +73 -0
- package/src/agent/mcp/MCPServer.js +950 -0
- package/src/agent/mcp/index.js +9 -0
- package/src/agent/mcp/tools.js +548 -0
- package/src/agent/telemetry/EventStream.js +669 -0
- package/src/agent/telemetry/Instrumentation.js +618 -0
- package/src/agent/telemetry/TelemetryExporters.js +427 -0
- package/src/agent/telemetry/TelemetryService.js +464 -0
- package/src/agent/telemetry/index.js +52 -0
- package/src/benchmarks/BenchmarkRunner.js +381 -0
- package/src/benchmarks/MetricsCollector.js +299 -0
- package/src/benchmarks/index.js +9 -0
- package/src/benchmarks/scenes.js +259 -0
- package/src/cli/index.js +675 -0
- package/src/config/ApiConfig.js +88 -0
- package/src/core/CanvasManager.js +217 -0
- package/src/core/ErrorReporter.js +117 -0
- package/src/core/ParameterMapper.js +333 -0
- package/src/core/Parameters.js +396 -0
- package/src/core/RendererContracts.js +200 -0
- package/src/core/UnifiedResourceManager.js +370 -0
- package/src/core/VIB3Engine.js +636 -0
- package/src/core/renderers/FacetedRendererAdapter.js +32 -0
- package/src/core/renderers/HolographicRendererAdapter.js +29 -0
- package/src/core/renderers/QuantumRendererAdapter.js +29 -0
- package/src/core/renderers/RendererLifecycleManager.js +63 -0
- package/src/creative/ColorPresetsSystem.js +980 -0
- package/src/creative/ParameterTimeline.js +1061 -0
- package/src/creative/PostProcessingPipeline.js +1113 -0
- package/src/creative/TransitionAnimator.js +683 -0
- package/src/export/CSSExporter.js +226 -0
- package/src/export/CardGeneratorBase.js +279 -0
- package/src/export/ExportManager.js +580 -0
- package/src/export/FacetedCardGenerator.js +279 -0
- package/src/export/HolographicCardGenerator.js +543 -0
- package/src/export/LottieExporter.js +552 -0
- package/src/export/QuantumCardGenerator.js +315 -0
- package/src/export/SVGExporter.js +519 -0
- package/src/export/ShaderExporter.js +903 -0
- package/src/export/TradingCardGenerator.js +3055 -0
- package/src/export/TradingCardManager.js +181 -0
- package/src/export/VIB3PackageExporter.js +559 -0
- package/src/export/index.js +14 -0
- package/src/export/systems/TradingCardSystemFaceted.js +494 -0
- package/src/export/systems/TradingCardSystemHolographic.js +452 -0
- package/src/export/systems/TradingCardSystemQuantum.js +411 -0
- package/src/faceted/FacetedSystem.js +963 -0
- package/src/features/CollectionManager.js +433 -0
- package/src/gallery/CollectionManager.js +240 -0
- package/src/gallery/GallerySystem.js +485 -0
- package/src/geometry/GeometryFactory.js +314 -0
- package/src/geometry/GeometryLibrary.js +72 -0
- package/src/geometry/buffers/BufferBuilder.js +338 -0
- package/src/geometry/buffers/index.js +18 -0
- package/src/geometry/generators/Crystal.js +420 -0
- package/src/geometry/generators/Fractal.js +298 -0
- package/src/geometry/generators/KleinBottle.js +197 -0
- package/src/geometry/generators/Sphere.js +192 -0
- package/src/geometry/generators/Tesseract.js +160 -0
- package/src/geometry/generators/Tetrahedron.js +225 -0
- package/src/geometry/generators/Torus.js +304 -0
- package/src/geometry/generators/Wave.js +341 -0
- package/src/geometry/index.js +142 -0
- package/src/geometry/warp/HypersphereCore.js +211 -0
- package/src/geometry/warp/HypertetraCore.js +386 -0
- package/src/geometry/warp/index.js +57 -0
- package/src/holograms/HolographicVisualizer.js +1073 -0
- package/src/holograms/RealHolographicSystem.js +966 -0
- package/src/holograms/variantRegistry.js +69 -0
- package/src/integrations/FigmaPlugin.js +854 -0
- package/src/integrations/OBSMode.js +754 -0
- package/src/integrations/ThreeJsPackage.js +660 -0
- package/src/integrations/TouchDesignerExport.js +552 -0
- package/src/integrations/frameworks/Vib3React.js +591 -0
- package/src/integrations/frameworks/Vib3Svelte.js +654 -0
- package/src/integrations/frameworks/Vib3Vue.js +628 -0
- package/src/llm/LLMParameterInterface.js +240 -0
- package/src/llm/LLMParameterUI.js +577 -0
- package/src/math/Mat4x4.js +708 -0
- package/src/math/Projection.js +341 -0
- package/src/math/Rotor4D.js +637 -0
- package/src/math/Vec4.js +476 -0
- package/src/math/constants.js +164 -0
- package/src/math/index.js +68 -0
- package/src/math/projections.js +54 -0
- package/src/math/rotations.js +196 -0
- package/src/quantum/QuantumEngine.js +906 -0
- package/src/quantum/QuantumVisualizer.js +1103 -0
- package/src/reactivity/ReactivityConfig.js +499 -0
- package/src/reactivity/ReactivityManager.js +586 -0
- package/src/reactivity/SpatialInputSystem.js +1783 -0
- package/src/reactivity/index.js +93 -0
- package/src/render/CommandBuffer.js +465 -0
- package/src/render/MultiCanvasBridge.js +340 -0
- package/src/render/RenderCommand.js +514 -0
- package/src/render/RenderResourceRegistry.js +523 -0
- package/src/render/RenderState.js +552 -0
- package/src/render/RenderTarget.js +512 -0
- package/src/render/ShaderLoader.js +253 -0
- package/src/render/ShaderProgram.js +599 -0
- package/src/render/UnifiedRenderBridge.js +496 -0
- package/src/render/backends/WebGLBackend.js +1108 -0
- package/src/render/backends/WebGPUBackend.js +1409 -0
- package/src/render/commands/CommandBufferExecutor.js +607 -0
- package/src/render/commands/RenderCommandBuffer.js +661 -0
- package/src/render/commands/index.js +17 -0
- package/src/render/index.js +367 -0
- package/src/scene/Disposable.js +498 -0
- package/src/scene/MemoryPool.js +618 -0
- package/src/scene/Node4D.js +697 -0
- package/src/scene/ResourceManager.js +599 -0
- package/src/scene/Scene4D.js +540 -0
- package/src/scene/index.js +98 -0
- package/src/schemas/error.schema.json +84 -0
- package/src/schemas/extension.schema.json +88 -0
- package/src/schemas/index.js +214 -0
- package/src/schemas/parameters.schema.json +142 -0
- package/src/schemas/tool-pack.schema.json +44 -0
- package/src/schemas/tool-response.schema.json +127 -0
- package/src/shaders/common/fullscreen.vert.glsl +5 -0
- package/src/shaders/common/fullscreen.vert.wgsl +17 -0
- package/src/shaders/common/geometry24.glsl +65 -0
- package/src/shaders/common/geometry24.wgsl +54 -0
- package/src/shaders/common/rotation4d.glsl +85 -0
- package/src/shaders/common/rotation4d.wgsl +86 -0
- package/src/shaders/common/uniforms.glsl +44 -0
- package/src/shaders/common/uniforms.wgsl +48 -0
- package/src/shaders/faceted/faceted.frag.glsl +129 -0
- package/src/shaders/faceted/faceted.frag.wgsl +164 -0
- package/src/shaders/holographic/holographic.frag.glsl +406 -0
- package/src/shaders/holographic/holographic.frag.wgsl +185 -0
- package/src/shaders/quantum/quantum.frag.glsl +513 -0
- package/src/shaders/quantum/quantum.frag.wgsl +361 -0
- package/src/testing/ParallelTestFramework.js +519 -0
- package/src/testing/__snapshots__/exportFormats.test.js.snap +24 -0
- package/src/testing/exportFormats.test.js +8 -0
- package/src/testing/projections.test.js +14 -0
- package/src/testing/rotations.test.js +37 -0
- package/src/ui/InteractivityMenu.js +516 -0
- package/src/ui/StatusManager.js +96 -0
- package/src/ui/adaptive/renderers/webgpu/BufferLayout.ts +252 -0
- package/src/ui/adaptive/renderers/webgpu/PolytopeInstanceBuffer.ts +144 -0
- package/src/ui/adaptive/renderers/webgpu/TripleBufferedUniform.ts +170 -0
- package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +735 -0
- package/src/ui/adaptive/renderers/webgpu/index.ts +112 -0
- package/src/variations/VariationManager.js +431 -0
- package/src/viewer/AudioReactivity.js +505 -0
- package/src/viewer/CardBending.js +481 -0
- package/src/viewer/GalleryUI.js +832 -0
- package/src/viewer/ReactivityManager.js +590 -0
- package/src/viewer/TradingCardExporter.js +600 -0
- package/src/viewer/ViewerPortal.js +374 -0
- package/src/viewer/index.js +12 -0
- package/src/wasm/WasmLoader.js +296 -0
- package/src/wasm/index.js +132 -0
- package/tools/agentic/mcpTools.js +88 -0
- package/tools/cli/agent-cli.js +92 -0
- package/tools/export/formats.js +24 -0
- package/tools/math/rotation-baseline.mjs +64 -0
- package/tools/shader-sync-verify.js +937 -0
- package/tools/telemetry/manifestPipeline.js +141 -0
- package/tools/telemetry/telemetryEvents.js +35 -0
- package/types/adaptive-sdk.d.ts +185 -0
- package/types/advanced/AIPresetGenerator.d.ts +81 -0
- package/types/advanced/MIDIController.d.ts +100 -0
- package/types/advanced/OffscreenWorker.d.ts +82 -0
- package/types/advanced/WebGPUCompute.d.ts +52 -0
- package/types/advanced/WebXRRenderer.d.ts +77 -0
- package/types/advanced/index.d.ts +46 -0
- package/types/core/ErrorReporter.d.ts +50 -0
- package/types/core/VIB3Engine.d.ts +204 -0
- package/types/creative/ColorPresetsSystem.d.ts +91 -0
- package/types/creative/ParameterTimeline.d.ts +74 -0
- package/types/creative/PostProcessingPipeline.d.ts +109 -0
- package/types/creative/TransitionAnimator.d.ts +71 -0
- package/types/creative/index.d.ts +35 -0
- package/types/integrations/FigmaPlugin.d.ts +46 -0
- package/types/integrations/OBSMode.d.ts +74 -0
- package/types/integrations/ThreeJsPackage.d.ts +62 -0
- package/types/integrations/TouchDesignerExport.d.ts +36 -0
- package/types/integrations/Vib3React.d.ts +74 -0
- package/types/integrations/Vib3Svelte.d.ts +63 -0
- package/types/integrations/Vib3Vue.d.ts +55 -0
- package/types/integrations/index.d.ts +52 -0
- package/types/reactivity/SpatialInputSystem.d.ts +173 -0
- package/types/reactivity/index.d.ts +394 -0
- package/types/render/CommandBuffer.d.ts +169 -0
- package/types/render/RenderCommand.d.ts +312 -0
- package/types/render/RenderState.d.ts +279 -0
- package/types/render/RenderTarget.d.ts +254 -0
- package/types/render/ShaderProgram.d.ts +277 -0
- package/types/render/UnifiedRenderBridge.d.ts +143 -0
- package/types/render/WebGLBackend.d.ts +168 -0
- package/types/render/WebGPUBackend.d.ts +186 -0
- 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
|
+
}
|