Bladeren bron

Add gamepad-controlled synthesizer with debug interface

- Implement real-time gamepad input polling with visual feedback
- Add Tone.js synthesizer with frequency, filter, and volume control
- Create debug interface showing stick positions, triggers, and buttons
- Add comprehensive error handling with emergency audio stop
- Fix gamepad axis mapping with detailed comments
- Include audio visualization and click-to-start functionality
Billie Hilton 4 maanden geleden
bovenliggende
commit
8e0506456e
1 gewijzigde bestanden met toevoegingen van 94 en 67 verwijderingen
  1. 94 67
      src/index.js

+ 94 - 67
src/index.js

@@ -123,78 +123,105 @@ class DawdlehornApp {
     }
 
     handleGamepadInput(gamepad) {
-        // Update debug displays FIRST - this shows raw input immediately
-        const leftStickX = gamepad.axes[0] || 0;
-        const leftStickY = gamepad.axes[1] || 0;
-        const rightStickX = gamepad.axes[2] || 0;
-        const rightStickY = gamepad.axes[3] || 0;
-
-        this.elements.leftStickDisplay.textContent = `${leftStickX.toFixed(2)}, ${leftStickY.toFixed(2)}`;
-        this.elements.rightStickDisplay.textContent = `${rightStickX.toFixed(2)}, ${rightStickY.toFixed(2)}`;
-
-        const leftTrigger = gamepad.buttons[6] ? gamepad.buttons[6].value : 0;
-        const rightTrigger = gamepad.buttons[7] ? gamepad.buttons[7].value : 0;
-        this.elements.triggersDisplay.textContent = `L: ${leftTrigger.toFixed(2)} R: ${rightTrigger.toFixed(2)}`;
-
-        const buttonA = gamepad.buttons[0] && gamepad.buttons[0].pressed;
-        const buttonB = gamepad.buttons[1] && gamepad.buttons[1].pressed;
-        const buttonX = gamepad.buttons[2] && gamepad.buttons[2].pressed;
-        const buttonY = gamepad.buttons[3] && gamepad.buttons[3].pressed;
-
-        let buttonDisplay = '';
-        buttonDisplay += buttonA ? '[A]' : 'A';
-        buttonDisplay += ' ';
-        buttonDisplay += buttonB ? '[B]' : 'B';
-        buttonDisplay += ' ';
-        buttonDisplay += buttonX ? '[X]' : 'X';
-        buttonDisplay += ' ';
-        buttonDisplay += buttonY ? '[Y]' : 'Y';
-        this.elements.buttonsDisplay.textContent = buttonDisplay;
-
-        // Early return if audio not ready
-        if (!this.audioInitialized) return;
-
-        // Left stick X-axis controls frequency (200Hz - 2000Hz)
-        const newFreq = 440 + (leftStickX * 560); // 440Hz ± 560Hz
-        if (Math.abs(newFreq - this.currentFreq) > 5) {
-            this.currentFreq = newFreq;
-            this.synth.frequency.setValueAtTime(this.currentFreq, Tone.now());
-            this.elements.freqDisplay.textContent = `${Math.round(this.currentFreq)} Hz`;
-        }
+        try {
+            // Update debug displays FIRST - this shows raw input immediately
+            // Standard gamepad mapping: axes[0]=left stick X, axes[1]=left stick Y, axes[2]=right stick X, axes[3]=right stick Y
+            const leftStickX = gamepad.axes[0] || 0;
+            const leftStickY = gamepad.axes[1] || 0;
+            const rightStickX = gamepad.axes[2] || 0;
+            const rightStickY = gamepad.axes[3] || 0;
+
+            this.elements.leftStickDisplay.textContent = `${leftStickX.toFixed(2)}, ${leftStickY.toFixed(2)}`;
+            this.elements.rightStickDisplay.textContent = `${rightStickX.toFixed(2)}, ${rightStickY.toFixed(2)}`;
+
+            // Triggers: L2=buttons[6], R2=buttons[7] (analog values)
+            const leftTrigger = gamepad.buttons[6] ? gamepad.buttons[6].value : 0;
+            const rightTrigger = gamepad.buttons[7] ? gamepad.buttons[7].value : 0;
+            this.elements.triggersDisplay.textContent = `L: ${leftTrigger.toFixed(2)} R: ${rightTrigger.toFixed(2)}`;
+
+            // Face buttons: A=0, B=1, X=2, Y=3
+            const buttonA = gamepad.buttons[0] && gamepad.buttons[0].pressed;
+            const buttonB = gamepad.buttons[1] && gamepad.buttons[1].pressed;
+            const buttonX = gamepad.buttons[2] && gamepad.buttons[2].pressed;
+            const buttonY = gamepad.buttons[3] && gamepad.buttons[3].pressed;
+
+            let buttonDisplay = '';
+            buttonDisplay += buttonA ? '[A]' : 'A';
+            buttonDisplay += ' ';
+            buttonDisplay += buttonB ? '[B]' : 'B';
+            buttonDisplay += ' ';
+            buttonDisplay += buttonX ? '[X]' : 'X';
+            buttonDisplay += ' ';
+            buttonDisplay += buttonY ? '[Y]' : 'Y';
+            this.elements.buttonsDisplay.textContent = buttonDisplay;
+
+            // Early return if audio not ready
+            if (!this.audioInitialized) return;
+
+            // Left stick X-axis controls frequency (200Hz - 2000Hz)
+            const newFreq = 440 + (leftStickX * 560); // 440Hz ± 560Hz
+            if (Math.abs(newFreq - this.currentFreq) > 5) {
+                this.currentFreq = newFreq;
+                this.synth.frequency.setValueAtTime(this.currentFreq, Tone.now());
+                this.elements.freqDisplay.textContent = `${Math.round(this.currentFreq)} Hz`;
+            }
 
-        // Right stick Y-axis controls filter frequency (100Hz - 5000Hz)
-        const newFilter = 1000 + (rightStickY * -2000); // Inverted Y, 1000Hz ± 2000Hz
-        if (Math.abs(newFilter - this.currentFilter) > 50) {
-            this.currentFilter = Math.max(100, Math.min(5000, newFilter));
-            this.filter.frequency.setValueAtTime(this.currentFilter, Tone.now());
-            this.elements.filterDisplay.textContent = `${Math.round(this.currentFilter)} Hz`;
-        }
+            // Right stick Y-axis controls filter frequency (100Hz - 5000Hz)
+            const newFilter = 1000 + (rightStickY * -2000); // Inverted Y, 1000Hz ± 2000Hz
+            if (Math.abs(newFilter - this.currentFilter) > 50) {
+                this.currentFilter = Math.max(100, Math.min(5000, newFilter));
+                this.filter.frequency.setValueAtTime(this.currentFilter, Tone.now());
+                this.elements.filterDisplay.textContent = `${Math.round(this.currentFilter)} Hz`;
+            }
 
-        // Triggers control volume
-        const triggerVolume = (leftTrigger + rightTrigger) / 2;
-        if (Math.abs(triggerVolume - this.currentVolume) > 0.05) {
-            this.currentVolume = triggerVolume;
-            const dbVolume = -40 + (this.currentVolume * 40); // -40dB to 0dB
-            this.volume.volume.setValueAtTime(dbVolume, Tone.now());
-            this.elements.volumeDisplay.textContent = `${Math.round(this.currentVolume * 100)}%`;
-        }
+            // Triggers control volume
+            const triggerVolume = (leftTrigger + rightTrigger) / 2;
+            if (Math.abs(triggerVolume - this.currentVolume) > 0.05) {
+                this.currentVolume = triggerVolume;
+                const dbVolume = -40 + (this.currentVolume * 40); // -40dB to 0dB
+                this.volume.volume.setValueAtTime(dbVolume, Tone.now());
+                this.elements.volumeDisplay.textContent = `${Math.round(this.currentVolume * 100)}%`;
+            }
 
-        // Face buttons control note playing
-        const anyButtonPressed = buttonA || buttonB || buttonX || buttonY;
+            // Face buttons control note playing
+            const anyButtonPressed = buttonA || buttonB || buttonX || buttonY;
 
-        if (anyButtonPressed && !this.isPlaying) {
-            this.synth.start();
-            this.isPlaying = true;
-        } else if (!anyButtonPressed && this.isPlaying) {
-            this.synth.stop();
-            this.isPlaying = false;
-        }
+            if (anyButtonPressed && !this.isPlaying) {
+                this.synth.start();
+                this.isPlaying = true;
+            } else if (!anyButtonPressed && this.isPlaying) {
+                this.synth.stop();
+                this.isPlaying = false;
+            }
+
+            // Different buttons trigger different note frequencies
+            if (buttonA) this.synth.frequency.setValueAtTime(261.63, Tone.now()); // C4
+            if (buttonB) this.synth.frequency.setValueAtTime(293.66, Tone.now()); // D4
+            if (buttonX) this.synth.frequency.setValueAtTime(329.63, Tone.now()); // E4
+            if (buttonY) this.synth.frequency.setValueAtTime(349.23, Tone.now()); // F4
+
+        } catch (error) {
+            console.error('❌ Gamepad input error:', error);
+
+            // Emergency stop audio on any error
+            if (this.isPlaying && this.synth) {
+                try {
+                    this.synth.stop();
+                    this.isPlaying = false;
+                } catch (stopError) {
+                    console.error('❌ Failed to stop synth:', stopError);
+                }
+            }
 
-        // Different buttons trigger different note frequencies
-        if (buttonA) this.synth.frequency.setValueAtTime(261.63, Tone.now()); // C4
-        if (buttonB) this.synth.frequency.setValueAtTime(293.66, Tone.now()); // D4
-        if (buttonX) this.synth.frequency.setValueAtTime(329.63, Tone.now()); // E4
-        if (buttonY) this.synth.frequency.setValueAtTime(349.23, Tone.now()); // F4
+            // Update UI to show error state
+            this.elements.buttonsDisplay.textContent = 'ERROR - Audio Stopped';
+            this.elements.buttonsDisplay.style.color = '#ff0000';
+
+            // Reset error state after 2 seconds
+            setTimeout(() => {
+                this.elements.buttonsDisplay.style.color = '#00ff00';
+            }, 2000);
+        }
     }
 
     setupVisualizer() {