Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import random | |
| import re | |
| import json | |
| # --- Strudel Code Generation and Playback Functions --- | |
| def generate_simple_beat(): | |
| """Generates a very simple Strudel beat as a starting point.""" | |
| code = '''// 🎵 Simple Voice-Generated Beat | |
| // Try saying: "Make a beat" or "Play music" | |
| // Then click PLAY or press Ctrl+Enter! | |
| stack( | |
| sound("bd").struct("x ~ x ~").gain(0.8), | |
| sound("sd").struct("~ x ~ x").gain(0.7), | |
| sound("hh").struct("x x x x").gain(0.4) | |
| ).cpm(120)''' | |
| return "✅ Simple beat generated!", code | |
| def clear_code(): | |
| """Clears the Strudel code editor to a default state.""" | |
| initial_code = '''// 🎵 Voice Strudel Synth | |
| // Speak a command to generate music! | |
| // Try: "Make a beat" | |
| // Then: "Play music" (or Ctrl+Enter) | |
| stack( | |
| // Your generated code will appear here... | |
| ).cpm(120) // Master tempo | |
| ''' | |
| return "🗑️ Code cleared - Ready for new ideas!", initial_code | |
| # --- Voice Command Processing --- | |
| def process_voice_command(command_text, current_code): | |
| """ | |
| Processes a voice command to generate or control Strudel code. | |
| This version is highly simplified for bare-minimum functionality. | |
| """ | |
| if not command_text: | |
| return "❌ No voice command received.", current_code | |
| command_lower = command_text.lower() | |
| if any(word in command_lower for word in ["make a beat", "generate beat", "create music", "new beat"]): | |
| return generate_simple_beat() | |
| elif "play music" in command_lower or "start music" in command_lower: | |
| # This message will instruct the user to use the play button/shortcut | |
| return "▶️ Click PLAY MUSIC or press Ctrl+Enter to hear!", current_code | |
| elif "stop music" in command_lower: | |
| # This message will instruct the user to use the stop button/shortcut | |
| return "⏹️ Click STOP MUSIC or press Ctrl+Space!", current_code | |
| elif "clear code" in command_lower or "reset" in command_lower or "start over" in command_lower: | |
| return clear_code() | |
| else: | |
| return "🤔 Command not recognized. Try: 'Make a beat', 'Play music', 'Stop music', 'Clear code'.", current_code | |
| # --- Gradio Interface Setup --- | |
| def create_interface(): | |
| # --- Simplified CSS for a clean, futuristic look --- | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap'); | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap'); | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| overflow: hidden; /* Hide scrollbars due to matrix rain */ | |
| background: #000; | |
| } | |
| .gradio-container { | |
| background: linear-gradient(135deg, #0a0a0a, #1a1a1a); | |
| color: #00ff00; | |
| font-family: 'Share Tech Mono', monospace !important; | |
| min-height: 100vh; | |
| border-radius: 15px; | |
| box-shadow: 0 0 50px rgba(0, 255, 0, 0.3); | |
| padding: 20px; | |
| position: relative; | |
| z-index: 1; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| h1 { | |
| font-size: 3.5em; | |
| color: #00ff00; | |
| text-shadow: 0 0 15px rgba(0, 255, 0, 0.7); | |
| animation: neon-flicker 1.5s infinite alternate; | |
| text-align: center; | |
| margin-bottom: 5px; | |
| } | |
| p { | |
| color: #00ee00; | |
| font-size: 1.5em; | |
| text-shadow: 0 0 10px #00ff00; | |
| text-align: center; | |
| margin-top: 0; | |
| margin-bottom: 30px; | |
| } | |
| @keyframes neon-flicker { | |
| 0% { opacity: 1; text-shadow: 0 0 15px rgba(0, 255, 0, 0.7); } | |
| 100% { opacity: 0.9; text-shadow: 0 0 20px rgba(0, 255, 0, 0.9), 0 0 30px rgba(0, 255, 0, 0.5); } | |
| } | |
| .gr-textbox, .gr-code { | |
| background: rgba(0, 10, 0, 0.7) !important; | |
| border: 1px solid #00ff00 !important; | |
| color: #00ff41 !important; | |
| border-radius: 10px !important; | |
| box-shadow: inset 0 0 8px rgba(0, 255, 0, 0.3); | |
| padding: 15px; | |
| margin-bottom: 20px; | |
| width: 100%; | |
| } | |
| .gr-code textarea { | |
| background: rgba(0, 0, 0, 0.9) !important; | |
| color: #00ff41 !important; | |
| font-family: 'Fira Code', 'Share Tech Mono', monospace !important; | |
| font-size: 15px !important; | |
| line-height: 1.4; | |
| } | |
| .gr-button { | |
| background: linear-gradient(45deg, #008800, #00bb00) !important; | |
| border: 2px solid #33ff33 !important; | |
| color: #00ff00 !important; | |
| font-weight: bold !important; | |
| text-shadow: 0 0 10px #00ff00 !important; | |
| border-radius: 8px !important; | |
| transition: all 0.2s ease-in-out !important; | |
| box-shadow: 0 0 10px rgba(51, 255, 51, 0.7); | |
| padding: 12px 25px; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| font-size: 1.1em; | |
| margin: 5px; | |
| } | |
| .gr-button:hover { | |
| background: linear-gradient(45deg, #00bb00, #00ee00) !important; | |
| box-shadow: 0 0 30px rgba(51, 255, 51, 1); | |
| transform: translateY(-2px) scale(1.02); | |
| } | |
| #voice-status { | |
| background: rgba(0, 30, 0, 0.5) !important; | |
| border: 2px solid #00ff00 !important; | |
| border-radius: 12px !important; | |
| padding: 15px !important; | |
| text-align: center; | |
| font-size: 1.2em; | |
| font-weight: bold; | |
| box-shadow: 0 0 15px rgba(0, 255, 0, 0.5); | |
| animation: pulse 2s infinite alternate; | |
| } | |
| .listening { | |
| animation: listening-pulse 1s infinite alternate !important; | |
| background: rgba(100, 0, 0, 0.3) !important; | |
| border-color: #ff0000 !important; | |
| box-shadow: 0 0 25px rgba(255, 0, 0, 0.7) !important; | |
| } | |
| @keyframes pulse { | |
| 0% { opacity: 1; box-shadow: 0 0 15px rgba(0, 255, 0, 0.5); } | |
| 100% { opacity: 0.8; box-shadow: 0 0 25px rgba(0, 255, 0, 0.8); } | |
| } | |
| @keyframes listening-pulse { | |
| 0% { box-shadow: 0 0 15px #ff0000; } | |
| 100% { box-shadow: 0 0 40px #ff0000; } | |
| } | |
| .instructions { | |
| background: rgba(0, 15, 0, 0.6) !important; | |
| border: 1px dashed #00ff00 !important; | |
| border-radius: 15px !important; | |
| padding: 25px !important; | |
| margin: 30px auto; /* Centered */ | |
| max-width: 800px; | |
| box-shadow: 0 0 20px rgba(0, 255, 0, 0.2); | |
| } | |
| .instructions h3 { | |
| color: #33ff33; | |
| text-shadow: none; | |
| text-align: center; | |
| margin-bottom: 15px; | |
| } | |
| .instructions ul { | |
| list-style: none; | |
| padding-left: 0; | |
| margin-bottom: 15px; | |
| } | |
| .instructions ul li::before { | |
| content: '» '; | |
| color: #00ff00; | |
| font-weight: bold; | |
| margin-right: 5px; | |
| } | |
| .instructions p { | |
| font-size: 1em; | |
| color: #ccffcc; | |
| text-shadow: none; | |
| } | |
| #matrix-canvas { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100vw; | |
| height: 100vh; | |
| background: radial-gradient(ellipse at center, rgba(0,20,0,0.8) 0%, rgba(0,0,0,0.9) 100%); | |
| z-index: -2; | |
| pointer-events: none; | |
| } | |
| .gr-row, .gr-column { | |
| width: 100%; /* Make rows/columns take full width */ | |
| max-width: 900px; /* Constrain main content width */ | |
| margin: 0 auto; /* Center content */ | |
| } | |
| .main-controls { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| margin-bottom: 20px; | |
| } | |
| .control-buttons { | |
| display: flex; | |
| justify-content: center; | |
| width: 100%; | |
| } | |
| """ | |
| # --- JavaScript for Voice Recognition and Strudel.js Integration --- | |
| voice_strudel_js = """ | |
| function() { | |
| console.log('🎵 Initializing Voice Recognition and Strudel.js...'); | |
| let recognition = null; | |
| let isListening = false; | |
| let strudelPlayer = null; | |
| let strudelReady = false; | |
| // Initialize Strudel.js Player | |
| async function initStrudel() { | |
| if (strudelReady) { | |
| console.log("🎶 Strudel.js already initialized."); | |
| return true; | |
| } | |
| try { | |
| // Using a direct import from unpkg for the 'strudel' package itself | |
| const { default: Strudel } = await import('https://unpkg.com/strudel/strudel.js'); | |
| // Create or resume AudioContext | |
| if (!window.audioContext || window.audioContext.state === 'closed') { | |
| window.audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| console.log("New AudioContext created."); | |
| } | |
| // Attempt to resume audio context, crucial for autoplay policies | |
| if (window.audioContext.state === 'suspended') { | |
| await window.audioContext.resume(); | |
| console.log("AudioContext resumed during initialization."); | |
| } | |
| strudelPlayer = Strudel.Player({ audioContext: window.audioContext }); // Pass the existing context | |
| strudelReady = true; | |
| console.log("🎶 Strudel.js player created and initialized successfully!"); | |
| return true; | |
| } catch (error) { | |
| console.error("Failed to load or initialize Strudel.js:", error); | |
| const statusElement = document.querySelector('#audio-status textarea'); | |
| if (statusElement) { | |
| statusElement.value = `❌ Audio Engine Error: ${error.message}. Check browser console for details.`; | |
| } | |
| return false; | |
| } | |
| } | |
| // Check if browser supports speech recognition | |
| if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) { | |
| const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; | |
| recognition = new SpeechRecognition(); | |
| recognition.continuous = false; | |
| recognition.interimResults = false; | |
| recognition.lang = 'en-US'; | |
| recognition.maxAlternatives = 1; | |
| console.log('✅ Speech Recognition Available'); | |
| } else { | |
| console.log('❌ Speech Recognition Not Available. Please use a Chromium-based browser (Chrome, Edge).'); | |
| } | |
| // Matrix rain effect (simplified and integrated) | |
| function createMatrixRain() { | |
| const existingCanvas = document.querySelector('#matrix-canvas'); | |
| if (existingCanvas) existingCanvas.remove(); | |
| const canvas = document.createElement('canvas'); | |
| canvas.id = 'matrix-canvas'; | |
| canvas.style.cssText = ` | |
| position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; | |
| background: radial-gradient(ellipse at center, rgba(0,20,0,0.8) 0%, rgba(0,0,0,0.9) 100%); | |
| z-index: -2; pointer-events: none; | |
| `; | |
| document.body.appendChild(canvas); | |
| const ctx = canvas.getContext('2d'); | |
| const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789♪♫♬♩🎵🎶⚡🔊🎤🎧'; | |
| const fontSize = 16; | |
| let columns, drops; | |
| function resizeCanvas() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| columns = Math.floor(canvas.width / fontSize); | |
| drops = Array(columns).fill(1); | |
| } | |
| resizeCanvas(); | |
| window.addEventListener('resize', resizeCanvas); | |
| function draw() { | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.08)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); | |
| gradient.addColorStop(0, '#00ff41'); | |
| gradient.addColorStop(0.5, '#00aa00'); | |
| gradient.addColorStop(1, 'rgba(0, 255, 0, 0.1)'); | |
| ctx.fillStyle = gradient; | |
| ctx.font = `${fontSize}px 'Share Tech Mono', monospace`; | |
| for (let i = 0; i < drops.length; i++) { | |
| const text = chars[Math.floor(Math.random() * chars.length)]; | |
| const x = i * fontSize; | |
| const y = drops[i] * fontSize; | |
| if (text.match(/[♪♫♬♩🎵🎶⚡🔊🎤🎧]/)) { | |
| ctx.shadowColor = '#00ff41'; ctx.shadowBlur = 20; | |
| } else { ctx.shadowBlur = 0; } | |
| ctx.fillText(text, x, y); | |
| if (y * fontSize > canvas.height && Math.random() > 0.975) { | |
| drops[i] = 0; | |
| } | |
| drops[i]++; | |
| } | |
| } | |
| setInterval(draw, 60); | |
| } | |
| createMatrixRain(); // Initialize matrix effect | |
| // Start voice recognition | |
| window.startVoiceRecognition = function() { | |
| if (!recognition) { | |
| return "❌ Speech recognition not supported in this browser. Try Chrome/Edge."; | |
| } | |
| if (isListening) { | |
| return "🎤 Already listening..."; | |
| } | |
| isListening = true; | |
| recognition.onstart = function() { | |
| console.log('🎤 Voice recognition started'); | |
| const statusElement = document.querySelector('#voice-status textarea'); | |
| if (statusElement) { | |
| statusElement.value = "🎤 LISTENING... Speak your command now!"; | |
| statusElement.parentElement.classList.add('listening'); | |
| } | |
| }; | |
| recognition.onresult = function(event) { | |
| const transcript = event.results[0][0].transcript; | |
| console.log('🗣️ Voice command:', transcript); | |
| const voiceInputs = document.querySelectorAll('textarea'); | |
| for (let input of voiceInputs) { | |
| if (input.placeholder && input.placeholder.includes('Voice commands')) { | |
| input.value = transcript; | |
| input.dispatchEvent(new Event('input', { bubbles: true })); | |
| break; | |
| } | |
| } | |
| setTimeout(() => { | |
| const processBtn = Array.from(document.querySelectorAll('button')).find( | |
| button => button.textContent.includes('PROCESS') || button.querySelector('span')?.textContent.includes('PROCESS') | |
| ); | |
| if (processBtn) processBtn.click(); | |
| }, 100); | |
| }; | |
| recognition.onerror = function(event) { | |
| console.error('🚫 Voice recognition error:', event.error); | |
| const statusElement = document.querySelector('#voice-status textarea'); | |
| if (statusElement) { | |
| statusElement.value = `❌ Error: ${event.error}. Please try again.`; | |
| statusElement.parentElement.classList.remove('listening'); | |
| } | |
| isListening = false; | |
| }; | |
| recognition.onend = function() { | |
| console.log('🎤 Voice recognition ended'); | |
| const statusElement = document.querySelector('#voice-status textarea'); | |
| if (statusElement) { | |
| if (statusElement.value.includes('LISTENING')) { | |
| statusElement.value = "✅ Voice command captured! Processing..."; | |
| } | |
| statusElement.parentElement.classList.remove('listening'); | |
| } | |
| isListening = false; | |
| }; | |
| try { | |
| recognition.start(); | |
| return "🎤 Voice recognition started - speak now!"; | |
| } catch (error) { | |
| console.error('Failed to start recognition:', error); | |
| isListening = false; | |
| return "❌ Failed to start voice recognition"; | |
| } | |
| }; | |
| // Stop voice recognition | |
| window.stopVoiceRecognition = function() { | |
| if (recognition && isListening) { | |
| recognition.stop(); | |
| isListening = false; | |
| return "⏹️ Voice recognition stopped"; | |
| } | |
| return "⏹️ Voice recognition not active"; | |
| }; | |
| // --- Strudel Playback Functions --- | |
| window.playStrudelCode = async function(code) { | |
| const isInitialized = await initStrudel(); | |
| if (!isInitialized) { | |
| return "❌ Strudel audio engine could not be initialized."; | |
| } | |
| try { | |
| if (strudelPlayer && strudelPlayer.stop) { | |
| strudelPlayer.stop(); | |
| console.log("Previous Strudel pattern stopped."); | |
| } | |
| if (window.audioContext && window.audioContext.state === 'suspended') { | |
| await window.audioContext.resume(); | |
| console.log("AudioContext resumed on user interaction for playback."); | |
| } | |
| if (strudelPlayer && strudelPlayer.setSynth && strudelPlayer.play) { | |
| await strudelPlayer.setSynth(code); | |
| strudelPlayer.play(); | |
| console.log("▶️ Playing Strudel code."); | |
| return "▶️ Playing music..."; | |
| } else { | |
| console.error("Strudel player methods (setSynth, play) not available."); | |
| return "❌ Strudel player not fully ready for playback."; | |
| } | |
| } catch (error) { | |
| console.error("Error playing Strudel code:", error); | |
| let errorMessage = `❌ Audio Error: ${error.message}.`; | |
| if (error.message.includes("Unexpected token") || error.message.includes("SyntaxError")) { | |
| errorMessage = "❌ Strudel code syntax error. Check code!"; | |
| } else if (error.message.includes("Failed to load module")) { | |
| errorMessage = "❌ Strudel.js library failed to load. Check browser console for network/CORS issues."; | |
| } else if (error.message.includes("AudioContext") || error.message.includes("Web Audio API")) { | |
| errorMessage = "❌ Browser audio engine issue. Try refreshing."; | |
| } | |
| return errorMessage; | |
| } | |
| }; | |
| window.stopStrudelCode = function() { | |
| if (strudelPlayer && strudelReady && strudelPlayer.stop) { | |
| strudelPlayer.stop(); | |
| console.log("⏹️ Strudel music stopped."); | |
| return "⏹️ Music stopped"; | |
| } | |
| return "⏹️ Music not active"; | |
| }; | |
| // Add keyboard shortcuts | |
| document.addEventListener('keydown', function(e) { | |
| if (e.ctrlKey || e.metaKey) { // Ctrl for Windows/Linux, Cmd for Mac | |
| switch(e.key.toLowerCase()) { | |
| case 'enter': | |
| e.preventDefault(); | |
| const playBtn = Array.from(document.querySelectorAll('button')).find( | |
| button => button.textContent.includes('PLAY') || button.querySelector('span')?.textContent.includes('PLAY') | |
| ); | |
| if (playBtn) playBtn.click(); | |
| break; | |
| case ' ': | |
| e.preventDefault(); | |
| const stopBtn = Array.from(document.querySelectorAll('button')).find( | |
| button => button.textContent.includes('STOP') || button.querySelector('span')?.textContent.includes('STOP') | |
| ); | |
| if (stopBtn) stopBtn.click(); | |
| break; | |
| case 'm': | |
| e.preventDefault(); | |
| window.startVoiceRecognition(); | |
| break; | |
| } | |
| } | |
| }); | |
| console.log('🎵 Voice-Controlled Strudel Generator Ready!'); | |
| console.log('🎼 Shortcuts: Ctrl+Enter (Play), Ctrl+Space (Stop), Ctrl+M (Voice)'); | |
| return "🎵 Real Voice Recognition and Strudel.js Initialized!"; | |
| } | |
| """ | |
| # --- Gradio Interface Layout --- | |
| with gr.Blocks(css=custom_css, js=voice_strudel_js, title="🎵 Voice Strudel Synth") as interface: | |
| # Header Section | |
| gr.HTML(""" | |
| <div style="text-align: center; padding: 20px;"> | |
| <h1>🎵 VOICE STRUDEL SYNTH</h1> | |
| <p>Speak Your Beats Into Existence — Code, See, Hear!</p> | |
| </div> | |
| """) | |
| with gr.Column(elem_classes="main-controls"): | |
| # Voice Input and Status | |
| voice_status = gr.Textbox( | |
| value="🎤 Ready - Click 'Start Voice' or Ctrl+M!", | |
| label="VOICE STATUS", | |
| elem_id="voice-status", | |
| interactive=False, | |
| max_lines=2 | |
| ) | |
| with gr.Row(elem_classes="control-buttons"): | |
| start_voice_btn = gr.Button("🎤 START VOICE", variant="primary") | |
| stop_voice_btn = gr.Button("⏹️ STOP VOICE", variant="secondary") | |
| voice_input = gr.Textbox( | |
| label="🗣️ CAPTURED VOICE COMMAND", | |
| placeholder="Voice commands appear here automatically...", | |
| lines=1, # Simplified to 1 line for minimal display | |
| interactive=False | |
| ) | |
| process_voice_btn = gr.Button("🎯 PROCESS VOICE COMMAND", variant="primary") | |
| gr.Markdown("---") # Separator | |
| # Audio Status and Controls | |
| audio_status = gr.Textbox( | |
| value="⏹️ Ready to play", | |
| label="🔊 AUDIO STATUS", | |
| interactive=False, | |
| max_lines=2 | |
| ) | |
| with gr.Row(elem_classes="control-buttons"): | |
| play_btn = gr.Button("▶️ PLAY MUSIC", variant="primary") | |
| stop_btn = gr.Button("⏹️ STOP MUSIC", variant="secondary") | |
| # Strudel Code Editor | |
| music_code = gr.Code( | |
| value='''// 🎵 Voice Strudel Synth | |
| // Speak a command to generate music! | |
| // Try: "Make a beat" | |
| // Then: "Play music" (or Ctrl+Enter) | |
| stack( | |
| // Your generated code will appear here... | |
| ).cpm(120) // Master tempo | |
| ''', | |
| label="🎼 STRUDEL CODE EDITOR", | |
| language="javascript", # Strudel is JavaScript-like | |
| lines=15, # Reduced lines for more compact view | |
| interactive=True | |
| ) | |
| # Instructions Section (Simplified) | |
| gr.HTML(""" | |
| <div class="instructions"> | |
| <h3>HOW TO USE</h3> | |
| <ul> | |
| <li>Click <strong>"START VOICE"</strong> or press <strong>Ctrl+M</strong>.</li> | |
| <li>Say: <strong>"Make a beat"</strong></li> | |
| <li>Then: <strong>"Play music"</strong> (or click <strong>"PLAY MUSIC"</strong> / <strong>Ctrl+Enter</strong>)</li> | |
| <li>To stop: <strong>"Stop music"</strong> (or click <strong>"STOP MUSIC"</strong> / <strong>Ctrl+Space</strong>)</li> | |
| <li>To clear: <strong>"Clear code"</strong></li> | |
| </ul> | |
| <p style="text-align: center;">🎤 IMPORTANT: Allow microphone access when prompted!</p> | |
| </div> | |
| """) | |
| # --- Event Handlers (Python functions triggered by Gradio events) --- | |
| start_voice_btn.click( | |
| fn=lambda: "🎤 Voice recognition started - speak now!", | |
| outputs=voice_status, | |
| js="() => window.startVoiceRecognition()" | |
| ) | |
| stop_voice_btn.click( | |
| fn=lambda: "⏹️ Voice recognition stopped", | |
| outputs=voice_status, | |
| js="() => window.stopVoiceRecognition()" | |
| ) | |
| process_voice_btn.click( | |
| fn=process_voice_command, | |
| inputs=[voice_input, music_code], | |
| outputs=[voice_status, music_code] | |
| ) | |
| # This handles the play button click, which also triggers JS play function | |
| def handle_play_button_click_status(js_status_message): # Only receive the status message from JS | |
| return js_status_message | |
| play_btn.click( | |
| fn=handle_play_button_click_status, | |
| inputs=[music_code], # music_code is passed to the JS function, not the Python fn directly | |
| outputs=audio_status, | |
| js="(code) => window.playStrudelCode(code)" # Pass code to JS, JS handles audio and returns status | |
| ) | |
| stop_btn.click( | |
| fn=lambda: "⏹️ Music stopped", | |
| outputs=audio_status, | |
| js="() => window.stopStrudelCode()" | |
| ) | |
| clear_btn = gr.Button("🗑️ Clear Code", variant="secondary") | |
| clear_btn.click(fn=clear_code, outputs=[voice_status, music_code]) | |
| return interface | |
| # --- Main execution block for Gradio --- | |
| if __name__ == "__main__": | |
| create_interface().launch() |