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("""
Speak Your Beats Into Existence β Code, See, Hear!
π€ IMPORTANT: Allow microphone access when prompted!