p5-phone 1.6.2 → 1.6.4

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 (68) hide show
  1. package/README.md +3 -3
  2. package/dist/p5-phone.js +67 -33
  3. package/dist/p5-phone.min.js +2 -2
  4. package/examples/Phone Sensor Examples/microphone/01_mic_level/index.html +1 -1
  5. package/examples/Phone Sensor Examples/microphone/02_speech_recognition/README.md +135 -0
  6. package/examples/Phone Sensor Examples/microphone/02_speech_recognition/index.html +20 -0
  7. package/examples/Phone Sensor Examples/microphone/02_speech_recognition/sketch.js +91 -0
  8. package/examples/Phone Sensor Examples/movement/01_orientation_basic/index.html +1 -1
  9. package/examples/Phone Sensor Examples/movement/02_rotational_velocity/index.html +1 -1
  10. package/examples/Phone Sensor Examples/movement/03_acceleration/index.html +1 -1
  11. package/examples/Phone Sensor Examples/sound/01_dual_audio/index.html +1 -1
  12. package/examples/Phone Sensor Examples/sound/02_volume_touches/index.html +1 -1
  13. package/examples/Phone Sensor Examples/touch/01_touch_basic/index.html +1 -1
  14. package/examples/Phone Sensor Examples/touch/02_touch_zones/index.html +1 -1
  15. package/examples/Phone Sensor Examples/touch/03_touch_count/index.html +1 -1
  16. package/examples/Phone Sensor Examples/touch/04_touch_distance/index.html +1 -1
  17. package/examples/Phone Sensor Examples/touch/05_touch_angle/index.html +1 -1
  18. package/examples/Phone Sensor Examples/vibration/01_haptic_feedback/index.html +1 -1
  19. package/examples/Phone Sensor Examples - Minimal/microphone/01_mic_level/index.html +1 -1
  20. package/examples/Phone Sensor Examples - Minimal/movement/01_orientation_basic/index.html +1 -1
  21. package/examples/Phone Sensor Examples - Minimal/movement/02_rotational_velocity/index.html +1 -1
  22. package/examples/Phone Sensor Examples - Minimal/movement/03_acceleration/index.html +1 -1
  23. package/examples/Phone Sensor Examples - Minimal/sound/01_sound_basic/index.html +1 -1
  24. package/examples/Phone Sensor Examples - Minimal/sound/02_sound_amplitude/index.html +1 -1
  25. package/examples/Phone Sensor Examples - Minimal/touch/01_touch_basic/index.html +1 -1
  26. package/examples/Phone Sensor Examples - Minimal/touch/02_touch_zones/index.html +1 -1
  27. package/examples/Phone Sensor Examples - Minimal/touch/03_touch_count/index.html +1 -1
  28. package/examples/Phone Sensor Examples - Minimal/touch/04_touch_distance/index.html +1 -1
  29. package/examples/Phone Sensor Examples - Minimal/touch/05_touch_angle/index.html +1 -1
  30. package/examples/Phone Sensor Examples - Minimal/vibration/01_haptic_feedback/index.html +1 -1
  31. package/examples/Phone and Gif/collision/index.html +1 -1
  32. package/examples/Phone and Gif/fetch/index.html +1 -1
  33. package/examples/Phone and Gif/fly/index.html +1 -1
  34. package/examples/Phone and Gif/roll/index.html +1 -1
  35. package/examples/UXcompare/button-vs-movement/index.html +1 -1
  36. package/examples/UXcompare/button-vs-orientation/index.html +1 -1
  37. package/examples/UXcompare/button-vs-shake/index.html +1 -1
  38. package/examples/UXcompare/gyroscope-demo/index.html +1 -1
  39. package/examples/UXcompare/microphone-demo/index.html +1 -1
  40. package/examples/UXcompare/slider-vs-angle/index.html +1 -1
  41. package/examples/UXcompare/slider-vs-distance/index.html +1 -1
  42. package/examples/UXcompare/slider-vs-microphone/index.html +1 -1
  43. package/examples/UXcompare/slider-vs-touches/index.html +1 -1
  44. package/examples/UXcompare/sliders-vs-acceleration/index.html +1 -1
  45. package/examples/UXcompare/sliders-vs-rotation/index.html +1 -1
  46. package/examples/blankTemplate/index.html +1 -1
  47. package/examples/homepage/index.html +173 -5
  48. package/examples/ml5/Gaze_detector_class/GazeDetector.js +452 -0
  49. package/examples/ml5/Gaze_detector_class/README.md +592 -0
  50. package/examples/ml5/Gaze_detector_class/index.html +37 -0
  51. package/examples/ml5/Gaze_detector_class/sketch.js +220 -0
  52. package/examples/ml5/PHONE_BodyPose_two_points/index.html +35 -0
  53. package/examples/ml5/PHONE_BodyPose_two_points/sketch.js +390 -0
  54. package/examples/ml5/PHONE_FaceMesh_two_points/index.html +35 -0
  55. package/examples/ml5/PHONE_FaceMesh_two_points/sketch.js +380 -0
  56. package/examples/ml5/PHONE_HandPose_two_points/index.html +35 -0
  57. package/examples/ml5/PHONE_HandPose_two_points/sketch.js +380 -0
  58. package/examples/ml5/THREE_BodyPose_two_points/functions.js +526 -0
  59. package/examples/ml5/THREE_BodyPose_two_points/index.html +49 -0
  60. package/examples/ml5/THREE_BodyPose_two_points/sketch.js +354 -0
  61. package/examples/ml5/THREE_FaceMesh_two_points/functions.js +532 -0
  62. package/examples/ml5/THREE_FaceMesh_two_points/index.html +49 -0
  63. package/examples/ml5/THREE_FaceMesh_two_points/sketch.js +344 -0
  64. package/examples/ml5/THREE_HandPose_two_points/functions.js +467 -0
  65. package/examples/ml5/THREE_HandPose_two_points/index.html +101 -0
  66. package/examples/ml5/THREE_HandPose_two_points/sketch.js +332 -0
  67. package/package.json +1 -1
  68. package/src/p5-phone.js +67 -33
package/README.md CHANGED
@@ -67,10 +67,10 @@ This library simplifies access to the following p5.js mobile sensor and audio co
67
67
 
68
68
  ```html
69
69
  <!-- Minified version (recommended) -->
70
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.1/dist/p5-phone.min.js"></script>
70
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.min.js"></script>
71
71
 
72
72
  <!-- Development version (larger, with comments) -->
73
- <!-- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.1/dist/p5-phone.js"></script> -->
73
+ <!-- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.js"></script> -->
74
74
  ```
75
75
 
76
76
  ### Basic Setup
@@ -98,7 +98,7 @@ This library simplifies access to the following p5.js mobile sensor and audio co
98
98
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.min.js"></script>
99
99
 
100
100
  <!-- Load p5-phone library -->
101
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.1/dist/p5-phone.min.js"></script>
101
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.min.js"></script>
102
102
 
103
103
  </head>
104
104
  <body>
package/dist/p5-phone.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * p5-phone v1.6.0
2
+ * p5-phone v1.6.4
3
3
  * Simplified mobile hardware access for p5.js - handle sensors, microphone, touch, and browser gestures with ease
4
4
  * https://github.com/npuckett/p5-phone
5
5
  *
@@ -177,6 +177,20 @@ function enableSoundTap(message = 'Tap screen to enable sound') {
177
177
  });
178
178
  }
179
179
 
