mudra-skills 1.0.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 (149) hide show
  1. package/.claude-plugin/plugin.json +19 -0
  2. package/CLAUDE.md +35 -0
  3. package/bin/install.js +25 -0
  4. package/package.json +28 -0
  5. package/skills/mudra-master/SKILL.md +202 -0
  6. package/skills/mudra-master/mudra-preview/SKILL.md +151 -0
  7. package/skills/mudra-master/mudra-preview/assets/ar-menu.html +382 -0
  8. package/skills/mudra-master/mudra-preview/assets/document-scroller.html +468 -0
  9. package/skills/mudra-master/mudra-preview/assets/drum-machine.html +788 -0
  10. package/skills/mudra-master/mudra-preview/assets/emg-visualizer.html +390 -0
  11. package/skills/mudra-master/mudra-preview/assets/generative-art.html +408 -0
  12. package/skills/mudra-master/mudra-preview/assets/gesture-assistant.html +342 -0
  13. package/skills/mudra-master/mudra-preview/assets/gesture-speech.html +444 -0
  14. package/skills/mudra-master/mudra-preview/assets/hands-free-desktop.html +376 -0
  15. package/skills/mudra-master/mudra-preview/assets/model-rotator.html +365 -0
  16. package/skills/mudra-master/mudra-preview/assets/mudra-duel.html +3200 -0
  17. package/skills/mudra-master/mudra-preview/assets/mudra-monitor.html +1451 -0
  18. package/skills/mudra-master/mudra-preview/assets/mudra-ultimate-template.html +891 -0
  19. package/skills/mudra-master/mudra-preview/assets/music-sequencer.html +455 -0
  20. package/skills/mudra-master/mudra-preview/assets/neural-pong.html +380 -0
  21. package/skills/mudra-master/mudra-preview/assets/neural-snake.html +364 -0
  22. package/skills/mudra-master/mudra-preview/assets/presentation-controller.html +345 -0
  23. package/skills/mudra-master/mudra-preview/assets/pressure-painter.html +285 -0
  24. package/skills/mudra-master/mudra-preview/assets/runner.html +1812 -0
  25. package/skills/mudra-master/mudra-preview/assets/smart-home.html +461 -0
  26. package/skills/mudra-master/mudra-preview/assets/space-invaders.html +1154 -0
  27. package/skills/mudra-master/mudra-preview/assets/waterful-ring-toss.html +1624 -0
  28. package/skills/mudra-master/mudra-preview/references/agent_protocol.json +646 -0
  29. package/skills/mudra-master/mudra-preview/references/promt.md +16829 -0
  30. package/skills/mudra-master/mudra-xr/SKILL.md +245 -0
  31. package/skills/mudra-master/mudra-xr/assets/demos/3dgs-walkthrough.html +242 -0
  32. package/skills/mudra-master/mudra-xr/assets/demos/aisimulator.html +792 -0
  33. package/skills/mudra-master/mudra-xr/assets/demos/balloonpop.html +422 -0
  34. package/skills/mudra-master/mudra-xr/assets/demos/ballpit.html +284 -0
  35. package/skills/mudra-master/mudra-xr/assets/demos/drone.html +41 -0
  36. package/skills/mudra-master/mudra-xr/assets/demos/gemini-icebreakers.html +338 -0
  37. package/skills/mudra-master/mudra-xr/assets/demos/gemini-xrobject.html +302 -0
  38. package/skills/mudra-master/mudra-xr/assets/demos/math3d.html +188 -0
  39. package/skills/mudra-master/mudra-xr/assets/demos/measure.html +247 -0
  40. package/skills/mudra-master/mudra-xr/assets/demos/occlusion.html +251 -0
  41. package/skills/mudra-master/mudra-xr/assets/demos/rain.html +498 -0
  42. package/skills/mudra-master/mudra-xr/assets/demos/screenwiper.html +432 -0
  43. package/skills/mudra-master/mudra-xr/assets/demos/splash.html +519 -0
  44. package/skills/mudra-master/mudra-xr/assets/demos/webcam_gestures.html +253 -0
  45. package/skills/mudra-master/mudra-xr/assets/demos/xremoji.html +833 -0
  46. package/skills/mudra-master/mudra-xr/assets/demos/xrpoet.html +151 -0
  47. package/skills/mudra-master/mudra-xr/assets/samples/depthmap.html +266 -0
  48. package/skills/mudra-master/mudra-xr/assets/samples/depthmesh.html +100 -0
  49. package/skills/mudra-master/mudra-xr/assets/samples/game_rps.html +1099 -0
  50. package/skills/mudra-master/mudra-xr/assets/samples/gestures_custom.html +467 -0
  51. package/skills/mudra-master/mudra-xr/assets/samples/gestures_heuristic.html +256 -0
  52. package/skills/mudra-master/mudra-xr/assets/samples/lighting.html +163 -0
  53. package/skills/mudra-master/mudra-xr/assets/samples/mesh_detection.html +46 -0
  54. package/skills/mudra-master/mudra-xr/assets/samples/modelviewer.html +183 -0
  55. package/skills/mudra-master/mudra-xr/assets/samples/paint.html +133 -0
  56. package/skills/mudra-master/mudra-xr/assets/samples/planar-vst.html +45 -0
  57. package/skills/mudra-master/mudra-xr/assets/samples/reticle.html +95 -0
  58. package/skills/mudra-master/mudra-xr/assets/samples/skybox_agent.html +384 -0
  59. package/skills/mudra-master/mudra-xr/assets/samples/sound.html +390 -0
  60. package/skills/mudra-master/mudra-xr/assets/samples/ui.html +496 -0
  61. package/skills/mudra-master/mudra-xr/assets/samples/virtual-screens.html +808 -0
  62. package/skills/mudra-master/mudra-xr/assets/templates/0_basic.html +64 -0
  63. package/skills/mudra-master/mudra-xr/assets/templates/1_ui.html +107 -0
  64. package/skills/mudra-master/mudra-xr/assets/templates/2_hands.html +183 -0
  65. package/skills/mudra-master/mudra-xr/assets/templates/3_depth.html +89 -0
  66. package/skills/mudra-master/mudra-xr/assets/templates/4_stereo.html +84 -0
  67. package/skills/mudra-master/mudra-xr/assets/templates/5_camera.html +117 -0
  68. package/skills/mudra-master/mudra-xr/assets/templates/6_ai.html +154 -0
  69. package/skills/mudra-master/mudra-xr/assets/templates/7_ai_live.html +253 -0
  70. package/skills/mudra-master/mudra-xr/assets/templates/8_objects.html +95 -0
  71. package/skills/mudra-master/mudra-xr/assets/templates/9_xr-toggle.html +90 -0
  72. package/skills/mudra-master/mudra-xr/assets/templates/heuristic_hand_gestures.html +102 -0
  73. package/skills/mudra-master/mudra-xr/assets/templates/meshes.html +47 -0
  74. package/skills/mudra-master/mudra-xr/assets/templates/planes.html +47 -0
  75. package/skills/mudra-master/mudra-xr/assets/templates/uikit.html +187 -0
  76. package/skills/mudra-master/mudra-xr/references/agent_protocol.json +878 -0
  77. package/skills/mudra-master/mudra-xr/references/promt.md +1516 -0
  78. package/skills/mudra-preview/SKILL.md +151 -0
  79. package/skills/mudra-preview/assets/ar-menu.html +382 -0
  80. package/skills/mudra-preview/assets/document-scroller.html +468 -0
  81. package/skills/mudra-preview/assets/drum-machine.html +788 -0
  82. package/skills/mudra-preview/assets/emg-visualizer.html +390 -0
  83. package/skills/mudra-preview/assets/generative-art.html +408 -0
  84. package/skills/mudra-preview/assets/gesture-assistant.html +342 -0
  85. package/skills/mudra-preview/assets/gesture-speech.html +444 -0
  86. package/skills/mudra-preview/assets/hands-free-desktop.html +376 -0
  87. package/skills/mudra-preview/assets/model-rotator.html +365 -0
  88. package/skills/mudra-preview/assets/mudra-duel.html +3200 -0
  89. package/skills/mudra-preview/assets/mudra-monitor.html +1451 -0
  90. package/skills/mudra-preview/assets/mudra-ultimate-template.html +891 -0
  91. package/skills/mudra-preview/assets/music-sequencer.html +455 -0
  92. package/skills/mudra-preview/assets/neural-pong.html +380 -0
  93. package/skills/mudra-preview/assets/neural-snake.html +364 -0
  94. package/skills/mudra-preview/assets/presentation-controller.html +345 -0
  95. package/skills/mudra-preview/assets/pressure-painter.html +285 -0
  96. package/skills/mudra-preview/assets/runner.html +1812 -0
  97. package/skills/mudra-preview/assets/smart-home.html +461 -0
  98. package/skills/mudra-preview/assets/space-invaders.html +1154 -0
  99. package/skills/mudra-preview/assets/waterful-ring-toss.html +1624 -0
  100. package/skills/mudra-preview/references/agent_protocol.json +646 -0
  101. package/skills/mudra-preview/references/promt.md +16829 -0
  102. package/skills/mudra-xr/SKILL.md +245 -0
  103. package/skills/mudra-xr/assets/demos/3dgs-walkthrough.html +242 -0
  104. package/skills/mudra-xr/assets/demos/aisimulator.html +792 -0
  105. package/skills/mudra-xr/assets/demos/balloonpop.html +422 -0
  106. package/skills/mudra-xr/assets/demos/ballpit.html +284 -0
  107. package/skills/mudra-xr/assets/demos/drone.html +41 -0
  108. package/skills/mudra-xr/assets/demos/gemini-icebreakers.html +338 -0
  109. package/skills/mudra-xr/assets/demos/gemini-xrobject.html +302 -0
  110. package/skills/mudra-xr/assets/demos/math3d.html +188 -0
  111. package/skills/mudra-xr/assets/demos/measure.html +247 -0
  112. package/skills/mudra-xr/assets/demos/occlusion.html +251 -0
  113. package/skills/mudra-xr/assets/demos/rain.html +498 -0
  114. package/skills/mudra-xr/assets/demos/screenwiper.html +432 -0
  115. package/skills/mudra-xr/assets/demos/splash.html +519 -0
  116. package/skills/mudra-xr/assets/demos/webcam_gestures.html +253 -0
  117. package/skills/mudra-xr/assets/demos/xremoji.html +833 -0
  118. package/skills/mudra-xr/assets/demos/xrpoet.html +151 -0
  119. package/skills/mudra-xr/assets/samples/depthmap.html +266 -0
  120. package/skills/mudra-xr/assets/samples/depthmesh.html +100 -0
  121. package/skills/mudra-xr/assets/samples/game_rps.html +1099 -0
  122. package/skills/mudra-xr/assets/samples/gestures_custom.html +467 -0
  123. package/skills/mudra-xr/assets/samples/gestures_heuristic.html +256 -0
  124. package/skills/mudra-xr/assets/samples/lighting.html +163 -0
  125. package/skills/mudra-xr/assets/samples/mesh_detection.html +46 -0
  126. package/skills/mudra-xr/assets/samples/modelviewer.html +183 -0
  127. package/skills/mudra-xr/assets/samples/paint.html +133 -0
  128. package/skills/mudra-xr/assets/samples/planar-vst.html +45 -0
  129. package/skills/mudra-xr/assets/samples/reticle.html +95 -0
  130. package/skills/mudra-xr/assets/samples/skybox_agent.html +384 -0
  131. package/skills/mudra-xr/assets/samples/sound.html +390 -0
  132. package/skills/mudra-xr/assets/samples/ui.html +496 -0
  133. package/skills/mudra-xr/assets/samples/virtual-screens.html +808 -0
  134. package/skills/mudra-xr/assets/templates/0_basic.html +64 -0
  135. package/skills/mudra-xr/assets/templates/1_ui.html +107 -0
  136. package/skills/mudra-xr/assets/templates/2_hands.html +183 -0
  137. package/skills/mudra-xr/assets/templates/3_depth.html +89 -0
  138. package/skills/mudra-xr/assets/templates/4_stereo.html +84 -0
  139. package/skills/mudra-xr/assets/templates/5_camera.html +117 -0
  140. package/skills/mudra-xr/assets/templates/6_ai.html +154 -0
  141. package/skills/mudra-xr/assets/templates/7_ai_live.html +253 -0
  142. package/skills/mudra-xr/assets/templates/8_objects.html +95 -0
  143. package/skills/mudra-xr/assets/templates/9_xr-toggle.html +90 -0
  144. package/skills/mudra-xr/assets/templates/heuristic_hand_gestures.html +102 -0
  145. package/skills/mudra-xr/assets/templates/meshes.html +47 -0
  146. package/skills/mudra-xr/assets/templates/planes.html +47 -0
  147. package/skills/mudra-xr/assets/templates/uikit.html +187 -0
  148. package/skills/mudra-xr/references/agent_protocol.json +878 -0
  149. package/skills/mudra-xr/references/promt.md +1516 -0
