iniital attempts at live feedback
- SHA
7be5951d533514c8ed48ce99b66c4010b9b251db- Parents
-
3c3843d - Tree
956ad48
7be5951
7be5951d533514c8ed48ce99b66c4010b9b251db3c3843d
956ad48| Status | File | + | - |
|---|---|---|---|
| M |
index.html
|
1 | 0 |
| M |
js/app.js
|
31 | 0 |
| M |
js/audio-engine.js
|
42 | 0 |
index.htmlmodified@@ -15,6 +15,7 @@ | ||
| 15 | 15 | <section class="controls"> |
| 16 | 16 | <div class="control-group"> |
| 17 | 17 | <button id="startCamera" class="btn btn-primary">Start Camera</button> |
| 18 | + <button id="microphoneToggle" class="btn btn-warning" disabled>Start Microphone</button> | |
| 18 | 19 | <select id="audioSelect" class="select"> |
| 19 | 20 | <option value="">Select Audio File...</option> |
| 20 | 21 | <option value="__demo__">Generated Demo</option> |
js/app.jsmodified@@ -144,6 +144,9 @@ class GestureDSPApp { | ||
| 144 | 144 | // Camera control |
| 145 | 145 | document.getElementById('startCamera').addEventListener('click', () => this.startCamera()); |
| 146 | 146 | |
| 147 | + // Microphone control | |
| 148 | + document.getElementById('microphoneToggle').addEventListener('click', () => this.toggleMicrophone()); | |
| 149 | + | |
| 147 | 150 | // Audio selection |
| 148 | 151 | document.getElementById('audioSelect').addEventListener('change', async (e) => { |
| 149 | 152 | const value = e.target.value; |
@@ -324,12 +327,40 @@ class GestureDSPApp { | ||
| 324 | 327 | await this.gestureDetector.start(); |
| 325 | 328 | this.updateStatus('Camera active - wear colored gloves!'); |
| 326 | 329 | document.getElementById('startCamera').disabled = true; |
| 330 | + document.getElementById('microphoneToggle').disabled = false; | |
| 327 | 331 | } catch (err) { |
| 328 | 332 | console.error('Error starting camera:', err); |
| 329 | 333 | this.updateStatus('Camera access denied'); |
| 330 | 334 | } |
| 331 | 335 | } |
| 332 | 336 | |
| 337 | + async toggleMicrophone() { | |
| 338 | + const micBtn = document.getElementById('microphoneToggle'); | |
| 339 | + | |
| 340 | + try { | |
| 341 | + if (this.audioEngine.isMicrophoneActive) { | |
| 342 | + this.audioEngine.stopMicrophone(); | |
| 343 | + micBtn.textContent = 'Start Microphone'; | |
| 344 | + micBtn.className = 'btn btn-warning'; | |
| 345 | + this.updateStatus('Microphone stopped'); | |
| 346 | + } else { | |
| 347 | + await this.audioEngine.startMicrophone(); | |
| 348 | + micBtn.textContent = 'Stop Microphone'; | |
| 349 | + micBtn.className = 'btn btn-danger'; | |
| 350 | + this.updateStatus('Live microphone active! Use gestures to control effects.'); | |
| 351 | + } | |
| 352 | + } catch (err) { | |
| 353 | + console.error('Microphone error:', err); | |
| 354 | + if (err.name === 'NotAllowedError') { | |
| 355 | + this.updateStatus('Microphone permission denied. Please allow microphone access.'); | |
| 356 | + } else if (err.name === 'NotFoundError') { | |
| 357 | + this.updateStatus('No microphone found. Please connect a microphone.'); | |
| 358 | + } else { | |
| 359 | + this.updateStatus('Microphone error: ' + err.message); | |
| 360 | + } | |
| 361 | + } | |
| 362 | + } | |
| 363 | + | |
| 333 | 364 | togglePlayback() { |
| 334 | 365 | if (this.audioEngine.isPlaying) { |
| 335 | 366 | this.audioEngine.stop(); |
js/audio-engine.jsmodified@@ -8,6 +8,11 @@ export class AudioEngine { | ||
| 8 | 8 | this.isPlaying = false; |
| 9 | 9 | this.audioBuffer = null; |
| 10 | 10 | |
| 11 | + // Microphone support | |
| 12 | + this.microphoneStream = null; | |
| 13 | + this.microphoneSource = null; | |
| 14 | + this.isMicrophoneActive = false; | |
| 15 | + | |
| 11 | 16 | this.currentEffectIndex = 0; |
| 12 | 17 | this.currentParam = 0.5; |
| 13 | 18 | this.rawParam = 0.5; |
@@ -436,6 +441,43 @@ export class AudioEngine { | ||
| 436 | 441 | } |
| 437 | 442 | } |
| 438 | 443 | |
| 444 | + async startMicrophone() { | |
| 445 | + try { | |
| 446 | + await this.init(); | |
| 447 | + | |
| 448 | + this.microphoneStream = await navigator.mediaDevices.getUserMedia({ | |
| 449 | + audio: { | |
| 450 | + echoCancellation: false, | |
| 451 | + noiseSuppression: false, | |
| 452 | + autoGainControl: false | |
| 453 | + } | |
| 454 | + }); | |
| 455 | + | |
| 456 | + this.microphoneSource = this.audioContext.createMediaStreamSource(this.microphoneStream); | |
| 457 | + this.microphoneSource.connect(this.inputGain); | |
| 458 | + | |
| 459 | + this.isMicrophoneActive = true; | |
| 460 | + return true; | |
| 461 | + } catch (err) { | |
| 462 | + console.error('Error starting microphone:', err); | |
| 463 | + throw err; | |
| 464 | + } | |
| 465 | + } | |
| 466 | + | |
| 467 | + stopMicrophone() { | |
| 468 | + if (this.microphoneSource) { | |
| 469 | + this.microphoneSource.disconnect(); | |
| 470 | + this.microphoneSource = null; | |
| 471 | + } | |
| 472 | + | |
| 473 | + if (this.microphoneStream) { | |
| 474 | + this.microphoneStream.getTracks().forEach(track => track.stop()); | |
| 475 | + this.microphoneStream = null; | |
| 476 | + } | |
| 477 | + | |
| 478 | + this.isMicrophoneActive = false; | |
| 479 | + } | |
| 480 | + | |
| 439 | 481 | play() { |
| 440 | 482 | if (!this.audioBuffer) return; |
| 441 | 483 | |