180
+ /**
181
+ * Enable speech recognition with tap-to-start
182
+ * User taps anywhere on screen to enable
183
+ * IMPORTANT: This does NOT create a p5.AudioIn object
184
+ * Only activates audio context for Web Speech API
185
+ * You must create your own p5.SpeechRec object after this
186
+ */
187
+ function enableSpeechTap(message = 'Tap to enable speech recognition') {
188
+ _createTapToEnable(message, async () => {
189
+ await _requestSpeechPermission();
190
+ console.log('✅ Speech recognition enabled via tap');
191
+ });
192
+ }
193
+
180
194
  /**
181
195
  * Enable vibration motor with a button interface
182
196
  * Creates a start button that user must click
@@ -339,6 +353,26 @@ async function _requestSoundOutput() {
339
353
  }
340
354
  }
341
355
 
356
+ async function _requestSpeechPermission() {
357
+ try {
358
+ // Start audio context for Web Speech API
359
+ // DO NOT create or start p5.AudioIn - this would conflict with speech recognition
360
+ if (typeof userStartAudio !== 'undefined') {
361
+ await userStartAudio();
362
+ }
363
+
364
+ window.speechEnabled = true;
365
+ _notifySketchReady();
366
+
367
+ } catch (error) {
368
+ console.error('Speech permission error:', error);
369
+ if (_debugVisible) {
370
+ debugError('Speech permission error:', error);
371
+ }
372
+ _notifySketchReady();
373
+ }
374
+ }
375
+
342
376
  async function _requestVibrationPermission() {
343
377
  try {
344
378
  // Check if Vibration API is supported
@@ -930,6 +964,7 @@ window.enableMicTap = enableMicTap;
930
964
  window.enableMicButton = enableMicButton;
931
965
  window.enableSoundTap = enableSoundTap;
932
966
  window.enableSoundButton = enableSoundButton;
967
+ window.enableSpeechTap = enableSpeechTap;
933
968
  window.enableVibrationTap = enableVibrationTap;
934
969
  window.enableVibrationButton = enableVibrationButton;
935
970
  window.vibrate = vibrate;
@@ -1368,8 +1403,9 @@ class PhoneCamera {
1368
1403
  const videoWidth = this._video.width;
1369
1404
  const videoHeight = this._video.height;
1370
1405
 
1371
- // Get actual canvas dimensions (works on both desktop and mobile)
1372
- // Use p5.js width/height globals if available, otherwise fall back to window dimensions
1406
+ // Get actual canvas DISPLAY dimensions (not drawing buffer dimensions)
1407
+ // In p5.js, the width/height globals represent the logical canvas size
1408
+ // which already accounts for pixel density in recent versions
1373
1409
  const canvasWidth = (typeof width !== 'undefined') ? width : window.innerWidth;
1374
1410
  const canvasHeight = (typeof height !== 'undefined') ? height : window.innerHeight;
1375
1411
 
@@ -1445,11 +1481,10 @@ class PhoneCamera {
1445
1481
  }
1446
1482
 
1447
1483
  // Add offset to position on canvas
1448
- // Note: dims.x can be negative in fitHeight mode when video is wider than canvas
1449
- // In that case, the video is drawn starting off-screen, but we want coordinates
1450
- // relative to the visible portion, so we use max(0, dims.x)
1451
- const mappedX = scaledX + Math.max(0, dims.x);
1452
- const mappedY = scaledY + Math.max(0, dims.y);
1484
+ // dims.x and dims.y represent where the video is drawn on the canvas
1485
+ // This can be negative when the video is larger than the canvas (fitHeight/fitWidth modes)
1486
+ const mappedX = scaledX + dims.x;
1487
+ const mappedY = scaledY + dims.y;
1453
1488
 
1454
1489
  return { x: mappedX, y: mappedY };
1455
1490
  }
@@ -1505,14 +1540,32 @@ class PhoneCamera {
1505
1540
 
1506
1541
  const dims = this.getDimensions();
1507
1542
 
1543
+ // Get canvas display dimensions for mirroring
1544
+ const canvasWidth = (typeof width !== 'undefined') ? width : window.innerWidth;
1545
+
1546
+ // Debug: log what we're drawing (once)
1547
+ if (!this._drawDebugLogged) {
1548
+ console.log('_draw() params:', {
1549
+ x: dims.x,
1550
+ y: dims.y,
1551
+ width: dims.width,
1552
+ height: dims.height,
1553
+ canvasWidth: canvasWidth,
1554
+ mirror: this._mirror
1555
+ });
1556
+ this._drawDebugLogged = true;
1557
+ }
1558
+
1508
1559
  // Save current drawing state
1509
1560
  push();
1510
1561
 
1511
1562
  // Apply mirroring if needed
1512
1563
  if (this._mirror) {
1513
- translate(dims.x + dims.width, dims.y);
1564
+ // Mirror by flipping around the center of the canvas
1565
+ translate(canvasWidth, 0);
1514
1566
  scale(-1, 1);
1515
- image(this._video, 0, 0, dims.width, dims.height);
1567
+ // Draw at the same logical position
1568
+ image(this._video, dims.x, dims.y, dims.width, dims.height);
1516
1569
  } else {
1517
1570
  image(this._video, dims.x, dims.y, dims.width, dims.height);
1518
1571
  }
@@ -1622,29 +1675,9 @@ if (typeof p5 !== 'undefined' && p5.prototype) {
1622
1675
  if (args[0] instanceof PhoneCamera) {
1623
1676
  const cam = args[0];
1624
1677
 
1625
- // If x and y are provided, use them, otherwise use camera's calculated position
1626
- if (args.length >= 3) {
1627
- // User provided position: image(cam, x, y, [w], [h])
1628
- if (!cam.ready || !cam.video) return;
1629
-
1630
- const x = args[1];
1631
- const y = args[2];
1632
- const w = args[3] || cam.width;
1633
- const h = args[4] || cam.height;
1634
-
1635
- this.push();
1636
- if (cam.mirror) {
1637
- this.translate(x + w, y);
1638
- this.scale(-1, 1);
1639
- originalImage.call(this, cam.video, 0, 0, w, h);
1640
- } else {
1641
- originalImage.call(this, cam.video, x, y, w, h);
1642
- }
1643
- this.pop();
1644
- } else {
1645
- // No position provided: image(cam) - use auto-positioning
1646
- cam._draw();
1647
- }
1678
+ // Always use auto-positioning for PhoneCamera
1679
+ // The camera calculates the correct position based on mode (fitHeight, fitWidth, etc)
1680
+ cam._draw();
1648
1681
  } else {
1649
1682
  // Not a PhoneCamera, use original image function
1650
1683
  originalImage.apply(this, args);
@@ -1669,6 +1702,7 @@ if (typeof p5 !== 'undefined' && p5.prototype) {
1669
1702
  p5.prototype.enableMicButton = enableMicButton;
1670
1703
  p5.prototype.enableSoundTap = enableSoundTap;
1671
1704
  p5.prototype.enableSoundButton = enableSoundButton;
1705
+ p5.prototype.enableSpeechTap = enableSpeechTap;
1672
1706
  p5.prototype.enableVibrationTap = enableVibrationTap;
1673
1707
  p5.prototype.enableVibrationButton = enableVibrationButton;
1674
1708
  p5.prototype.vibrate = vibrate;
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * p5-phone v1.6.0
2
+ * p5-phone v1.6.4
3
3
  * Simplified mobile hardware access for p5.js - handle sensors, microphone, touch, and browser gestures with ease
4
4
  * https://github.com/npuckett/p5-phone
5
5
  *
@@ -7,4 +7,4 @@
7
7
  * Released under the MIT License
8
8
  * https://opensource.org/licenses/MIT
9
9
  */
