PageSourceSearch

https://humgod.vercel.app/musicgen-integration.js?v=7.0-force-deploy-1735471000

js humgod.vercel.app collected 2026-10-03 07:05:39 UTC 31,409 bytes, 870 lines download raw bytes

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.