1// MusicGen Integration for HumGod 2// Complete audio recording, processing, and MusicGen generation system 3 4class MusicGenIntegration { 5 constructor() { 6 this.audioContext = null; 7 this.mediaRecorder = null; 8 this.audioChunks = []; 9 this.recordedAudio = null; 10 this.generatedAudio = null; 11 this.isRecording = false; 12 this.isPlaying = false; 13 this.currentInstrument = 'piano'; 14 this.audioVisualizer = null; 15 this.animationId = null; 16 17 // MusicGen API configuration 18 this.musicGenConfig = { 19 serverUrl: '/api', // Vercel API endpoint 20 apiUrl: 'https://api-inference.huggingface.co/models/facebook/musicgen-stereo-melody-large', 21 apiKey: 'YOUR_HUGGING_FACE_API_KEY', // Replace with your actual API key 22 model: 'facebook/musicgen-stereo-melody-large' 23 }; 24 25 this.init(); 26 } 27 28 async init() { 29 try { 30 // Initialize Web Audio API 31 this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); 32 33 // Initialize audio visualizer 34 this.initAudioVisualizer(); 35 36 // Bind event listeners 37 this.bindEventListeners(); 38 39 // Add user interaction handler to enable audio 40 this.addUserInteractionHandler(); 41 42 console.log('MusicGen Integration initialized successfully'); 43 } catch (error) { 44 console.error('Failed to initialize MusicGen Integration:', error); 45 this.showError('Failed to initialize audio system. Please refresh the page.'); 46 } 47 } 48 49 bindEventListeners() { 50 // Recording controls 51 const recordBtn = document.getElementById('recordBtn'); 52 const stopBtn = document.getElementById('stopBtn'); 53 const playBtn = document.getElementById('playBtn'); 54 const demoBtn = document.getElementById('demoBtn'); 55 56 if (recordBtn) { 57 recordBtn.addEventListener('click', () => this.startRecording()); 58 } 59 if (stopBtn) { 60 stopBtn.addEventListener('click', () => this.stopRecording()); 61 } 62 if (playBtn) { 63 playBtn.addEventListener('click', () => this.playGeneratedAudio()); 64 } 65 if (demoBtn) { 66 demoBtn.addEventListener('click', () => this.showDemoMode()); 67 } 68 69 // Instrument selection 70 this.bindInstrumentButtons(); 71 72 // Tempo control 73 const tempoSlider = document.getElementById('tempoSlider'); 74 const tempoValue = document.getElementById('tempoValue'); 75 if (tempoSlider && tempoValue) { 76 tempoSlider.addEventListener('input', (e) => { 77 tempoValue.textContent = `${e.target.value} BPM`; 78 }); 79 } 80 } 81 82 bindInstrumentButtons() { 83 // Try to bind immediately 84 this.attachInstrumentListeners(); 85 86 // Also try after a delay in case DOM isn't ready 87 setTimeout(() => { 88 this.attachInstrumentListeners(); 89 }, 100); 90 } 91 92 attachInstrumentListeners() { 93 const instrumentBtns = document.querySelectorAll('.instrument-btn'); 94 console.log(`Found ${instrumentBtns.length} instrument buttons`); 95 96 instrumentBtns.forEach((btn, index) => { 97 // Remove existing listeners to avoid duplicates 98 btn.removeEventListener('click', this.handleInstrumentClick); 99 100 console.log(`Button ${index}:`, btn.dataset.instrument, btn); 101 btn.addEventListener('click', this.handleInstrumentClick.bind(this)); 102 }); 103 } 104 105 handleInstrumentClick(e) { 106 e.preventDefault(); 107 const instrument = e.target.closest('.instrument-btn').dataset.instrument; 108 console.log(`Clicked instrument: ${instrument}`); 109 this.selectInstrument(instrument); 110 } 111 112 addUserInteractionHandler() { 113 // Enable audio context on first user interaction 114 const enableAudio = async () => { 115 if (this.audioContext && this.audioContext.state === 'suspended') { 116 console.log('ðµ Enabling audio context on user interaction...'); 117 await this.audioContext.resume(); 118 console.log('â Audio context enabled'); 119 } 120 }; 121 122 // Add click listeners to enable audio 123 document.addEventListener('click', enableAudio, { once: true }); 124 document.addEventListener('touchstart', enableAudio, { once: true }); 125 document.addEventListener('keydown', enableAudio, { once: true }); 126 } 127 128 async startRecording() { 129 try { 130 // Request microphone access 131 const stream = await navigator.mediaDevices.getUserMedia({ 132 audio: { 133 echoCancellation: true, 134 noiseSuppression: true, 135 sampleRate: 44100 136 } 137 }); 138 139 // Create MediaRecorder 140 this.mediaRecorder = new MediaRecorder(stream, { 141 mimeType: 'audio/webm;codecs=opus' 142 }); 143
144 this.audioChunks = []; 145 this.isRecording = true; 146 147 // Set up recording event handlers 148 this.mediaRecorder.ondataavailable = (event) => { 149 if (event.data.size > 0) { 150 this.audioChunks.push(event.data); 151 } 152 }; 153 154 this.mediaRecorder.onstop = () => { 155 this.processRecordedAudio(); 156 }; 157 158 // Start recording 159 this.mediaRecorder.start(100); // Collect data every 100ms 160 161 // Update UI 162 this.updateRecordingUI(true); 163 this.showRecordingStatus('Recording... Speak or hum into your microphone', 'recording'); 164 this.startAudioVisualization(stream); 165 166 console.log('Recording started'); 167 } catch (error) { 168 console.error('Failed to start recording:', error); 169 this.showError('Failed to access microphone. Please check permissions.'); 170 } 171 } 172 173 stopRecording() { 174 if (this.mediaRecorder && this.isRecording) { 175 this.mediaRecorder.stop(); 176 this.isRecording = false; 177 this.updateRecordingUI(false); 178 this.stopAudioVisualization(); 179 180 // Stop all tracks to release microphone 181 this.mediaRecorder.stream.getTracks().forEach(track => track.stop()); 182 183 console.log('Recording stopped'); 184 } 185 } 186 187 async processRecordedAudio() { 188 try { 189 // Create blob from recorded chunks 190 const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' }); 191 this.recordedAudio = audioBlob; 192 193 // Convert to WAV for better compatibility 194 const wavBlob = await this.convertToWav(audioBlob); 195 196 // Update UI 197 this.updateProcessingUI(true); 198 this.showRecordingStatus('Processing your recording...', 'processing'); 199 200 // Generate music with MusicGen 201 await this.generateMusicWithMusicGen(wavBlob); 202 203 } catch (error) { 204 console.error('Failed to process recorded audio:', error); 205 this.showError('Failed to process your recording. Please try again.'); 206 } 207 } 208 209 async convertToWav(audioBlob) { 210 return new Promise((resolve, reject) => { 211 const reader = new FileReader(); 212 reader.onload = async () => { 213 try { 214 const arrayBuffer = reader.result; 215 const audioContext = new (window.AudioContext || window.webkitAudioContext)(); 216 const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); 217 218 // Convert to WAV format 219 const wavBlob = this.audioBufferToWav(audioBuffer); 220 resolve(wavBlob); 221 } catch (error) { 222 reject(error); 223 } 224 }; 225 reader.readAsArrayBuffer(audioBlob); 226 }); 227 } 228 229 audioBufferToWav(buffer) { 230 const length = buffer.length; 231 const sampleRate = buffer.sampleRate; 232 const numberOfChannels = buffer.numberOfChannels; 233 const arrayBuffer = new ArrayBuffer(44 + length * numberOfChannels * 2); 234 const view = new DataView(arrayBuffer); 235 236 // WAV header 237 const writeString = (offset, string) => { 238 for (let i = 0; i < string.length; i++) { 239 view.setUint8(offset + i, string.charCodeAt(i)); 240 } 241 }; 242 243 writeString(0, 'RIFF'); 244 view.setUint32(4, 36 + length * numberOfChannels * 2, true); 245 writeString(8, 'WAVE'); 246 writeString(12, 'fmt '); 247 view.setUint32(16, 16, true); 248 view.setUint16(20, 1, true); 249 view.setUint16(22, numberOfChannels, true); 250 view.setUint32(24, sampleRate, true); 251 view.setUint32(28, sampleRate * numberOfChannels * 2, true); 252 view.setUint16(32, numberOfChannels * 2, true); 253 view.setUint16(34, 16, true); 254 writeString(36, 'data'); 255 view.setUint32(40, length * numberOfChannels * 2, true); 256 257 // Convert audio data 258 let offset = 44; 259 for (let i = 0; i < length; i++) { 260 for (let channel = 0; channel < numberOfChannels; channel++) { 261 const sample = Math.max(-1, Math.min(1, buffer.getChannelData(ch
261annel)[i])); 262 view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true); 263 offset += 2; 264 } 265 } 266 267 return new Blob([arrayBuffer], { type: 'audio/wav' }); 268 } 269 270 async generateMusicWithMusicGen(audioBlob) { 271 try { 272 // Show progress 273 this.updateProgress(0, 'Starting...'); 274 275 // Convert audio to base64 for server transmission 276 const base64Audio = await this.audioBlobToBase64(audioBlob); 277 278 this.updateProgress(30, 'Sending to MusicGen API...'); 279 280 // Create request payload 281 const payload = { 282 audioData: base64Audio, 283 instrument: this.currentInstrument, 284 duration: '10', 285 style: 'classical' 286 }; 287 288 // Make API request to our server 289 const response = await fetch(`${this.musicGenConfig.serverUrl}/generate-music`, { 290 method: 'POST', 291 headers: { 292 'Content-Type': 'application/json', 293 }, 294 body: JSON.stringify(payload) 295 }); 296 297 if (!response.ok) { 298 const errorData = await response.json(); 299 throw new Error(errorData.error || `Server request failed: ${response.status}`); 300 } 301 302 this.updateProgress(70, 'Processing with MusicGen...'); 303 304 // Get the response 305 const result = await response.json(); 306 307 if (!result.success) { 308 throw new Error(result.error || 'Failed to generate music'); 309 } 310 311 this.updateProgress(90, 'Converting audio...'); 312 313 // Convert base64 back to blob 314 const generatedBlob = this.base64ToBlob(result.audioData, 'audio/wav'); 315 this.generatedAudio = generatedBlob; 316 317 // Debug: Log blob details 318 console.log('Generated blob details:', { 319 size: generatedBlob.size, 320 type: generatedBlob.type, 321 isMock: result.isMock 322 }); 323 324 this.updateProgress(100, 'Complete!'); 325 326 // Update UI - give user time to see 100% completion 327 setTimeout(async () => { 328 this.updateProcessingUI(false); 329 this.showRecordingStatus('Music generated successfully!', 'success'); 330 this.updatePlayButton(true); 331 332 // Auto-play the generated audio 333 console.log('ðµ Auto-playing generated audio...'); 334 try { 335 await this.playGeneratedAudio(); 336 } catch (error) { 337 console.warn('Auto-play failed, user can click play button:', error); 338 this.showMessage('Audio ready! Click the play button to listen.', 'info'); 339 } 340 341 // Hide progress bar after a delay so user can see completion 342 setTimeout(() => { 343 this.hideProgress(); 344 }, 2000); 345 }, 1000); 346 347 console.log('Music generated successfully'); 348 349 } catch (error) { 350 console.error('Failed to generate music:', error); 351 this.showError(`Failed to generate music: ${error.message}`); 352 this.updateProcessingUI(false); 353 this.hideProgress(); 354 } 355 } 356 357 // Helper function to convert audio blob to base64 358 async audioBlobToBase64(audioBlob) { 359 return new Promise((resolve, reject) => { 360 const reader = new FileReader(); 361 reader.onload = () => { 362 const base64 = reader.result.split(',')[1]; // Remove data:audio/wav;base64, prefix 363 resolve(base64); 364 }; 365 reader.onerror = reject; 366 reader.readAsDataURL(audioBlob); 367 }); 368 } 369 370 // Helper function to convert base64 to blob 371 base64ToBlob(base64, mimeType) { 372 const byteCharacters = atob(base64); 373 const byteNumbers = new Array(byteCharacters.length); 374 for (let i = 0; i < byteCharacters.length; i++) {
375 byteNumbers[i] = byteCharacters.charCodeAt(i); 376 } 377 const byteArray = new Uint8Array(byteNumbers); 378 return new Blob([byteArray], { type: mimeType }); 379 } 380 381 // Test if an audio blob can be played 382 testAudioBlob(blob) { 383 try { 384 const audio = new Audio(); 385 const url = URL.createObjectURL(blob); 386 audio.src = url; 387 388 // Check if the browser can handle this format 389 const canPlay = audio.canPlayType(blob.type); 390 URL.revokeObjectURL(url); 391 392 return canPlay !== ''; 393 } catch (error) { 394 console.warn('Error testing audio blob:', error); 395 return false; 396 } 397 } 398 399 // Fallback audio generation using Web Audio API 400 tryFallbackAudio() { 401 try { 402 console.log('Creating fallback audio using Web Audio API...'); 403 404 // Create a simple sine wave using Web Audio API 405 const sampleRate = 44100; 406 const duration = 2; // 2 seconds 407 const frequency = 440; // A4 note 408 409 const audioContext = new (window.AudioContext || window.webkitAudioContext)(); 410 const buffer = audioContext.createBuffer(1, sampleRate * duration, sampleRate); 411 const data = buffer.getChannelData(0); 412 413 // Generate sine wave 414 for (let i = 0; i < data.length; i++) { 415 data[i] = Math.sin(2 * Math.PI * frequency * i / sampleRate) * 0.3; 416 } 417 418 // Create audio source 419 const source = audioContext.createBufferSource(); 420 source.buffer = buffer; 421 source.connect(audioContext.destination); 422 423 // Play the audio 424 source.start(); 425 426 this.isPlaying = true; 427 this.updatePlayButton(true); 428 429 // Stop after duration 430 setTimeout(() => { 431 source.stop(); 432 this.isPlaying = false; 433 this.updatePlayButton(false); 434 }, duration * 1000); 435 436 console.log('â Fallback audio playing successfully'); 437 438 } catch (error) { 439 console.error('â Fallback audio failed:', error); 440 this.showError('Failed to play generated audio. Please try again.'); 441 this.isPlaying = false; 442 this.updatePlayButton(false); 443 } 444 } 445 446 // Update progress bar 447 updateProgress(percentage, text) { 448 const progressFill = document.getElementById('progressFill'); 449 const progressText = document.getElementById('progressText'); 450 const progressContainer = document.getElementById('progressContainer'); 451 452 if (progressFill) { 453 progressFill.style.width = `${percentage}%`; 454 } 455 if (progressText) { 456 progressText.textContent = text; 457 } 458 if (progressContainer) { 459 progressContainer.style.display = 'block'; 460 } 461 } 462 463 // Hide progress bar 464 hideProgress() { 465 const progressContainer = document.getElementById('progressContainer'); 466 if (progressContainer) { 467 setTimeout(() => { 468 progressContainer.style.display = 'none'; 469 }, 1000); 470 } 471 } 472 473 createMusicGenPrompt() { 474 const instrumentPrompts = { 475 'piano': 'Generate a beautiful piano melody based on the provided audio input. Focus on the melody and harmony.', 476 'guitar': 'Create an acoustic guitar arrangement that follows the melody and rhythm of the input audio.', 477 'violin': 'Generate a violin melody that captures the emotional essence of the input audio.', 478 'synth': 'Create a modern synthesizer track with electronic sounds based on the input audio.', 479 'drums': 'Generate ONLY a drum track with kick, snare, hi-hats, and cymbals based on the beatboxing rhythm. NO other instruments.', 480 'bass': 'Create a bass line that follows the rhythm and complements the melody of the input audio.' 481 }; 482 483 return instrumentPrompts[this.currentInstrument] || instrumentPrompts['piano']; 484 } 485 486 async playGeneratedAudio() { 487 if (!this.generatedAudio) { 488 this.showError('No generated audio available. Please record and generate music first.'); 489 return; 490 } 491 492 try { 493 if (this.isPlaying) { 494 this.stopAudio(); 495 return; 496 } 497 498 console.log('ðµ Starting audio playback...'); 499 console.log('Generated audio blob:', this.generatedAudio); 500 console.log('Audio blob size:', this.generatedAudio.size, 'bytes'); 501 console.log('Audio blob type:', this.generatedAudio.type); 502 503 // Create audio element for playback 504 const audioUrl = URL.createObjectURL(this.generatedAudio); 505 const audio = new Audio(); 506 507 // Set audio properties 508 audio.volume = 0.8; 509 audio.preload = 'auto'; 510 511 // Add event listeners 512 audio.addEventListener('loadstart', () => console.log('Audio load started')); 513 audio.addEventListener('loadeddata', () => console.log('Audio data loaded')); 514 audio.addEventListener('canplay', () => console.log('Audio can play')); 515 audio.addEventListener('canplaythrough', () => console.log('Audio can play through')); 516 517 audio.onended = () => { 518 console.log('Audio playback ended'); 519 this.isPlaying = false;
520 this.updatePlayButton(false); 521 URL.revokeObjectURL(audioUrl); 522 }; 523 524 audio.onerror = (error) => { 525 console.error('Audio playback error:', error); 526 console.error('Audio error details:', { 527 error: error, 528 src: audio.src, 529 networkState: audio.networkState, 530 readyState: audio.readyState, 531 errorCode: audio.error ? audio.error.code : 'no error code', 532 errorMessage: audio.error ? audio.error.message : 'no error message' 533 }); 534 535 // Log the actual error object properties 536 if (audio.error) { 537 console.error('Audio error object:', { 538 code: audio.error.code, 539 message: audio.error.message, 540 MEDIA_ERR_ABORTED: audio.error.MEDIA_ERR_ABORTED, 541 MEDIA_ERR_NETWORK: audio.error.MEDIA_ERR_NETWORK, 542 MEDIA_ERR_DECODE: audio.error.MEDIA_ERR_DECODE, 543 MEDIA_ERR_SRC_NOT_SUPPORTED: audio.error.MEDIA_ERR_SRC_NOT_SUPPORTED 544 }); 545 } 546 547 // Try fallback: create a simple test audio 548 console.log('Trying fallback audio generation...'); 549 this.tryFallbackAudio(); 550 }; 551 552 // Ensure audio context is resumed for user interaction 553 if (this.audioContext && this.audioContext.state === 'suspended') { 554 console.log('Resuming audio context...'); 555 await this.audioContext.resume(); 556 } 557 558 // Set the source and wait for it to load 559 audio.src = audioUrl; 560 561 // Wait for audio to be ready 562 console.log('Waiting for audio to load...'); 563 await new Promise((resolve, reject) => { 564 const timeout = setTimeout(() => { 565 reject(new Error('Audio load timeout')); 566 }, 10000); // 10 second timeout 567 568 audio.addEventListener('canplay', () => { 569 clearTimeout(timeout); 570 console.log('Audio is ready to play'); 571 resolve(); 572 }, { once: true }); 573 574 audio.addEventListener('error', (error) => { 575 clearTimeout(timeout); 576 console.error('Audio load error:', error); 577 reject(error); 578 }, { once: true }); 579 }); 580 581 // Try to play the audio 582 console.log('Attempting to play audio...', audio); 583 console.log('Audio ready state:', audio.readyState); 584 console.log('Audio network state:', audio.networkState); 585 586 const playPromise = audio.play(); 587 588 if (playPromise !== undefined) { 589 await playPromise; 590 } 591 592 this.isPlaying = true; 593 this.updatePlayButton(true); 594 595 console.log('â Playing generated audio successfully'); 596 597 } catch (error) { 598 console.error('â Failed to play audio:', error); 599 this.showError(`Failed to play generated audio: ${error.message}`); 600 } 601 } 602 603 stopAudio() { 604 // Stop any currently playing audio 605 const audioElements = document.querySelectorAll('audio'); 606 audioElements.forEach(audio => { 607 audio.pause(); 608 audio.currentTime = 0; 609 }); 610 611 this.isPlaying = false; 612 this.updatePlayButton(false); 613 } 614 615 selectInstrument(instrument) { 616 console.log(`ðµ Selecting instrument: ${instrument} (was: ${this.currentInstrument})`); 617 this.currentInstrument = instrument; 618 619 // Update UI 620 document.querySelectorAll('.instrument-btn').forEach(btn => { 621 btn.classList.remove('active', 'drum-only'); 622 }); 623 624 const selectedBtn = document.querySelector(`[data-instrument="${instrument}"]`); 625 selectedBtn.classList.add('active'); 626 627 // Add special styling for drums to emphasize drums-only output 628 if (instrument === 'drums') { 629 selectedBtn.classList.add('drum-only'); 630 } 631 632 console.log(`Selected instrument: ${instrument}`); 633 634 // Show special message for drums 635 if (instrument === 'drums') { 636 this.showMessage('Drum mode selected - Only percussion instruments will be generated!', 'info'); 637 } 638 } 639 640 initAudioVisualizer() { 641 const canvas = document.getElementById('waveformCanvas'); 642 if (!canvas) return; 643 644 this.audioVisualizer = { 645 canvas: canvas, 646 ctx: canvas.getContext('2d'), 647 width: canvas.width, 648 height: canvas.height 649 }; 650 } 651 652 startAudioVisualization(stream) { 653 if (!this.audioVisualizer) return; 654 655 const analyser = this.audioContext.createAnalyser(); 656 const source = this.audioContext.createMediaStreamSource(stream); 657 source.connect(analyser); 658 659 analyser.fftSize = 256; 660 const bufferLength = analyser.frequencyBinCount; 661 const dataArray = new Uint8Array(bufferLength); 662 663 const draw = () => { 664 if (!this.isRecording) return; 665 666 this.animationId = requestAnimationFrame(draw); 667 analyser.getByteFrequencyData(dataArray); 668 669 const ctx = this.audioVisualizer.ctx; 670 const width = this.audioVisualizer.width; 671 const height = this.audioVisualizer.height; 672 673 // Clear canvas 674 ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; 675 ctx.fillRect(0, 0, width, height); 676 677 // Draw waveform 678 ctx.lineWidth = 2; 679 ctx.strokeStyle = '#ff6b35'; 680 ctx.beginPath(); 681 682 const sliceWidth = width / bufferLength; 683 let x = 0; 684 685 for (let i = 0; i < bufferLength; i++) {
686 const v = dataArray[i] / 255.0; 687 const y = (v * height) / 2; 688 689 if (i === 0) { 690 ctx.moveTo(x, y); 691 } else { 692 ctx.lineTo(x, y); 693 } 694 695 x += sliceWidth; 696 } 697 698 ctx.lineTo(width, height / 2); 699 ctx.stroke(); 700 }; 701 702 draw(); 703 } 704 705 stopAudioVisualization() { 706 if (this.animationId) { 707 cancelAnimationFrame(this.animationId); 708 this.animationId = null; 709 } 710 711 // Clear canvas 712 if (this.audioVisualizer) { 713 const ctx = this.audioVisualizer.ctx; 714 const width = this.audioVisualizer.width; 715 const height = this.audioVisualizer.height; 716 717 ctx.clearRect(0, 0, width, height); 718 } 719 } 720 721 updateRecordingUI(isRecording) { 722 const recordBtn = document.getElementById('recordBtn'); 723 const stopBtn = document.getElementById('stopBtn'); 724 const playBtn = document.getElementById('playBtn'); 725 726 if (recordBtn) { 727 recordBtn.disabled = isRecording; 728 recordBtn.classList.toggle('recording', isRecording); 729 } 730 if (stopBtn) { 731 stopBtn.disabled = !isRecording; 732 } 733 if (playBtn) { 734 playBtn.disabled = true; 735 } 736 } 737 738 updateProcessingUI(isProcessing) { 739 const recordBtn = document.getElementById('recordBtn'); 740 const stopBtn = document.getElementById('stopBtn'); 741 const playBtn = document.getElementById('playBtn'); 742 743 if (recordBtn) { 744 recordBtn.disabled = isProcessing; 745 } 746 if (stopBtn) { 747 stopBtn.disabled = isProcessing; 748 } 749 if (playBtn) { 750 playBtn.disabled = isProcessing; 751 } 752 } 753 754 updatePlayButton(isPlaying) { 755 const playBtn = document.getElementById('playBtn'); 756 if (!playBtn) return; 757 758 const icon = playBtn.querySelector('i'); 759 const text = playBtn.querySelector('span'); 760 761 if (isPlaying) { 762 icon.className = 'fas fa-stop'; 763 text.textContent = 'Stop Playing'; 764 } else { 765 icon.className = 'fas fa-play'; 766 text.textContent = 'Play Converted'; 767 } 768 } 769 770 showRecordingStatus(message, type = 'default') { 771 const statusElement = document.getElementById('recordingStatus'); 772 if (!statusElement) return; 773 774 const statusText = statusElement.querySelector('span'); 775 const statusIndicator = statusElement.querySelector('.status-indicator'); 776 777 if (statusText) { 778 statusText.textContent = message; 779 } 780 781 if (statusIndicator) { 782 statusIndicator.className = `status-indicator ${type}`; 783 } 784 } 785 786 showProgress(percentage) { 787 // Create or update progress bar 788 let progressBar = document.getElementById('progressBar'); 789 if (!progressBar) { 790 progressBar = document.createElement('div'); 791 progressBar.id = 'progressBar'; 792 progressBar.style.cssText = ` 793 position: fixed; 794 top: 50%; 795 left: 50%; 796 transform: translate(-50%, -50%); 797 background: rgba(0, 0, 0, 0.9); 798 color: white; 799 padding: 20px; 800 border-radius: 10px; 801 z-index: 10000; 802 text-align: center; 803 `; 804 document.body.appendChild(progressBar); 805 } 806 807 progressBar.innerHTML = ` 808 <div style="margin-bottom: 10px;">Generating music... ${Math.round(percentage)}%</div> 809 <div style="width: 200px; height: 4px; background: #333; border-radius: 2px; overflow: hidden;"> 810 <div style="width: ${percentage}%; height: 100%; background: #ff6b35; transition: width 0.3s ease;"></div> 811 </div> 812 `; 813 814 if (percentage >= 100) { 815 setTimeout(() => { 816 if (progressBar.parentNode) { 817 progressBar.parentNode.removeChild(progressBar); 818 } 819 }, 1000); 820 } 821 } 822 823 showError(message) { 824 this.showMessage(message, 'error'); 825 } 826 827 showMessage(message, type = 'info') { 828 // Create message container if it doesn't exist 829 let messageContainer = document.getElementById('messageContainer'); 830 if (!messageContainer) { 831 messageContainer = document.createElement('div'); 832 messageContainer.id = 'messageContainer'; 833 messageContainer.className = 'message-container'; 834 document.body.appendChild(messageContainer); 835 } 836 837 // Create message element 838 const messageDiv = document.createElement('div'); 839 messageDiv.className = `message ${type}`; 840 messageDiv.textContent = message; 841 842 // Add to container 843 messageContainer.appendChild(messageDiv); 844 845 // Auto-remove after 5 seconds 846 setTimeout(() => { 847 if (messageDiv.parentNode) { 848 messageDiv.parentNode.removeChild(messageDiv); 849 } 850 }, 5000); 851 } 852 853 showDemoMode() { 854 // Show demo with sample audio 855 this.showRecordingStatus('Demo mode - Click record to try the system!', 'demo'); 856 857 // You can add demo functionality here 858 console.log('Demo mode activated'); 859 } 860} 861 862// Initialize MusicGen Integration when DOM is loaded 863document.addEventListener('DOMContentLoaded', () => { 864 window.musicGenIntegration = new MusicGenIntegration(); 865}); 866 867// Export for use in other scripts 868if (typeof module !== 'undefined' && module.exports) { 869 module.exports = MusicGenIntegration; 870}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.