10
- window._originalConsoleError=console.error,window._originalConsoleWarn=console.warn,window._debugErrorHandlersSet||(window._debugErrorHandlersSet=!0,window._earlyErrors=window._earlyErrors||[],window.addEventListener("error",function(e){const n=`${e.error?.message||e.message||"Unknown error"} (${e.filename?e.filename.split("/").pop():"unknown file"}:${e.lineno||"unknown line"})`;console.error("🚨 Error caught:",n),e.error?.stack&&console.error("Stack:",e.error.stack),window._earlyErrors.push({type:"error",message:"JavaScript Error: "+n,stack:e.error?.stack}),!1===window.SHOW_DEBUG||window._debugVisible||"function"==typeof showDebug&&showDebug(),window._debugVisible&&"function"==typeof debugError&&(debugError("JavaScript Error:",n),e.error?.stack&&debugError("Stack trace:",e.error.stack))}),window.addEventListener("unhandledrejection",function(e){const n=e.reason?.message||e.reason||"Unknown promise rejection";console.error("🚨 Promise rejection caught:",n),window._earlyErrors.push({type:"error",message:"Unhandled Promise Rejection: "+n}),window._debugVisible&&"function"==typeof debugError&&debugError("Unhandled Promise Rejection:",n)})),window.sensorsEnabled=!1,window.micEnabled=!1,window.soundEnabled=!1,window.gesturesLocked=!1,window.vibrationEnabled=!1;let _permissionsInitialized=!1,_micInstance=null;function lockGestures(){window.gesturesLocked||(console.log("🔒 Locking mobile gestures..."),_initializeGestureBlocking(),_initializeP5TouchOverrides(),window.gesturesLocked=!0,console.log("✅ Mobile gestures locked"))}function enableGyroButton(e="ENABLE MOTION SENSORS",n="Requesting motion sensors..."){_createPermissionButton(e,n,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via button")})}function enableGyroTap(e="Tap screen to enable motion sensors"){_createTapToEnable(e,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via tap")})}function enableMicButton(e="ENABLE MICROPHONE",n="Requesting microphone access..."){_createPermissionButton(e,n,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via button")})}function enableMicTap(e="Tap screen to enable microphone"){_createTapToEnable(e,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via tap")})}function enableSoundButton(e="ENABLE SOUND",n="Enabling audio..."){_createPermissionButton(e,n,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via button")})}function enableSoundTap(e="Tap screen to enable sound"){_createTapToEnable(e,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via tap")})}function enableVibrationButton(e="ENABLE VIBRATION",n="Enabling vibration..."){_createPermissionButton(e,n,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via button")})}function enableVibrationTap(e="Tap screen to enable vibration"){_createTapToEnable(e,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via tap")})}function enableAllButton(e="ENABLE MOTION & MICROPHONE",n="Requesting permissions..."){_createPermissionButton(e,n,async()=>{await _requestMotionPermissions(),await _requestMicrophonePermissions(),console.log("✅ Motion sensors and microphone enabled via button")})}function enableAllTap(e="Tap screen to enable motion sensors & microphone"){_createTapToEnable(e,async()=>{await _requestMotionPermissions(),await _requestMicrophonePermissions(),console.log("✅ Motion sensors and microphone enabled via tap")})}function vibrate(e){return window.vibrationEnabled?navigator.vibrate?navigator.vibrate(e):(console.warn("⚠️ Vibration API not supported on this device"),!1):(console.warn("⚠️ Vibration not enabled. Call enableVibrationTap() or enableVibrationButton() first."),!1)}function stopVibration(){navigator.vibrate&&navigator.vibrate(0)}async function _requestMotionPermissions(){try{if("undefined"!=typeof DeviceOrientationEvent&&"function"==typeof DeviceOrientationEvent.requestPermission){const e=await DeviceOrientationEvent.requestPermission();if(console.log("Orientation permission:",e),"undefined"!=typeof DeviceMotionEvent&&"function"==typeof DeviceMotionEvent.requestPermission){const e=await DeviceMotionEvent.requestPermission();console.log("Motion permission:",e)}}window.sensorsEnabled=!0,_notifySketchReady()}catch(e){console.error("Motion sensor permission error:",e),_debugVisible&&debugError("Motion sensor permission error:",e),window.sensorsEnabled=!0,_notifySketchReady()}}async function _requestMicrophonePermissions(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),"undefined"!=typeof mic&&mic&&mic.start?(mic.start(),_micInstance=mic,window.micEnabled=!0):console.warn("No microphone object found. Create one with: mic = new p5.AudioIn();"),_notifySketchReady()}catch(e){console.error("Microphone permission error:",e),_debugVisible&&debugError("Microphone permission error:",e),_notifySketchReady()}}async function _requestSoundOutput(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),window.soundEnabled=!0,_notifySketchReady()}catch(e){console.error("Sound output error:",e),_debugVisible&&debugError("Sound output error:",e),window.soundEnabled=!0,_notifySketchReady()}}async function _requestVibrationPermission(){try{if(!navigator.vibrate)return console.warn("⚠️ Vibration API not supported on this device (likely iOS)"),_debugVisible&&debugWarn("Vibration API not supported on this device"),window.vibrationEnabled=!1,void _notifySketchReady();navigator.vibrate(1)?(window.vibrationEnabled=!0,console.log("✅ Vibration enabled")):(console.warn("⚠️ Vibration API available but vibration failed"),window.vibrationEnabled=!1),_notifySketchReady()}catch(e){console.error("Vibration permission error:",e),_debugVisible&&debugError("Vibration permission error:",e),window.vibrationEnabled=!1,_notifySketchReady()}}function _notifySketchReady(){"function"==typeof userSetupComplete&&userSetupComplete(),window.dispatchEvent(new CustomEvent("permissionsReady",{detail:{sensors:window.sensorsEnabled,microphone:window.micEnabled,sound:window.soundEnabled,vibration:window.vibrationEnabled,gestures:window.gesturesLocked}}))}function _createPermissionButton(e,n,t){_removeExistingUI();const o=document.createElement("button");o.id="permissionButton",o.textContent=e,o.style.cssText="\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n padding: 20px 40px;\n font-size: 18px;\n font-weight: bold;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border: none;\n border-radius: 12px;\n cursor: pointer;\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n box-shadow: 0 4px 15px rgba(0,0,0,0.2);\n transition: transform 0.2s ease;\n touch-action: manipulation;\n ";const i=document.createElement("div");i.id="permissionStatus",i.textContent=n,i.style.cssText="\n position: fixed;\n top: 60%;\n left: 50%;\n transform: translate(-50%, 0);\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n z-index: 999998;\n display: none;\n ",o.addEventListener("mouseenter",()=>{o.style.transform="translate(-50%, -50%) scale(1.05)"}),o.addEventListener("mouseleave",()=>{o.style.transform="translate(-50%, -50%) scale(1)"});const r=async()=>{o.parentNode&&(o.style.display="none",i.style.display="block",await t(),i.style.display="none",_removeExistingUI())};o.addEventListener("click",r),o.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),r()}),o.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),r()}),document.body.appendChild(o),document.body.appendChild(i)}function _createTapToEnable(e,n){_removeExistingUI();const t=document.createElement("div");t.id="tapOverlay",t.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.8);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999999;\n cursor: pointer;\n touch-action: manipulation;\n ";const o=document.createElement("div");o.textContent=e,o.style.cssText="\n color: white;\n font-size: 24px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n padding: 40px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.1);\n backdrop-filter: blur(10px);\n ",t.appendChild(o);const i=async()=>{t.parentNode&&(o.textContent="Enabling...",await n(),t.parentNode&&document.body.removeChild(t))};t.addEventListener("click",i),t.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),i()}),t.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),i()}),document.body.appendChild(t)}function _removeExistingUI(){const e=document.getElementById("permissionButton"),n=document.getElementById("permissionStatus"),t=document.getElementById("tapOverlay");e&&e.remove(),n&&n.remove(),t&&t.remove()}function _initializeGestureBlocking(){window.history.pushState(null,"",window.location.href),window.onpopstate=function(){window.history.pushState(null,"",window.location.href)},window.addEventListener("beforeunload",function(e){e.preventDefault(),e.returnValue=""}),_initializeEdgeSwipePrevention(),_initializeOtherGesturePrevention()}function _initializeEdgeSwipePrevention(){let e=0,n=0;document.addEventListener("touchstart",function(t){t.touches&&t.touches.length>0&&(e=t.touches[0].clientX,n=t.touches[0].clientY,(e<20||e>window.innerWidth-20)&&t.preventDefault())},{passive:!1,capture:!0}),document.addEventListener("touchmove",function(t){if(!t.touches||0===t.touches.length)return;let o=t.touches[0].clientX,i=t.touches[0].clientY,r=o-e,a=i-n;(e<20&&r>0||e>window.innerWidth-20&&r<0)&&(t.preventDefault(),t.stopPropagation()),0===window.pageYOffset&&a>0&&t.preventDefault(),!t.target||"CANVAS"!==t.target.tagName||document.getElementById("tapOverlay")||document.getElementById("permissionButton")||t.preventDefault()},{passive:!1,capture:!0})}function _initializeOtherGesturePrevention(){document.addEventListener("gesturestart",function(e){e.preventDefault()}),document.addEventListener("gesturechange",function(e){e.preventDefault()}),document.addEventListener("gestureend",function(e){e.preventDefault()});let e=0;document.addEventListener("touchend",function(n){if(n.target&&("tapOverlay"===n.target.id||n.target.closest("#tapOverlay")||"permissionButton"===n.target.id||"permissionStatus"===n.target.id||n.target.closest("#permissionButton")||n.target.closest("#permissionStatus")))return;const t=Date.now();t-e<=300&&n.preventDefault(),e=t},!1),window.oncontextmenu=function(e){return e.preventDefault(),e.stopPropagation(),!1}}function _initializeP5TouchOverrides(){setTimeout(()=>{if(window._setupDone)_overrideP5Touch();else{const e=setInterval(()=>{window._setupDone&&(_overrideP5Touch(),clearInterval(e))},100)}},100)}function _overrideP5Touch(){const e=window.touchStarted||function(){},n=window.touchMoved||function(){},t=window.touchEnded||function(){},o=window.mousePressed||function(){},i=window.mouseDragged||function(){},r=window.mouseReleased||function(){};window.touchStarted=function(n){return e(n),!1},window.touchMoved=function(e){return n(e),!1},window.touchEnded=function(e){return t(e),!1},window.mousePressed=function(e){return o(e),!1},window.mouseDragged=function(e){return i(e),!1},window.mouseReleased=function(e){return r(e),!1}}document.addEventListener("DOMContentLoaded",function(){const e=document.getElementById("startButton"),n=document.getElementById("statusText");e&&n&&(console.warn("⚠️ Legacy HTML elements detected. Consider using the new API functions instead."),e.addEventListener("click",async()=>{e.classList.add("hidden"),n.classList.remove("hidden"),n.textContent="Requesting permissions...",await _requestMotionPermissions(),await _requestMicrophonePermissions(),n.classList.add("hidden")}),lockGestures())});let _debugPanel=null,_debugVisible=!1,_debugMessages=[];const MAX_DEBUG_MESSAGES=20;function showDebug(){_createDebugPanel(),_debugPanel.style.display="block",_debugVisible=!0,window._debugVisible=!0,_setupConsoleOverrides(),_displayEarlyErrors()}function hideDebug(){_debugPanel&&(_debugPanel.style.display="none",_debugVisible=!1)}function toggleDebug(){_debugVisible?hideDebug():showDebug()}function debug(...e){console.log(...e);_addDebugMessage(e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" "),"log")}function debugError(...e){(window._originalConsoleError||console.error).apply(console,e);_addDebugMessage(`❌ ERROR: ${e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" ")}`,"error")}function debugWarn(...e){(window._originalConsoleWarn||console.warn).apply(console,e);_addDebugMessage(`⚠️ WARNING: ${e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" ")}`,"warning")}function _addDebugMessage(e,n="log"){const t={text:`[${(new Date).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}] ${e}`,type:n};_debugMessages.push(t),_debugMessages.length>20&&_debugMessages.shift(),_debugPanel&&_updateDebugDisplay()}function _setupConsoleOverrides(){window._consoleOverrideSet||(window._consoleOverrideSet=!0,window._originalConsoleError=console.error,window._originalConsoleWarn=console.warn,console.error=function(...e){window._originalConsoleError.apply(console,e),_debugVisible&&debugError(...e)},console.warn=function(...e){window._originalConsoleWarn.apply(console,e),_debugVisible&&debugWarn(...e)})}function _displayEarlyErrors(){window._earlyErrors&&window._earlyErrors.length>0&&(debugError(`🚨 Found ${window._earlyErrors.length} early error(s):`),window._earlyErrors.forEach(e=>{debugError(e.message),e.stack&&debugError("Stack trace:",e.stack)}),window._earlyErrors=[])}function _createDebugPanel(){if(_debugPanel)return;_debugPanel=document.createElement("div"),_debugPanel.id="mobile-debug-panel",_debugPanel.innerHTML='\n <div id="mobile-debug-header">\n <span>Debug</span>\n <button id="mobile-debug-close">×</button>\n </div>\n <div id="mobile-debug-content"></div>\n ';const e=document.createElement("style");e.textContent="\n #mobile-debug-panel {\n position: fixed;\n top: 20px;\n right: 20px;\n width: 350px;\n max-width: calc(100vw - 40px);\n max-height: 400px;\n background: rgba(0, 0, 0, 0.9);\n color: #ffffff;\n font-family: 'Courier New', monospace;\n font-size: 12px;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n z-index: 10000;\n display: none;\n }\n \n #mobile-debug-header {\n background: rgba(255, 255, 255, 0.1);\n padding: 8px 12px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-radius: 8px 8px 0 0;\n }\n \n #mobile-debug-header span {\n font-weight: bold;\n font-size: 13px;\n }\n \n #mobile-debug-close {\n background: none;\n border: none;\n color: #ffffff;\n font-size: 18px;\n cursor: pointer;\n padding: 0;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n #mobile-debug-close:hover {\n background: rgba(255, 255, 255, 0.1);\n border-radius: 4px;\n }\n \n #mobile-debug-content {\n padding: 12px;\n max-height: 340px;\n overflow-y: auto;\n word-wrap: break-word;\n line-height: 1.4;\n }\n \n .debug-message {\n margin-bottom: 4px;\n white-space: pre-wrap;\n }\n \n .debug-message.error {\n color: #ff6b6b;\n background: rgba(255, 107, 107, 0.1);\n padding: 4px;\n border-radius: 3px;\n border-left: 3px solid #ff6b6b;\n }\n \n .debug-message.warning {\n color: #ffd93d;\n background: rgba(255, 217, 61, 0.1);\n padding: 4px;\n border-radius: 3px;\n border-left: 3px solid #ffd93d;\n }\n \n .debug-timestamp {\n color: #888;\n font-size: 10px;\n }\n \n @media (max-width: 480px) {\n #mobile-debug-panel {\n width: calc(100vw - 20px);\n right: 10px;\n top: 10px;\n }\n }\n ",document.head.appendChild(e),document.body.appendChild(_debugPanel),document.getElementById("mobile-debug-close").onclick=hideDebug,_updateDebugDisplay()}function _updateDebugDisplay(){if(!_debugPanel)return;const e=document.getElementById("mobile-debug-content");e&&(e.innerHTML=_debugMessages.map(e=>"string"==typeof e?`<div class="debug-message">${e}</div>`:`<div class="debug-message ${e.type}">${e.text}</div>`).join(""),e.scrollTop=e.scrollHeight)}debug.clear=function(){_debugMessages=[],_debugPanel&&_updateDebugDisplay(),console.clear()},window.debug=debug,window.debugError=debugError,window.debugWarn=debugWarn,window.showDebug=showDebug,window.hideDebug=hideDebug,window.toggleDebug=toggleDebug,window.lockGestures=lockGestures,window.enableGyroTap=enableGyroTap,window.enableGyroButton=enableGyroButton,window.enableMicTap=enableMicTap,window.enableMicButton=enableMicButton,window.enableSoundTap=enableSoundTap,window.enableSoundButton=enableSoundButton,window.enableVibrationTap=enableVibrationTap,window.enableVibrationButton=enableVibrationButton,window.vibrate=vibrate,window.stopVibration=stopVibration,window.enableAllTap=enableAllTap,window.enableAllButton=enableAllButton;class PhoneCamera{constructor(e="user",n=!0,t="fitHeight"){this._active=e,this._mirror=n,this._mode=t,this._fixedWidth=640,this._fixedHeight=480,this._video=null,this._ready=!1,this._p5Instance=window,this._onReadyCallback=null,this._createCaptureRef=null,window._phoneCameras||(window._phoneCameras=[]),window._phoneCameras.push(this)}get ready(){return this._ready}get video(){return this._video}get videoElement(){return this._video?this._video.elt:null}get width(){if(!this._ready)return 0;return this.getDimensions().width}get height(){if(!this._ready)return 0;return this.getDimensions().height}get active(){return this._active}set active(e){"user"===e||"environment"===e?this._active!==e&&(this._active=e,this._ready&&this._switchCamera()):console.error('PhoneCamera: active must be "user" or "environment"')}get mirror(){return this._mirror}set mirror(e){this._mirror=!!e}get mode(){return this._mode}set mode(e){const n=["fitWidth","fitHeight","cover","contain","fixed"];n.includes(e)?this._mode=e:console.error("PhoneCamera: mode must be one of:",n.join(", "))}get fixedWidth(){return this._fixedWidth}set fixedWidth(e){this._fixedWidth=Math.max(1,e)}get fixedHeight(){return this._fixedHeight}set fixedHeight(e){this._fixedHeight=Math.max(1,e)}onReady(e){this._onReadyCallback=e,this._ready&&this._video&&this._video.elt&&this._video.elt.readyState>=2?e():this._video&&this._checkVideoReady()}_initializeCamera(){if(this._ready||this._video)return;const e={video:{facingMode:this._active},audio:!1};this._video=createCapture(e,()=>{this._ready=!0,this._video.hide(),console.log("✅ PhoneCamera ready"),this._checkVideoReady()}),this._video&&this._video.elt&&this._video.elt.addEventListener("loadeddata",()=>{this._ready=!0,this._checkVideoReady()})}_checkVideoReady(){if(this._video&&this._video.elt&&this._video.elt.readyState>=2){if(this._onReadyCallback){const e=this._onReadyCallback;this._onReadyCallback=null,e()}}else setTimeout(()=>this._checkVideoReady(),100)}_switchCamera(){if(!this._video)return;this._ready;this._ready=!1,this._video.remove();const e={video:{facingMode:this._active},audio:!1};this._video=createCapture(e,()=>{this._ready=!0,this._video.hide(),console.log(`✅ PhoneCamera switched to ${this._active} camera`)}),this._video&&this._video.elt&&this._video.elt.addEventListener("loadeddata",()=>{this._ready=!0})}remove(){this._video&&(this._video.remove(),this._video=null),this._ready=!1}getDimensions(){if(!this._ready||!this._video)return{x:0,y:0,width:0,height:0,scaleX:1,scaleY:1};const e=this._video.width,n=this._video.height,t="undefined"!=typeof width?width:window.innerWidth,o="undefined"!=typeof height?height:window.innerHeight;let i,r,a,s;if("fixed"===this._mode)i=this._fixedWidth,r=this._fixedHeight,a=(t-i)/2,s=(o-r)/2;else if("fitWidth"===this._mode)i=t,r=n/e*i,a=0,s=(o-r)/2;else if("fitHeight"===this._mode)r=o,i=e/n*r,a=(t-i)/2,s=0;else if("cover"===this._mode){const d=Math.max(t/e,o/n);i=e*d,r=n*d,a=(t-i)/2,s=(o-r)/2}else if("contain"===this._mode){const d=Math.min(t/e,o/n);i=e*d,r=n*d,a=(t-i)/2,s=(o-r)/2}return{x:a,y:s,width:i,height:r,scaleX:i/e,scaleY:r/n}}mapPoint(e,n){const t=this.getDimensions();let o=e*t.scaleX;const i=n*t.scaleY;this._mirror&&(o=t.width-o);return{x:o+Math.max(0,t.x),y:i+Math.max(0,t.y)}}mapKeypoint(e){if(!e||void 0===e.x||void 0===e.y)return console.warn("PhoneCamera.mapKeypoint: invalid keypoint",e),e;const n=this.mapPoint(e.x,e.y);return{...e,x:n.x,y:n.y}}mapKeypoints(e){return Array.isArray(e)?e.map(e=>this.mapKeypoint(e)):(console.warn("PhoneCamera.mapKeypoints: expected array, got",typeof e),e)}_draw(){if(!this._ready||!this._video)return;const e=this.getDimensions();push(),this._mirror?(translate(e.x+e.width,e.y),scale(-1,1),image(this._video,0,0,e.width,e.height)):image(this._video,e.x,e.y,e.width,e.height),pop()}}function createPhoneCamera(e="user",n=!0,t="fitHeight"){return new PhoneCamera(e,n,t)}function enableCameraButton(e="ENABLE CAMERA",n="Starting camera..."){_createPermissionButton(e,n,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via button")})}function enableCameraTap(e="Tap screen to enable camera"){navigator.permissions&&navigator.permissions.query?navigator.permissions.query({name:"camera"}).then(n=>{"granted"===n.state?(console.log("✅ Camera permission already granted - auto-starting"),_requestCameraPermission()):_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}).catch(()=>{_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}):_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}async function _requestCameraPermission(){try{if(void 0!==window._phoneCameras&&Array.isArray(window._phoneCameras))for(let e of window._phoneCameras)!e||e._ready||e._video||e._initializeCamera();"function"==typeof userCameraReady&&userCameraReady(),_notifySketchReady()}catch(e){console.error("Camera permission error:",e),_debugVisible&&debugError("Camera permission error:",e),_notifySketchReady()}}if(window.createPhoneCamera=createPhoneCamera,window.enableCameraButton=enableCameraButton,window.enableCameraTap=enableCameraTap,"undefined"!=typeof p5&&p5.prototype){const e=p5.prototype.image;p5.prototype.image=function(...n){if(n[0]instanceof PhoneCamera){const t=n[0];if(n.length>=3){if(!t.ready||!t.video)return;const o=n[1],i=n[2],r=n[3]||t.width,a=n[4]||t.height;this.push(),t.mirror?(this.translate(o+r,i),this.scale(-1,1),e.call(this,t.video,0,0,r,a)):e.call(this,t.video,o,i,r,a),this.pop()}else t._draw()}else e.apply(this,n)}}"undefined"!=typeof p5&&p5.prototype&&(p5.prototype.lockGestures=lockGestures,p5.prototype.enableGyroTap=enableGyroTap,p5.prototype.enableGyroButton=enableGyroButton,p5.prototype.enableMicTap=enableMicTap,p5.prototype.enableMicButton=enableMicButton,p5.prototype.enableSoundTap=enableSoundTap,p5.prototype.enableSoundButton=enableSoundButton,p5.prototype.enableVibrationTap=enableVibrationTap,p5.prototype.enableVibrationButton=enableVibrationButton,p5.prototype.vibrate=vibrate,p5.prototype.stopVibration=stopVibration,p5.prototype.enableAllTap=enableAllTap,p5.prototype.enableAllButton=enableAllButton,p5.prototype.createPhoneCamera=createPhoneCamera,p5.prototype.enableCameraButton=enableCameraButton,p5.prototype.enableCameraTap=enableCameraTap,p5.prototype.showDebug=showDebug,p5.prototype.hideDebug=hideDebug,p5.prototype.toggleDebug=toggleDebug,p5.prototype.debug=debug,p5.prototype.debugError=debugError,p5.prototype.debugWarn=debugWarn,console.log("✅ Mobile p5.js Permissions: p5.prototype functions registered"));
10
+ window._originalConsoleError=console.error,window._originalConsoleWarn=console.warn,window._debugErrorHandlersSet||(window._debugErrorHandlersSet=!0,window._earlyErrors=window._earlyErrors||[],window.addEventListener("error",function(e){const n=`${e.error?.message||e.message||"Unknown error"} (${e.filename?e.filename.split("/").pop():"unknown file"}:${e.lineno||"unknown line"})`;console.error("🚨 Error caught:",n),e.error?.stack&&console.error("Stack:",e.error.stack),window._earlyErrors.push({type:"error",message:"JavaScript Error: "+n,stack:e.error?.stack}),!1===window.SHOW_DEBUG||window._debugVisible||"function"==typeof showDebug&&showDebug(),window._debugVisible&&"function"==typeof debugError&&(debugError("JavaScript Error:",n),e.error?.stack&&debugError("Stack trace:",e.error.stack))}),window.addEventListener("unhandledrejection",function(e){const n=e.reason?.message||e.reason||"Unknown promise rejection";console.error("🚨 Promise rejection caught:",n),window._earlyErrors.push({type:"error",message:"Unhandled Promise Rejection: "+n}),window._debugVisible&&"function"==typeof debugError&&debugError("Unhandled Promise Rejection:",n)})),window.sensorsEnabled=!1,window.micEnabled=!1,window.soundEnabled=!1,window.gesturesLocked=!1,window.vibrationEnabled=!1;let _permissionsInitialized=!1,_micInstance=null;function lockGestures(){window.gesturesLocked||(console.log("🔒 Locking mobile gestures..."),_initializeGestureBlocking(),_initializeP5TouchOverrides(),window.gesturesLocked=!0,console.log("✅ Mobile gestures locked"))}function enableGyroButton(e="ENABLE MOTION SENSORS",n="Requesting motion sensors..."){_createPermissionButton(e,n,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via button")})}function enableGyroTap(e="Tap screen to enable motion sensors"){_createTapToEnable(e,async()=>{await _requestMotionPermissions(),console.log("✅ Gyroscope enabled via tap")})}function enableMicButton(e="ENABLE MICROPHONE",n="Requesting microphone access..."){_createPermissionButton(e,n,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via button")})}function enableMicTap(e="Tap screen to enable microphone"){_createTapToEnable(e,async()=>{await _requestMicrophonePermissions(),console.log("✅ Microphone enabled via tap")})}function enableSoundButton(e="ENABLE SOUND",n="Enabling audio..."){_createPermissionButton(e,n,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via button")})}function enableSoundTap(e="Tap screen to enable sound"){_createTapToEnable(e,async()=>{await _requestSoundOutput(),console.log("✅ Sound output enabled via tap")})}function enableSpeechTap(e="Tap to enable speech recognition"){_createTapToEnable(e,async()=>{await _requestSpeechPermission(),console.log("✅ Speech recognition enabled via tap")})}function enableVibrationButton(e="ENABLE VIBRATION",n="Enabling vibration..."){_createPermissionButton(e,n,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via button")})}function enableVibrationTap(e="Tap screen to enable vibration"){_createTapToEnable(e,async()=>{await _requestVibrationPermission(),console.log("✅ Vibration enabled via tap")})}function enableAllButton(e="ENABLE MOTION & MICROPHONE",n="Requesting permissions..."){_createPermissionButton(e,n,async()=>{await _requestMotionPermissions(),await _requestMicrophonePermissions(),console.log("✅ Motion sensors and microphone enabled via button")})}function enableAllTap(e="Tap screen to enable motion sensors & microphone"){_createTapToEnable(e,async()=>{await _requestMotionPermissions(),await _requestMicrophonePermissions(),console.log("✅ Motion sensors and microphone enabled via tap")})}function vibrate(e){return window.vibrationEnabled?navigator.vibrate?navigator.vibrate(e):(console.warn("⚠️ Vibration API not supported on this device"),!1):(console.warn("⚠️ Vibration not enabled. Call enableVibrationTap() or enableVibrationButton() first."),!1)}function stopVibration(){navigator.vibrate&&navigator.vibrate(0)}async function _requestMotionPermissions(){try{if("undefined"!=typeof DeviceOrientationEvent&&"function"==typeof DeviceOrientationEvent.requestPermission){const e=await DeviceOrientationEvent.requestPermission();if(console.log("Orientation permission:",e),"undefined"!=typeof DeviceMotionEvent&&"function"==typeof DeviceMotionEvent.requestPermission){const e=await DeviceMotionEvent.requestPermission();console.log("Motion permission:",e)}}window.sensorsEnabled=!0,_notifySketchReady()}catch(e){console.error("Motion sensor permission error:",e),_debugVisible&&debugError("Motion sensor permission error:",e),window.sensorsEnabled=!0,_notifySketchReady()}}async function _requestMicrophonePermissions(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),"undefined"!=typeof mic&&mic&&mic.start?(mic.start(),_micInstance=mic,window.micEnabled=!0):console.warn("No microphone object found. Create one with: mic = new p5.AudioIn();"),_notifySketchReady()}catch(e){console.error("Microphone permission error:",e),_debugVisible&&debugError("Microphone permission error:",e),_notifySketchReady()}}async function _requestSoundOutput(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),window.soundEnabled=!0,_notifySketchReady()}catch(e){console.error("Sound output error:",e),_debugVisible&&debugError("Sound output error:",e),window.soundEnabled=!0,_notifySketchReady()}}async function _requestSpeechPermission(){try{"undefined"!=typeof userStartAudio&&await userStartAudio(),window.speechEnabled=!0,_notifySketchReady()}catch(e){console.error("Speech permission error:",e),_debugVisible&&debugError("Speech permission error:",e),_notifySketchReady()}}async function _requestVibrationPermission(){try{if(!navigator.vibrate)return console.warn("⚠️ Vibration API not supported on this device (likely iOS)"),_debugVisible&&debugWarn("Vibration API not supported on this device"),window.vibrationEnabled=!1,void _notifySketchReady();navigator.vibrate(1)?(window.vibrationEnabled=!0,console.log("✅ Vibration enabled")):(console.warn("⚠️ Vibration API available but vibration failed"),window.vibrationEnabled=!1),_notifySketchReady()}catch(e){console.error("Vibration permission error:",e),_debugVisible&&debugError("Vibration permission error:",e),window.vibrationEnabled=!1,_notifySketchReady()}}function _notifySketchReady(){"function"==typeof userSetupComplete&&userSetupComplete(),window.dispatchEvent(new CustomEvent("permissionsReady",{detail:{sensors:window.sensorsEnabled,microphone:window.micEnabled,sound:window.soundEnabled,vibration:window.vibrationEnabled,gestures:window.gesturesLocked}}))}function _createPermissionButton(e,n,t){_removeExistingUI();const o=document.createElement("button");o.id="permissionButton",o.textContent=e,o.style.cssText="\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n padding: 20px 40px;\n font-size: 18px;\n font-weight: bold;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border: none;\n border-radius: 12px;\n cursor: pointer;\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n box-shadow: 0 4px 15px rgba(0,0,0,0.2);\n transition: transform 0.2s ease;\n touch-action: manipulation;\n ";const i=document.createElement("div");i.id="permissionStatus",i.textContent=n,i.style.cssText="\n position: fixed;\n top: 60%;\n left: 50%;\n transform: translate(-50%, 0);\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n z-index: 999998;\n display: none;\n ",o.addEventListener("mouseenter",()=>{o.style.transform="translate(-50%, -50%) scale(1.05)"}),o.addEventListener("mouseleave",()=>{o.style.transform="translate(-50%, -50%) scale(1)"});const r=async()=>{o.parentNode&&(o.style.display="none",i.style.display="block",await t(),i.style.display="none",_removeExistingUI())};o.addEventListener("click",r),o.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),r()}),o.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),r()}),document.body.appendChild(o),document.body.appendChild(i)}function _createTapToEnable(e,n){_removeExistingUI();const t=document.createElement("div");t.id="tapOverlay",t.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.8);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999999;\n cursor: pointer;\n touch-action: manipulation;\n ";const o=document.createElement("div");o.textContent=e,o.style.cssText="\n color: white;\n font-size: 24px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n text-align: center;\n padding: 40px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.1);\n backdrop-filter: blur(10px);\n ",t.appendChild(o);const i=async()=>{t.parentNode&&(o.textContent="Enabling...",await n(),t.parentNode&&document.body.removeChild(t))};t.addEventListener("click",i),t.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),i()}),t.addEventListener("pointerup",function(e){e.preventDefault(),e.stopPropagation(),i()}),document.body.appendChild(t)}function _removeExistingUI(){const e=document.getElementById("permissionButton"),n=document.getElementById("permissionStatus"),t=document.getElementById("tapOverlay");e&&e.remove(),n&&n.remove(),t&&t.remove()}function _initializeGestureBlocking(){window.history.pushState(null,"",window.location.href),window.onpopstate=function(){window.history.pushState(null,"",window.location.href)},window.addEventListener("beforeunload",function(e){e.preventDefault(),e.returnValue=""}),_initializeEdgeSwipePrevention(),_initializeOtherGesturePrevention()}function _initializeEdgeSwipePrevention(){let e=0,n=0;document.addEventListener("touchstart",function(t){t.touches&&t.touches.length>0&&(e=t.touches[0].clientX,n=t.touches[0].clientY,(e<20||e>window.innerWidth-20)&&t.preventDefault())},{passive:!1,capture:!0}),document.addEventListener("touchmove",function(t){if(!t.touches||0===t.touches.length)return;let o=t.touches[0].clientX,i=t.touches[0].clientY,r=o-e,a=i-n;(e<20&&r>0||e>window.innerWidth-20&&r<0)&&(t.preventDefault(),t.stopPropagation()),0===window.pageYOffset&&a>0&&t.preventDefault(),!t.target||"CANVAS"!==t.target.tagName||document.getElementById("tapOverlay")||document.getElementById("permissionButton")||t.preventDefault()},{passive:!1,capture:!0})}function _initializeOtherGesturePrevention(){document.addEventListener("gesturestart",function(e){e.preventDefault()}),document.addEventListener("gesturechange",function(e){e.preventDefault()}),document.addEventListener("gestureend",function(e){e.preventDefault()});let e=0;document.addEventListener("touchend",function(n){if(n.target&&("tapOverlay"===n.target.id||n.target.closest("#tapOverlay")||"permissionButton"===n.target.id||"permissionStatus"===n.target.id||n.target.closest("#permissionButton")||n.target.closest("#permissionStatus")))return;const t=Date.now();t-e<=300&&n.preventDefault(),e=t},!1),window.oncontextmenu=function(e){return e.preventDefault(),e.stopPropagation(),!1}}function _initializeP5TouchOverrides(){setTimeout(()=>{if(window._setupDone)_overrideP5Touch();else{const e=setInterval(()=>{window._setupDone&&(_overrideP5Touch(),clearInterval(e))},100)}},100)}function _overrideP5Touch(){const e=window.touchStarted||function(){},n=window.touchMoved||function(){},t=window.touchEnded||function(){},o=window.mousePressed||function(){},i=window.mouseDragged||function(){},r=window.mouseReleased||function(){};window.touchStarted=function(n){return e(n),!1},window.touchMoved=function(e){return n(e),!1},window.touchEnded=function(e){return t(e),!1},window.mousePressed=function(e){return o(e),!1},window.mouseDragged=function(e){return i(e),!1},window.mouseReleased=function(e){return r(e),!1}}document.addEventListener("DOMContentLoaded",function(){const e=document.getElementById("startButton"),n=document.getElementById("statusText");e&&n&&(console.warn("⚠️ Legacy HTML elements detected. Consider using the new API functions instead."),e.addEventListener("click",async()=>{e.classList.add("hidden"),n.classList.remove("hidden"),n.textContent="Requesting permissions...",await _requestMotionPermissions(),await _requestMicrophonePermissions(),n.classList.add("hidden")}),lockGestures())});let _debugPanel=null,_debugVisible=!1,_debugMessages=[];const MAX_DEBUG_MESSAGES=20;function showDebug(){_createDebugPanel(),_debugPanel.style.display="block",_debugVisible=!0,window._debugVisible=!0,_setupConsoleOverrides(),_displayEarlyErrors()}function hideDebug(){_debugPanel&&(_debugPanel.style.display="none",_debugVisible=!1)}function toggleDebug(){_debugVisible?hideDebug():showDebug()}function debug(...e){console.log(...e);_addDebugMessage(e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" "),"log")}function debugError(...e){(window._originalConsoleError||console.error).apply(console,e);_addDebugMessage(`❌ ERROR: ${e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" ")}`,"error")}function debugWarn(...e){(window._originalConsoleWarn||console.warn).apply(console,e);_addDebugMessage(`⚠️ WARNING: ${e.map(e=>{if("object"==typeof e&&null!==e)try{return JSON.stringify(e,null,2)}catch(n){return String(e)}return String(e)}).join(" ")}`,"warning")}function _addDebugMessage(e,n="log"){const t={text:`[${(new Date).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}] ${e}`,type:n};_debugMessages.push(t),_debugMessages.length>20&&_debugMessages.shift(),_debugPanel&&_updateDebugDisplay()}function _setupConsoleOverrides(){window._consoleOverrideSet||(window._consoleOverrideSet=!0,window._originalConsoleError=console.error,window._originalConsoleWarn=console.warn,console.error=function(...e){window._originalConsoleError.apply(console,e),_debugVisible&&debugError(...e)},console.warn=function(...e){window._originalConsoleWarn.apply(console,e),_debugVisible&&debugWarn(...e)})}function _displayEarlyErrors(){window._earlyErrors&&window._earlyErrors.length>0&&(debugError(`🚨 Found ${window._earlyErrors.length} early error(s):`),window._earlyErrors.forEach(e=>{debugError(e.message),e.stack&&debugError("Stack trace:",e.stack)}),window._earlyErrors=[])}function _createDebugPanel(){if(_debugPanel)return;_debugPanel=document.createElement("div"),_debugPanel.id="mobile-debug-panel",_debugPanel.innerHTML='\n <div id="mobile-debug-header">\n <span>Debug</span>\n <button id="mobile-debug-close">×</button>\n </div>\n <div id="mobile-debug-content"></div>\n ';const e=document.createElement("style");e.textContent="\n #mobile-debug-panel {\n position: fixed;\n top: 20px;\n right: 20px;\n width: 350px;\n max-width: calc(100vw - 40px);\n max-height: 400px;\n background: rgba(0, 0, 0, 0.9);\n color: #ffffff;\n font-family: 'Courier New', monospace;\n font-size: 12px;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n z-index: 10000;\n display: none;\n }\n \n #mobile-debug-header {\n background: rgba(255, 255, 255, 0.1);\n padding: 8px 12px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-radius: 8px 8px 0 0;\n }\n \n #mobile-debug-header span {\n font-weight: bold;\n font-size: 13px;\n }\n \n #mobile-debug-close {\n background: none;\n border: none;\n color: #ffffff;\n font-size: 18px;\n cursor: pointer;\n padding: 0;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n #mobile-debug-close:hover {\n background: rgba(255, 255, 255, 0.1);\n border-radius: 4px;\n }\n \n #mobile-debug-content {\n padding: 12px;\n max-height: 340px;\n overflow-y: auto;\n word-wrap: break-word;\n line-height: 1.4;\n }\n \n .debug-message {\n margin-bottom: 4px;\n white-space: pre-wrap;\n }\n \n .debug-message.error {\n color: #ff6b6b;\n background: rgba(255, 107, 107, 0.1);\n padding: 4px;\n border-radius: 3px;\n border-left: 3px solid #ff6b6b;\n }\n \n .debug-message.warning {\n color: #ffd93d;\n background: rgba(255, 217, 61, 0.1);\n padding: 4px;\n border-radius: 3px;\n border-left: 3px solid #ffd93d;\n }\n \n .debug-timestamp {\n color: #888;\n font-size: 10px;\n }\n \n @media (max-width: 480px) {\n #mobile-debug-panel {\n width: calc(100vw - 20px);\n right: 10px;\n top: 10px;\n }\n }\n ",document.head.appendChild(e),document.body.appendChild(_debugPanel),document.getElementById("mobile-debug-close").onclick=hideDebug,_updateDebugDisplay()}function _updateDebugDisplay(){if(!_debugPanel)return;const e=document.getElementById("mobile-debug-content");e&&(e.innerHTML=_debugMessages.map(e=>"string"==typeof e?`<div class="debug-message">${e}</div>`:`<div class="debug-message ${e.type}">${e.text}</div>`).join(""),e.scrollTop=e.scrollHeight)}debug.clear=function(){_debugMessages=[],_debugPanel&&_updateDebugDisplay(),console.clear()},window.debug=debug,window.debugError=debugError,window.debugWarn=debugWarn,window.showDebug=showDebug,window.hideDebug=hideDebug,window.toggleDebug=toggleDebug,window.lockGestures=lockGestures,window.enableGyroTap=enableGyroTap,window.enableGyroButton=enableGyroButton,window.enableMicTap=enableMicTap,window.enableMicButton=enableMicButton,window.enableSoundTap=enableSoundTap,window.enableSoundButton=enableSoundButton,window.enableSpeechTap=enableSpeechTap,window.enableVibrationTap=enableVibrationTap,window.enableVibrationButton=enableVibrationButton,window.vibrate=vibrate,window.stopVibration=stopVibration,window.enableAllTap=enableAllTap,window.enableAllButton=enableAllButton;class PhoneCamera{constructor(e="user",n=!0,t="fitHeight"){this._active=e,this._mirror=n,this._mode=t,this._fixedWidth=640,this._fixedHeight=480,this._video=null,this._ready=!1,this._p5Instance=window,this._onReadyCallback=null,this._createCaptureRef=null,window._phoneCameras||(window._phoneCameras=[]),window._phoneCameras.push(this)}get ready(){return this._ready}get video(){return this._video}get videoElement(){return this._video?this._video.elt:null}get width(){if(!this._ready)return 0;return this.getDimensions().width}get height(){if(!this._ready)return 0;return this.getDimensions().height}get active(){return this._active}set active(e){"user"===e||"environment"===e?this._active!==e&&(this._active=e,this._ready&&this._switchCamera()):console.error('PhoneCamera: active must be "user" or "environment"')}get mirror(){return this._mirror}set mirror(e){this._mirror=!!e}get mode(){return this._mode}set mode(e){const n=["fitWidth","fitHeight","cover","contain","fixed"];n.includes(e)?this._mode=e:console.error("PhoneCamera: mode must be one of:",n.join(", "))}get fixedWidth(){return this._fixedWidth}set fixedWidth(e){this._fixedWidth=Math.max(1,e)}get fixedHeight(){return this._fixedHeight}set fixedHeight(e){this._fixedHeight=Math.max(1,e)}onReady(e){this._onReadyCallback=e,this._ready&&this._video&&this._video.elt&&this._video.elt.readyState>=2?e():this._video&&this._checkVideoReady()}_initializeCamera(){if(this._ready||this._video)return;const e={video:{facingMode:this._active},audio:!1};this._video=createCapture(e,()=>{this._ready=!0,this._video.hide(),console.log("✅ PhoneCamera ready"),this._checkVideoReady()}),this._video&&this._video.elt&&this._video.elt.addEventListener("loadeddata",()=>{this._ready=!0,this._checkVideoReady()})}_checkVideoReady(){if(this._video&&this._video.elt&&this._video.elt.readyState>=2){if(this._onReadyCallback){const e=this._onReadyCallback;this._onReadyCallback=null,e()}}else setTimeout(()=>this._checkVideoReady(),100)}_switchCamera(){if(!this._video)return;this._ready;this._ready=!1,this._video.remove();const e={video:{facingMode:this._active},audio:!1};this._video=createCapture(e,()=>{this._ready=!0,this._video.hide(),console.log(`✅ PhoneCamera switched to ${this._active} camera`)}),this._video&&this._video.elt&&this._video.elt.addEventListener("loadeddata",()=>{this._ready=!0})}remove(){this._video&&(this._video.remove(),this._video=null),this._ready=!1}getDimensions(){if(!this._ready||!this._video)return{x:0,y:0,width:0,height:0,scaleX:1,scaleY:1};const e=this._video.width,n=this._video.height,t="undefined"!=typeof width?width:window.innerWidth,o="undefined"!=typeof height?height:window.innerHeight;let i,r,a,s;if("fixed"===this._mode)i=this._fixedWidth,r=this._fixedHeight,a=(t-i)/2,s=(o-r)/2;else if("fitWidth"===this._mode)i=t,r=n/e*i,a=0,s=(o-r)/2;else if("fitHeight"===this._mode)r=o,i=e/n*r,a=(t-i)/2,s=0;else if("cover"===this._mode){const d=Math.max(t/e,o/n);i=e*d,r=n*d,a=(t-i)/2,s=(o-r)/2}else if("contain"===this._mode){const d=Math.min(t/e,o/n);i=e*d,r=n*d,a=(t-i)/2,s=(o-r)/2}return{x:a,y:s,width:i,height:r,scaleX:i/e,scaleY:r/n}}mapPoint(e,n){const t=this.getDimensions();let o=e*t.scaleX;const i=n*t.scaleY;this._mirror&&(o=t.width-o);return{x:o+t.x,y:i+t.y}}mapKeypoint(e){if(!e||void 0===e.x||void 0===e.y)return console.warn("PhoneCamera.mapKeypoint: invalid keypoint",e),e;const n=this.mapPoint(e.x,e.y);return{...e,x:n.x,y:n.y}}mapKeypoints(e){return Array.isArray(e)?e.map(e=>this.mapKeypoint(e)):(console.warn("PhoneCamera.mapKeypoints: expected array, got",typeof e),e)}_draw(){if(!this._ready||!this._video)return;const e=this.getDimensions(),n="undefined"!=typeof width?width:window.innerWidth;this._drawDebugLogged||(console.log("_draw() params:",{x:e.x,y:e.y,width:e.width,height:e.height,canvasWidth:n,mirror:this._mirror}),this._drawDebugLogged=!0),push(),this._mirror?(translate(n,0),scale(-1,1),image(this._video,e.x,e.y,e.width,e.height)):image(this._video,e.x,e.y,e.width,e.height),pop()}}function createPhoneCamera(e="user",n=!0,t="fitHeight"){return new PhoneCamera(e,n,t)}function enableCameraButton(e="ENABLE CAMERA",n="Starting camera..."){_createPermissionButton(e,n,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via button")})}function enableCameraTap(e="Tap screen to enable camera"){navigator.permissions&&navigator.permissions.query?navigator.permissions.query({name:"camera"}).then(n=>{"granted"===n.state?(console.log("✅ Camera permission already granted - auto-starting"),_requestCameraPermission()):_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}).catch(()=>{_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}):_createTapToEnable(e,async()=>{await _requestCameraPermission(),console.log("✅ Camera enabled via tap")})}async function _requestCameraPermission(){try{if(void 0!==window._phoneCameras&&Array.isArray(window._phoneCameras))for(let e of window._phoneCameras)!e||e._ready||e._video||e._initializeCamera();"function"==typeof userCameraReady&&userCameraReady(),_notifySketchReady()}catch(e){console.error("Camera permission error:",e),_debugVisible&&debugError("Camera permission error:",e),_notifySketchReady()}}if(window.createPhoneCamera=createPhoneCamera,window.enableCameraButton=enableCameraButton,window.enableCameraTap=enableCameraTap,"undefined"!=typeof p5&&p5.prototype){const e=p5.prototype.image;p5.prototype.image=function(...n){if(n[0]instanceof PhoneCamera){n[0]._draw()}else e.apply(this,n)}}"undefined"!=typeof p5&&p5.prototype&&(p5.prototype.lockGestures=lockGestures,p5.prototype.enableGyroTap=enableGyroTap,p5.prototype.enableGyroButton=enableGyroButton,p5.prototype.enableMicTap=enableMicTap,p5.prototype.enableMicButton=enableMicButton,p5.prototype.enableSoundTap=enableSoundTap,p5.prototype.enableSoundButton=enableSoundButton,p5.prototype.enableSpeechTap=enableSpeechTap,p5.prototype.enableVibrationTap=enableVibrationTap,p5.prototype.enableVibrationButton=enableVibrationButton,p5.prototype.vibrate=vibrate,p5.prototype.stopVibration=stopVibration,p5.prototype.enableAllTap=enableAllTap,p5.prototype.enableAllButton=enableAllButton,p5.prototype.createPhoneCamera=createPhoneCamera,p5.prototype.enableCameraButton=enableCameraButton,p5.prototype.enableCameraTap=enableCameraTap,p5.prototype.showDebug=showDebug,p5.prototype.hideDebug=hideDebug,p5.prototype.toggleDebug=toggleDebug,p5.prototype.debug=debug,p5.prototype.debugError=debugError,p5.prototype.debugWarn=debugWarn,console.log("✅ Mobile p5.js Permissions: p5.prototype functions registered"));
@@ -4,7 +4,7 @@
4
4
  <title>P5.js Mobile - Microphone Level</title>