@@ -0,0 +1,455 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Neural Music Sequencer — Mudra Studio</title>
7
+ <style>
8
+ @media (prefers-reduced-motion: reduce) {
9
+ *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
10
+ }
11
+
12
+ :root {
13
+ --bg: #ffffff; --card: #f1f5f9; --primary: #0d9488; --accent: #14b8a6;
14
+ --text: #0f172a; --text-secondary: #475569; --success: #16a34a;
15
+ --warning: #ca8a04; --error: #dc2626;
16
+ --border: rgba(15,23,42,0.12); --surface: rgba(0,0,0,0.04);
17
+ }
18
+
19
+ * { margin: 0; padding: 0; box-sizing: border-box; }
20
+ body { background: var(--bg); color: var(--text); font-family: 'Poppins', system-ui, sans-serif; overflow: hidden; height: 100vh; display: flex; flex-direction: column; }
21
+
22
+ .header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; background: var(--card); border-bottom: 1px solid var(--border); z-index: 100; flex-shrink: 0; gap: 8px; flex-wrap: wrap; }
23
+ .header-left, .header-right { display: flex; align-items: center; gap: 10px; }
24
+ .brand { font-weight: 700; font-size: 14px; } .brand span { color: var(--primary); }
25
+ .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--warning); box-shadow: 0 0 0 3px rgba(234,179,8,0.15); transition: all 150ms; }
26
+ .dot.live { background: var(--success); box-shadow: 0 0 0 3px rgba(34,197,94,0.18); }
27
+ .conn-label { font-size: 12px; color: var(--text-secondary); }
28
+
29
+ .btn { background: var(--surface); border: 1px solid var(--border); color: var(--text-secondary); padding: 5px 12px; border-radius: 8px; font-size: 12px; cursor: pointer; font-family: inherit; transition: border-color 150ms, color 150ms; white-space: nowrap; }
30
+ .btn:hover { border-color: var(--primary); color: var(--primary); }
31
+ .btn.primary-btn { background: var(--primary); color: #072f33; border-color: transparent; font-weight: 600; }
32
+ .btn.primary-btn:hover { opacity: 0.9; }
33
+
34
+ /* Transport bar */
35
+ .transport { display: flex; align-items: center; gap: 14px; padding: 10px 16px; background: var(--card); border-bottom: 1px solid var(--border); flex-shrink: 0; flex-wrap: wrap; }
36
+ .play-btn { width: 38px; height: 38px; border-radius: 50%; border: 2px solid var(--primary); background: transparent; color: var(--primary); font-size: 15px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 150ms; flex-shrink: 0; }
37
+ .play-btn:hover { background: rgba(119,234,233,0.1); }
38
+ .play-btn.playing { background: var(--primary); color: #072f33; }
39
+ .bpm-display { font-family: 'JetBrains Mono', monospace; font-size: 20px; font-weight: 700; color: var(--primary); min-width: 75px; }
40
+ .bpm-label { font-size: 10px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 1px; }
41
+ .meter { display: flex; align-items: center; gap: 8px; margin-left: auto; }
42
+ .meter-label { font-size: 12px; color: var(--text-secondary); }
43
+ .meter-bar { width: 80px; height: 8px; background: var(--surface); border-radius: 999px; overflow: hidden; border: 1px solid var(--border); }
44
+ .meter-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--primary)); border-radius: 999px; transition: width 75ms linear; width: 50%; }
45
+ .meter-val { font-size: 12px; color: var(--text-secondary); min-width: 32px; text-align: right; font-variant-numeric: tabular-nums; }
46
+
47
+ /* Sequencer grid */
48
+ .sequencer { flex: 1; display: flex; flex-direction: column; padding: 16px; gap: 6px; overflow: auto; justify-content: center; }
49
+ .row { display: flex; align-items: center; gap: 0; }
50
+ .row-label { width: 60px; font-size: 12px; font-weight: 600; color: var(--text-secondary); text-align: right; padding-right: 10px; flex-shrink: 0; }
51
+ .cells { display: flex; gap: 3px; flex: 1; }
52
+ .cell { flex: 1; min-width: 24px; aspect-ratio: 1; background: var(--surface); border: 1px solid var(--border); border-radius: 5px; cursor: pointer; transition: all 100ms; position: relative; }
53
+ .cell.on { background: rgba(119,234,233,0.2); border-color: var(--primary); }
54
+ .cell.cursor { box-shadow: 0 0 0 2px var(--accent); z-index: 1; }
55
+ .cell.playhead { box-shadow: inset 0 0 10px rgba(119,234,233,0.3); }
56
+ .cell.on.playhead { background: var(--primary); border-color: var(--primary); box-shadow: 0 0 10px rgba(119,234,233,0.4); }
57
+ .cell.beat4 { margin-left: 5px; }
58
+
59
+ /* Bottom bar */
60
+ .bottom-bar { display: flex; align-items: center; justify-content: center; gap: 14px; padding: 10px 16px; background: var(--card); border-top: 1px solid var(--border); z-index: 100; flex-shrink: 0; flex-wrap: wrap; }
61
+ .hint { font-size: 11px; color: var(--text-secondary); opacity: 0.6; }
62
+
63
+ .mudra-badge { position: fixed; bottom: 12px; right: 12px; font-size: 11px; color: var(--text-secondary); opacity: 0.7; letter-spacing: 0.02em; pointer-events: none; z-index: 200; }
64
+
65
+ @media (max-width: 640px) {
66
+ .header { padding: 8px 10px; }
67
+ .transport { padding: 8px 10px; gap: 8px; }
68
+ .sequencer { padding: 10px; gap: 4px; }
69
+ .row-label { width: 42px; font-size: 10px; padding-right: 6px; }
70
+ .cell { min-width: 16px; border-radius: 3px; }
71
+ .bpm-display { font-size: 16px; }
72
+ .meter-bar { width: 50px; }
73
+ .bottom-bar { gap: 8px; padding: 8px 10px; }
74
+ .hint { display: none; }
75
+ }
76
+ </style>
77
+ </head>
78
+ <body>
79
+
80
+ <div class="header">
81
+ <div class="header-left">
82
+ <div class="brand"><span>Mudra</span> Neural Sequencer</div>
83
+ <span class="dot" id="dot"></span>
84
+ <span class="conn-label" id="connLabel">Connecting&hellip;</span>
85
+ </div>
86
+ <div class="header-right">
87
+ <a href="/gallery" class="btn" style="text-decoration:none;">&#8592; Gallery</a>
88
+ <button class="btn" id="simTap">Simulate Tap</button>
89
+ </div>
90
+ </div>
91
+
92
+ <div class="transport">
93
+ <button class="play-btn" id="playBtn" title="Play / Pause">&#9654;</button>
94
+ <div>
95
+ <div class="bpm-display" id="bpmDisplay">120 BPM</div>
96
+ <div class="bpm-label">Tempo</div>
97
+ </div>
98
+ <div class="meter">
99
+ <span class="meter-label">Velocity</span>
100
+ <div class="meter-bar"><div class="meter-fill" id="velFill"></div></div>
101
+ <span class="meter-val" id="velVal">50%</span>
102
+ </div>
103
+ </div>
104
+
105
+ <div class="sequencer" id="sequencer"></div>
106
+
107
+ <div class="bottom-bar">
108
+ <span class="hint">Space = toggle step &middot; Enter = play/pause &middot; Arrows = move cursor &middot; [ ] velocity &middot; C = clear row</span>
109
+ </div>
110
+
111
+ <div class="mudra-badge">Created by Mudra</div>
112
+
113
+ <script>
114
+ /* ── Mudra Protocol Connection (v2) ────────────────────────── */
115
+ const WS_URL = 'ws://127.0.0.1:8766';
116
+ const SIGNALS = ['gesture', 'navigation', 'pressure'];
117
+
118
+ let ws, reconnectTimer, simTimer;
119
+ let wsOpen = false, deviceReady = false, simulating = true;
120
+ const bus = {};
121
+
122
+ function on(ev, fn) { (bus[ev] ??= []).push(fn); }
123
+ function emit(ev, d) { (bus[ev] || []).forEach(fn => fn(d)); }
124
+ function send(p) { if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify(p)); }
125
+
126
+ function connect() {
127
+ try {
128
+ ws = new WebSocket(WS_URL);
129
+ ws.onopen = () => {
130
+ wsOpen = true; stopSim(); updateStatus();
131
+ SIGNALS.forEach(s => send({ command: 'subscribe', signal: s }));
132
+ };
133
+ ws.onmessage = (e) => {
134
+ try {
135
+ const msg = JSON.parse(e.data);
136
+ if (msg.type === 'connection_status') {
137
+ deviceReady = msg.data?.status === 'connected';
138
+ updateStatus(); return;
139
+ }
140
+ emit(msg.type, msg.data);
141
+ } catch {}
142
+ };
143
+ ws.onclose = () => {
144
+ wsOpen = false; deviceReady = false; updateStatus();
145
+ startSim();
146
+ clearTimeout(reconnectTimer);
147
+ reconnectTimer = setTimeout(connect, 1500);
148
+ };
149
+ ws.onerror = () => ws?.close();
150
+ } catch {
151
+ wsOpen = false; startSim();
152
+ clearTimeout(reconnectTimer);
153
+ reconnectTimer = setTimeout(connect, 1500);
154
+ }
155
+ }
156
+
157
+ function triggerGesture(type) {
158
+ if (wsOpen) send({ command: 'trigger_gesture', data: { type } });
159
+ else emit('gesture', { type, confidence: 0.95, timestamp: Date.now() });
160
+ }
161
+
162
+ /* ── Simulation (when Companion not reachable) ─────────────── */
163
+ function startSim() {
164
+ if (simTimer) return;
165
+ simulating = true;
166
+ let t = 0;
167
+ simTimer = setInterval(() => {
168
+ t += 0.05;
169
+ /* simulate pressure for velocity */
170
+ const n = Math.max(0, Math.min(1, 0.4 + 0.25 * Math.sin(t * 0.6) + (Math.random() - 0.5) * 0.1));
171
+ emit('pressure', { value: Math.round(n * 100), normalized: n, timestamp: Date.now() });
172
+ /* simulate navigation */
173
+ if (Math.random() < 0.04) {
174
+ emit('navigation', { delta_x: Math.round((Math.random() - 0.5) * 6), delta_y: Math.round((Math.random() - 0.5) * 4), timestamp: Date.now() });
175
+ }
176
+ /* occasional gesture */
177
+ if (Math.random() < 0.005) {
178
+ const g = ['tap', 'double_tap', 'twist', 'double_twist'];
179
+ emit('gesture', { type: g[Math.floor(Math.random() * g.length)], confidence: 0.7 + Math.random() * 0.3, timestamp: Date.now() });
180
+ }
181
+ }, 60);
182
+ }
183
+
184
+ function stopSim() { clearInterval(simTimer); simTimer = null; simulating = false; }
185
+
186
+ /* ── Status UI ─────────────────────────────────────────────── */
187
+ const dotEl = document.getElementById('dot');
188
+ const connEl = document.getElementById('connLabel');
189
+
190
+ function updateStatus() {
191
+ if (wsOpen && deviceReady) { dotEl.className = 'dot live'; connEl.textContent = 'Connected'; }
192
+ else if (wsOpen) { dotEl.className = 'dot'; connEl.textContent = 'Waiting for Mudra Band\u2026'; }
193
+ else { dotEl.className = 'dot'; connEl.textContent = simulating ? 'Simulated' : 'Disconnected'; }
194
+ }
195
+
196
+ /* ── Sequencer State ───────────────────────────────────────── */
197
+ const instruments = ['Kick', 'Snare', 'HiHat', 'Clap'];
198
+ const STEPS = 16;
199
+ const grid = Array.from({ length: 4 }, () => new Array(STEPS).fill(false));
200
+ const velocities = Array.from({ length: 4 }, () => new Array(STEPS).fill(0.5));
201
+ let playing = false, step = 0, bpm = 120;
202
+ let cursorRow = 0, cursorCol = 0;
203
+ let velocity = 0.5;
204
+ let audioCtx = null;
205
+ let stepTimeout = null;
206
+
207
+ /* Pre-fill a starter beat */
208
+ grid[0][0] = true; grid[0][4] = true; grid[0][8] = true; grid[0][12] = true;
209
+ grid[1][4] = true; grid[1][12] = true;
210
+ grid[2][0] = true; grid[2][2] = true; grid[2][4] = true; grid[2][6] = true;
211
+ grid[2][8] = true; grid[2][10] = true; grid[2][12] = true; grid[2][14] = true;
212
+
213
+ /* ── Build UI ──────────────────────────────────────────────── */
214
+ const sequencerEl = document.getElementById('sequencer');
215
+
216
+ function buildGrid() {
217
+ sequencerEl.textContent = '';
218
+ instruments.forEach((name, r) => {
219
+ const row = document.createElement('div');
220
+ row.className = 'row';
221
+ const label = document.createElement('div');
222
+ label.className = 'row-label';
223
+ label.textContent = name;
224
+ row.appendChild(label);
225
+ const cellsDiv = document.createElement('div');
226
+ cellsDiv.className = 'cells';
227
+ for (let c = 0; c < STEPS; c++) {
228
+ const cell = document.createElement('div');
229
+ let cls = 'cell';
230
+ if (grid[r][c]) cls += ' on';
231
+ if (r === cursorRow && c === cursorCol) cls += ' cursor';
232
+ if (c === step && playing) cls += ' playhead';
233
+ if (c % 4 === 0 && c > 0) cls += ' beat4';
234
+ cell.className = cls;
235
+ cell.dataset.r = r;
236
+ cell.dataset.c = c;
237
+ if (grid[r][c]) {
238
+ cell.style.opacity = 0.3 + velocities[r][c] * 0.7;
239
+ }
240
+ cell.addEventListener('click', () => { toggleCell(r, c); });
241
+ cellsDiv.appendChild(cell);
242
+ }
243
+ row.appendChild(cellsDiv);
244
+ sequencerEl.appendChild(row);
245
+ });
246
+ }
247
+
248
+ function updateGrid() {
249
+ const allCells = document.querySelectorAll('.cell');
250
+ allCells.forEach(cell => {
251
+ const r = +cell.dataset.r, c = +cell.dataset.c;
252
+ let cls = 'cell';
253
+ if (grid[r][c]) cls += ' on';
254
+ if (r === cursorRow && c === cursorCol) cls += ' cursor';
255
+ if (c === step && playing) cls += ' playhead';
256
+ if (c % 4 === 0 && c > 0) cls += ' beat4';
257
+ cell.className = cls;
258
+ if (grid[r][c]) {
259
+ cell.style.opacity = 0.3 + velocities[r][c] * 0.7;
260
+ } else {
261
+ cell.style.opacity = '';
262
+ }
263
+ });
264
+ }
265
+
266
+ function toggleCell(r, c) {
267
+ grid[r][c] = !grid[r][c];
268
+ if (grid[r][c]) velocities[r][c] = velocity;
269
+ updateGrid();
270
+ }
271
+
272
+ /* ── Web Audio Drum Sounds ─────────────────────────────────── */
273
+ function initAudio() {
274
+ if (audioCtx) return;
275
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
276
+ }
277
+
278
+ function playSound(instrument, vel) {
279
+ if (!audioCtx) return;
280
+ const v = vel;
281
+ const now = audioCtx.currentTime;
282
+
283
+ if (instrument === 0) {
284
+ /* Kick: low frequency sine with quick decay */
285
+ const osc = audioCtx.createOscillator();
286
+ const gain = audioCtx.createGain();
287
+ osc.type = 'sine';
288
+ osc.frequency.setValueAtTime(150, now);
289
+ osc.frequency.exponentialRampToValueAtTime(40, now + 0.12);
290
+ gain.gain.setValueAtTime(v * 0.8, now);
291
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.25);
292
+ osc.connect(gain); gain.connect(audioCtx.destination);
293
+ osc.start(now); osc.stop(now + 0.25);
294
+ } else if (instrument === 1) {
295
+ /* Snare: noise burst with bandpass filter */
296
+ const bufSize = audioCtx.sampleRate * 0.12;
297
+ const buf = audioCtx.createBuffer(1, bufSize, audioCtx.sampleRate);
298
+ const data = buf.getChannelData(0);
299
+ for (let i = 0; i < bufSize; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / bufSize);
300
+ const src = audioCtx.createBufferSource();
301
+ src.buffer = buf;
302
+ const filter = audioCtx.createBiquadFilter();
303
+ filter.type = 'bandpass'; filter.frequency.value = 3000; filter.Q.value = 1;
304
+ const gain = audioCtx.createGain();
305
+ gain.gain.setValueAtTime(v * 0.5, now);
306
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.14);
307
+ src.connect(filter); filter.connect(gain); gain.connect(audioCtx.destination);
308
+ src.start(now); src.stop(now + 0.14);
309
+ } else if (instrument === 2) {
310
+ /* HiHat: high frequency noise with very short decay */
311
+ const bufSize = audioCtx.sampleRate * 0.04;
312
+ const buf = audioCtx.createBuffer(1, bufSize, audioCtx.sampleRate);
313
+ const data = buf.getChannelData(0);
314
+ for (let i = 0; i < bufSize; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / bufSize);
315
+ const src = audioCtx.createBufferSource();
316
+ src.buffer = buf;
317
+ const hp = audioCtx.createBiquadFilter();
318
+ hp.type = 'highpass'; hp.frequency.value = 7000;
319
+ const gain = audioCtx.createGain();
320
+ gain.gain.setValueAtTime(v * 0.3, now);
321
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.05);
322
+ src.connect(hp); hp.connect(gain); gain.connect(audioCtx.destination);
323
+ src.start(now); src.stop(now + 0.05);
324
+ } else {
325
+ /* Clap: noise with bandpass and quick double-hit envelope */
326
+ const bufSize = audioCtx.sampleRate * 0.15;
327
+ const buf = audioCtx.createBuffer(1, bufSize, audioCtx.sampleRate);
328
+ const data = buf.getChannelData(0);
329
+ for (let i = 0; i < bufSize; i++) data[i] = (Math.random() * 2 - 1);
330
+ const src = audioCtx.createBufferSource();
331
+ src.buffer = buf;
332
+ const filter = audioCtx.createBiquadFilter();
333
+ filter.type = 'bandpass'; filter.frequency.value = 2000; filter.Q.value = 0.8;
334
+ const gain = audioCtx.createGain();
335
+ /* double-hit envelope */
336
+ gain.gain.setValueAtTime(v * 0.4, now);
337
+ gain.gain.exponentialRampToValueAtTime(0.05, now + 0.02);
338
+ gain.gain.setValueAtTime(v * 0.35, now + 0.025);
339
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.12);
340
+ src.connect(filter); filter.connect(gain); gain.connect(audioCtx.destination);
341
+ src.start(now); src.stop(now + 0.15);
342
+ }
343
+ }
344
+
345
+ /* ── Sequencer Transport ───────────────────────────────────── */
346
+ function tick() {
347
+ if (!playing) return;
348
+ for (let r = 0; r < 4; r++) {
349
+ if (grid[r][step]) playSound(r, velocities[r][step]);
350
+ }
351
+ updateGrid();
352
+ step = (step + 1) % STEPS;
353
+ const interval = (60 / bpm / 4) * 1000;
354
+ stepTimeout = setTimeout(tick, interval);
355
+ }
356
+
357
+ function togglePlay() {
358
+ initAudio();
359
+ playing = !playing;
360
+ const btn = document.getElementById('playBtn');
361
+ if (playing) {
362
+ btn.classList.add('playing');
363
+ btn.textContent = '\u23F8';
364
+ step = 0;
365
+ tick();
366
+ } else {
367
+ btn.classList.remove('playing');
368
+ btn.textContent = '\u25B6';
369
+ clearTimeout(stepTimeout);
370
+ updateGrid();
371
+ }
372
+ }
373
+
374
+ buildGrid();
375
+
376
+ /* ── Signal Handlers ───────────────────────────────────────── */
377
+ on('gesture', (d) => {
378
+ const type = d.type || 'unknown';
379
+ if (type === 'tap') toggleCell(cursorRow, cursorCol);
380
+ if (type === 'double_tap') togglePlay();
381
+ if (type === 'twist') {
382
+ /* clear current row */
383
+ for (let c = 0; c < STEPS; c++) grid[cursorRow][c] = false;
384
+ updateGrid();
385
+ }
386
+ if (type === 'double_twist') {
387
+ /* clear all */
388
+ for (let r = 0; r < 4; r++) for (let c = 0; c < STEPS; c++) grid[r][c] = false;
389
+ updateGrid();
390
+ }
391
+ });
392
+
393
+ let navAccX = 0, navAccY = 0;
394
+ const NAV_THRESH = 6;
395
+
396
+ on('navigation', (d) => {
397
+ navAccX += (d.delta_x || 0);
398
+ navAccY += (d.delta_y || 0);
399
+ let changed = false;
400
+ if (Math.abs(navAccX) >= NAV_THRESH) {
401
+ cursorCol = Math.max(0, Math.min(STEPS - 1, cursorCol + (navAccX > 0 ? 1 : -1)));
402
+ navAccX = 0;
403
+ changed = true;
404
+ }
405
+ if (Math.abs(navAccY) >= NAV_THRESH) {
406
+ cursorRow = Math.max(0, Math.min(3, cursorRow + (navAccY > 0 ? 1 : -1)));
407
+ navAccY = 0;
408
+ changed = true;
409
+ }
410
+ if (changed) updateGrid();
411
+ });
412
+
413
+ on('pressure', (d) => {
414
+ velocity = Math.max(0, Math.min(1, d.normalized ?? d.value / 100));
415
+ /* update the velocity for the cell under the cursor if it is active */
416
+ if (grid[cursorRow][cursorCol]) {
417
+ velocities[cursorRow][cursorCol] = velocity;
418
+ updateGrid();
419
+ }
420
+ document.getElementById('velFill').style.width = Math.round(velocity * 100) + '%';
421
+ document.getElementById('velVal').textContent = Math.round(velocity * 100) + '%';
422
+ });
423
+
424
+ /* ── Keyboard Fallback ─────────────────────────────────────── */
425
+ let kbVelocity = 0.5;
426
+ document.addEventListener('keydown', (e) => {
427
+ if (e.code === 'Space') { e.preventDefault(); toggleCell(cursorRow, cursorCol); }
428
+ if (e.code === 'Enter') { e.preventDefault(); togglePlay(); }
429
+ if (e.code === 'ArrowRight') { e.preventDefault(); cursorCol = Math.min(STEPS - 1, cursorCol + 1); updateGrid(); }
430
+ if (e.code === 'ArrowLeft') { e.preventDefault(); cursorCol = Math.max(0, cursorCol - 1); updateGrid(); }
431
+ if (e.code === 'ArrowDown') { e.preventDefault(); cursorRow = Math.min(3, cursorRow + 1); updateGrid(); }
432
+ if (e.code === 'ArrowUp') { e.preventDefault(); cursorRow = Math.max(0, cursorRow - 1); updateGrid(); }
433
+ if (e.code === 'KeyC') {
434
+ for (let c = 0; c < STEPS; c++) grid[cursorRow][c] = false;
435
+ updateGrid();
436
+ }
437
+ if (e.key === ']') {
438
+ kbVelocity = Math.min(1, kbVelocity + 0.08);
439
+ emit('pressure', { value: Math.round(kbVelocity * 100), normalized: kbVelocity, timestamp: Date.now() });
440
+ }
441
+ if (e.key === '[') {
442
+ kbVelocity = Math.max(0, kbVelocity - 0.08);
443
+ emit('pressure', { value: Math.round(kbVelocity * 100), normalized: kbVelocity, timestamp: Date.now() });
444
+ }
445
+ });
446
+
447
+ /* ── Buttons ───────────────────────────────────────────────── */
448
+ document.getElementById('simTap').addEventListener('click', () => triggerGesture('tap'));
449
+ document.getElementById('playBtn').addEventListener('click', togglePlay);
450
+
451
+ /* ── Start ─────────────────────────────────────────────────── */
452
+ connect();
453
+ </script>
454
+ </body>
455
+ </html>