5
5
  <script src="https://cdn.jsdelivr.net/npm/p5@1.11.10/lib/p5.min.js"></script>
6
6
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.0/addons/p5.sound.min.js"></script>
7
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.5.0/dist/p5-phone.min.js"></script>
7
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.min.js"></script>
8
8
  <style>
9
9
  body {
10
10
  margin: 0;
@@ -0,0 +1,135 @@
1
+ # Speech Recognition Example
2
+
3
+ This example demonstrates voice-to-text speech recognition using p5.js-speech library integrated with p5-phone for mobile microphone access.
4
+
5
+ ## Requirements
6
+
7
+ ### HTTPS Required
8
+ Speech recognition requires a secure HTTPS connection on mobile devices. This will NOT work on:
9
+ - HTTP connections (except localhost)
10
+ - File:// protocol
11
+
12
+ ### Browser Compatibility
13
+
14
+ **✅ Supported:**
15
+ - Chrome on Android (recommended)
16
+ - Chrome on Desktop
17
+ - Edge on Desktop
18
+ - Safari on Desktop (limited support)
19
+
20
+ **❌ NOT Supported:**
21
+ - **iOS Safari** - Apple's Safari on iOS does not support the Web Speech API
22
+ - **iOS Chrome** - Uses Safari's engine on iOS, so also not supported
23
+ - Firefox (as of 2025, limited support)
24
+
25
+ ### Mobile Testing
26
+ To test on mobile:
27
+ 1. Deploy to a server with HTTPS (GitHub Pages, Netlify, etc.)
28
+ 2. Or use a tool like `ngrok` to create an HTTPS tunnel to your localhost
29
+ 3. Make sure you're using Chrome on Android (not iOS)
30
+
31
+ ## How It Works
32
+
33
+ ### Cloud-Based Speech Recognition
34
+
35
+ The p5.speech library is built on top of the **Web Speech API**, which is a **cloud-based service**:
36
+
37
+ 1. **Microphone captures audio** on your device (handled by p5-phone's `enableMicTap()`)
38
+ 2. **Audio is sent to Google's servers** via the Web Speech Recognition API
39
+ 3. **Google's servers process the audio** using machine learning models
40
+ 4. **Transcribed text is sent back** to your browser
41
+ 5. **Your sketch displays the results**
42
+
43
+ **IMPORTANT:** Because speech recognition happens on remote servers, you **MUST have an active internet connection** for this to work. If you get "no match" errors, check your internet connection first.
44
+
45
+ ### Recognition Flow
46
+
47
+ 1. **p5-phone** handles microphone permission via `enableMicTap()`
48
+ 2. Once permission is granted, `userSetupComplete()` initializes p5.SpeechRec
49
+ 3. Speech recognition starts and listens for phrases
50
+ 4. Audio is sent to cloud for processing
51
+ 5. Recognized text is displayed on screen with confidence levels
52
+ 6. Recognition automatically restarts after each phrase
53
+
54
+ ## Common Issues
55
+
56
+ ### Desktop Works, Mobile Doesn't - Why?
57
+
58
+ **CRITICAL ISSUE IDENTIFIED:** Microphone hardware conflict!
59
+
60
+ **The Real Problem:**
61
+ - On mobile, only **ONE application can access the microphone at a time**
62
+ - `p5.AudioIn` (from p5.sound) and Web Speech API **both try to access the same mic**
63
+ - Desktop Chrome is more forgiving and allows both
64
+ - Mobile Chrome enforces strict exclusive access
65
+
66
+ **The Solution:**
67
+ - Create a `p5.AudioIn` object but **never call mic.start()**
68
+ - Use `enableMicTap()` to get permission, then **immediately stop the mic**
69
+ - This frees the hardware for Web Speech API to use
70
+
71
+ **Code pattern:**
72
+ ```javascript
73
+ // In setup()
74
+ mic = new p5.AudioIn(); // Create but don't start
75
+ enableMicTap(); // Get permission
76
+
77
+ // In userSetupComplete()
78
+ if (mic && mic.enabled) {
79
+ mic.stop(); // Free the microphone!
80
+ }
81
+ speechRec = new p5.SpeechRec(); // Now this can access the mic
82
+ ```
83
+
84
+ **Why this wasn't obvious:**
85
+ - Desktop allows multiple streams (mic.getLevel() AND speech recognition work together)
86
+ - Mobile enforces exclusive access (only ONE can work at a time)
87
+ - No error is thrown - speech API just silently fails to get audio
88
+
89
+ ### Previous "No match found for speech" debugging
90
+ This is the most common error. It means the Web Speech API heard you speaking but couldn't transcribe it.
91
+
92
+ **Common causes:**
93
+ 1. **Poor internet connection** - Audio couldn't be sent to Google's servers or response was delayed
94
+ 2. **Background noise** - Other sounds interfering with your voice
95
+ 3. **Speaking too quietly/quickly** - The API needs clear, distinct speech
96
+ 4. **Language mismatch** - Currently set to `en-US`. Speak in English for best results.
97
+
98
+ **Solutions:**
99
+ - Check you have a stable internet connection
100
+ - Move to a quieter environment
101
+ - Speak more clearly and at a moderate pace
102
+ - Check the microphone level indicator - it should move when you speak
103
+
104
+ ### "No speech detected" repeatedly
105
+ - Check that you're speaking clearly and loudly enough
106
+ - Ensure you're in a quiet environment
107
+ - Verify your device microphone is working (test with voice memos app)
108
+ - **Check internet connection** - without it, recognition cannot work
109
+
110
+ ### Nothing happens on iOS
111
+ - iOS Safari does not support Web Speech API
112
+ - Try using an Android device with Chrome instead
113
+
114
+ ### "Not allowed" error
115
+ - You may have denied microphone permission
116
+ - Reload the page and allow microphone access
117
+ - Check that you're using HTTPS (not HTTP)
118
+
119
+ ## Configuration
120
+
121
+ You can adjust these settings in the code:
122
+
123
+ ```javascript
124
+ speechRec.continuous = false; // false = one phrase at a time
125
+ speechRec.interimResults = true; // true = show partial results
126
+ const deviceLang = 'en-US'; // Change language (en-US, es-ES, etc.)
127
+ ```
128
+
129
+ ## Privacy Note
130
+
131
+ This example uses `continuous: false` mode, which means:
132
+ - Recognition stops after each phrase
133
+ - More privacy-conscious than continuous listening
134
+ - Auto-restarts after 1 second pause
135
+ - User maintains control over when mic is active
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>P5.js Mobile - Speech Recognition</title>
5
+ <script src="https://cdn.jsdelivr.net/npm/p5@1.11.10/lib/p5.min.js"></script>
6
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.0/addons/p5.sound.min.js"></script>
7
+ <script src="https://cdn.jsdelivr.net/gh/IDMNYU/p5.js-speech@0.0.3/lib/p5.speech.js"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.min.js"></script>
9
+ <style>
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ overflow: hidden;
14
+ }
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <script src="sketch.js"></script>
19
+ </body>
20
+ </html>
@@ -0,0 +1,91 @@
1
+ // Speech Recognition example using p5.speech
2
+ // Touch to talk - tap screen, speak, release
3
+ // Based on p5.speech example: 04simplerecognition.html
4
+
5
+ let speechRec;
6
+ let recognizedText = "Tap screen and say something";
7
+ let isListening = false;
8
+
9
+ function setup() {
10
+ createCanvas(windowWidth, windowHeight);
11
+ background(50);
12
+
13
+ // Show debug panel for troubleshooting
14
+ showDebug();
15
+
16
+ // Lock gestures to prevent browser interference
17
+ lockGestures();
18
+
19
+ // Enable speech recognition permission
20
+ // This activates the audio context without creating p5.AudioIn
21
+ enableSpeechTap('Tap to enable speech recognition');
22
+ }
23
+
24
+ // This runs after speech permission is granted
25
+ function userSetupComplete() {
26
+ // Create speech recognition object
27
+ speechRec = new p5.SpeechRec('en-US');
28
+ speechRec.continuous = false; // One phrase per touch
29
+ speechRec.interimResults = false; // Only final results
30
+ speechRec.onResult = showResult;
31
+
32
+ // Handle when recognition ends
33
+ speechRec.onEnd = function() {
34
+ isListening = false;
35
+ console.log("Recognition ended");
36
+ };
37
+
38
+ console.log('✅ Speech recognition ready - tap screen to talk');
39
+ }
40
+
41
+ function draw() {
42
+ // Change background color when listening
43
+ if (isListening) {
44
+ background(100, 200, 100); // Green when listening
45
+ } else {
46
+ background(50);
47
+ }
48
+
49
+ fill(255);
50
+ textSize(32);
51
+ textAlign(CENTER, CENTER);
52
+
53
+ if (speechRec && window.speechEnabled) {
54
+ if (isListening) {
55
+ text("🎤 Listening...", width/2, 50);
56
+ } else {
57
+ text("👆 Tap to talk", width/2, 50);
58
+ }
59
+ text(recognizedText, 40, height/2, width - 80);
60
+ } else {
61
+ text("Tap to enable speech recognition", width/2, height/2);
62
+ }
63
+ }
64
+
65
+ // Handle speech recognition results
66
+ function showResult() {
67
+ if (speechRec.resultValue) {
68
+ recognizedText = speechRec.resultString;
69
+ console.log("Recognized:", recognizedText);
70
+ }
71
+ }
72
+
73
+ // Touch started - start listening
74
+ function touchStarted() {
75
+ if (speechRec && window.speechEnabled && !isListening) {
76
+ isListening = true;
77
+ speechRec.start();
78
+ console.log("Started listening...");
79
+ }
80
+ return false; // Prevent default
81
+ }
82
+
83
+ // Touch ended - stop listening
84
+ function touchEnded() {
85
+ if (speechRec && isListening) {
86
+ isListening = false;
87
+ // Don't call stop - just let it process what was said
88
+ console.log("Stopped listening...");
89
+ }
90
+ return false;
91
+ }
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.5.0/dist/p5-phone.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.5.0/dist/p5-phone.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.5.0/dist/p5-phone.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>
@@ -21,7 +21,7 @@
21
21
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/addons/p5.sound.min.js"></script>
22
22
 
23
23
  <!-- Load the mobile p5.js permissions library -->
24
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.5.0/dist/p5-phone.min.js"></script>
24
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.min.js"></script>
25
25
 
26
26
  </head>
27
27
  <body>
@@ -21,7 +21,7 @@
21
21
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/addons/p5.sound.min.js"></script>
22
22
 
23
23
  <!-- Load the mobile p5.js permissions library -->
24
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.5.0/dist/p5-phone.min.js"></script>
24
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.min.js"></script>
25
25
 
26
26
  </head>
27
27
  <body>
@@ -18,7 +18,7 @@
18
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.10/p5.min.js"></script>
19
19
 
20
20
  <!-- Load the mobile p5.js permissions library -->
21
- <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.5.0/dist/p5-phone.min.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/p5-phone@1.6.3/dist/p5-phone.min.js"></script>
22
22
 
23
23
  </head>
24
24
  <body>