codel / app.py
baouws's picture
Update app.py
f19fe63 verified
raw
history blame
13.2 kB
import gradio as gr
import random
# Simple music generation logic
class StrudelGenerator:
def __init__(self):
self.patterns = {
'drums': ['"bd*4"', '"bd ~ sd ~"', '"bd*2 sd*2"'],
'bass': ['"c2 [~ c2] f2 [~ f2]"', '"c2*4"', '"c1 ~ f1 ~"'],
'chords': ['"<C^7 F^7 G^7 C^7>".voicing()', '"<c\'maj7 f\'maj7 g\'7 c\'maj7>".voicing()'],
'melody': ['"c5 d5 e5 f5"', '"c5 e5 g5 c6"']
}
def generate_code(self, description: str, tempo: int = 120) -> str:
"""Generate simple Strudel code"""
desc = description.lower()
patterns = []
# Simple keyword detection
if any(word in desc for word in ['drum', 'beat', 'kick']):
patterns.append(random.choice(self.patterns['drums']) + '.gain(0.8)')
if any(word in desc for word in ['bass', 'low']):
patterns.append(random.choice(self.patterns['bass']) + '.sound("sawtooth").gain(0.6)')
if any(word in desc for word in ['chord', 'harmony', 'piano']):
patterns.append(random.choice(self.patterns['chords']) + '.sound("piano").gain(0.4)')
if any(word in desc for word in ['melody', 'lead', 'tune']):
patterns.append(random.choice(self.patterns['melody']) + '.sound("sine").gain(0.5)')
# Default if nothing detected
if not patterns:
patterns = [
'"bd ~ sd ~".gain(0.7)',
'"<C^7 F^7 G^7 C^7>".voicing().sound("piano").gain(0.4)'
]
# Build code
if len(patterns) == 1:
code = patterns[0]
else:
code = "stack(\n"
for i, pattern in enumerate(patterns):
comma = "," if i < len(patterns) - 1 else ""
code += f" {pattern}{comma}\n"
code += ")"
code += f".cpm({tempo})"
return code
generator = StrudelGenerator()
def generate_music(description, tempo):
"""Generate music code"""
if not description.strip():
return "// Please describe your music first!"
code = generator.generate_code(description, tempo)
return code
# Create simple interface
with gr.Blocks(title="๐ŸŽต Strudel Generator") as app:
gr.HTML("""
<div style='text-align: center; margin-bottom: 30px;'>
<h1 style='color: #FF6B9D; font-size: 2.5rem; margin: 0;'>๐ŸŽต Strudel Music Generator</h1>
<p style='color: #666; font-size: 1.1rem;'>Describe your music and generate Strudel code!</p>
</div>
""")
# Simple input
description = gr.Textbox(
label="Describe your music",
placeholder="e.g., 'upbeat drums with piano chords'",
lines=2
)
tempo = gr.Slider(60, 180, 120, step=5, label="Tempo (BPM)")
generate_btn = gr.Button("๐Ÿš€ Generate Code", variant="primary")
# Code output
code_output = gr.Code(
label="Generated Strudel Code",
language="javascript",
lines=8
)
# Simple audio player with full Strudel.js
gr.HTML("""
<div id="player" style='
background: linear-gradient(135deg, #FF6B9D, #A855F7);
padding: 20px;
border-radius: 15px;
text-align: center;
margin: 20px 0;
'>
<h3 style='color: white; margin: 0 0 15px 0;'>๐ŸŽต Audio Player</h3>
<button id="playBtn" onclick="togglePlay()" style='
background: white;
color: #FF6B9D;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
margin: 5px;
transition: all 0.3s;
'>โ–ถ๏ธ Play</button>
<button onclick="stopPlay()" style='
background: rgba(255,255,255,0.8);
color: #FF6B9D;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
margin: 5px;
transition: all 0.3s;
'>โน๏ธ Stop</button>
<div id="status" style='color: white; margin-top: 10px; font-size: 14px;'>
Click to enable audio, then generate code and play!
</div>
</div>
<!-- Load Strudel.js libraries -->
<script type="module">
import { repl, controls, mini, initAudioOnFirstClick } from 'https://strudel.cc/strudel.mjs';
// Make Strudel available globally
window.strudel = { repl, controls, mini, initAudioOnFirstClick };
window.strudelReady = true;
console.log('Strudel.js loaded successfully!');
updateStatus('๐ŸŽต Strudel.js ready! Generate code and play!', 'white');
</script>
<script>
let isPlaying = false;
let currentPattern = null;
let strudelInitialized = false;
function getCurrentCode() {
// Get code from the textarea - try multiple selectors
const selectors = [
'textarea[data-testid*="code"]',
'.code-container textarea',
'textarea',
'code',
'[data-testid="code"] textarea'
];
for (let selector of selectors) {
const element = document.querySelector(selector);
if (element && (element.value || element.textContent)) {
const code = element.value || element.textContent;
if (code && !code.includes('Please describe') && code.includes('cpm')) {
return code;
}
}
}
return '';
}
function updateStatus(message, color = 'white') {
const status = document.getElementById('status');
if (status) {
status.textContent = message;
status.style.color = color;
}
}
function updatePlayButton(playing) {
const btn = document.getElementById('playBtn');
if (btn) {
btn.textContent = playing ? 'โธ๏ธ Pause' : 'โ–ถ๏ธ Play';
}
}
async function initializeStrudel() {
if (strudelInitialized) return true;
try {
// Wait for Strudel to load
let attempts = 0;
while (!window.strudelReady && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 100));
attempts++;
}
if (!window.strudel) {
throw new Error('Strudel failed to load');
}
// Initialize audio on first click
await window.strudel.initAudioOnFirstClick();
strudelInitialized = true;
updateStatus('๐ŸŽต Audio ready! Generate code and play!', '#ccffcc');
return true;
} catch (error) {
console.error('Strudel initialization failed:', error);
updateStatus('โš ๏ธ Audio init failed - using demo mode', '#ffcccc');
return false;
}
}
async function togglePlay() {
if (isPlaying) {
pausePlay();
} else {
await startPlay();
}
}
async function startPlay() {
const code = getCurrentCode();
if (!code || code.trim() === '' || code.includes('Please describe')) {
updateStatus('No code to play! Generate some first.', '#ffcccc');
return;
}
try {
// Initialize Strudel if needed
const initialized = await initializeStrudel();
if (!initialized || !window.strudel) {
// Fallback to demo mode
updateStatus('๐ŸŽฎ Demo: Playing ' + code.split('\\n')[0] + '...', '#ffffcc');
isPlaying = true;
updatePlayButton(true);
setTimeout(() => {
stopPlay();
updateStatus('Demo playback finished!', 'white');
}, 8000);
return;
}
// Stop any current pattern
if (currentPattern) {
currentPattern.stop();
}
updateStatus('๐ŸŽต Evaluating code...', '#ffffcc');
// Clean the code (remove comments)
const cleanCode = code
.split('\\n')
.filter(line => !line.trim().startsWith('//'))
.join('\\n')
.trim();
console.log('Playing code:', cleanCode);
// Evaluate the Strudel pattern
const pattern = window.strudel.repl.evaluate(cleanCode);
if (pattern && typeof pattern.play === 'function') {
currentPattern = pattern;
await pattern.play();
isPlaying = true;
updatePlayButton(true);
updateStatus('๐ŸŽต Playing your music! ๐ŸŽถ', '#ccffcc');
} else {
throw new Error('Invalid pattern - check your Strudel syntax');
}
} catch (error) {
console.error('Playback error:', error);
updateStatus('โŒ Error: ' + error.message, '#ffcccc');
isPlaying = false;
updatePlayButton(false);
// Try demo mode as fallback
setTimeout(() => {
updateStatus('๐ŸŽฎ Falling back to demo mode...', '#ffffcc');
isPlaying = true;
updatePlayButton(true);
setTimeout(() => {
stopPlay();
updateStatus('Demo playback finished!', 'white');
}, 5000);
}, 1000);
}
}
function pausePlay() {
if (currentPattern && typeof currentPattern.stop === 'function') {
currentPattern.stop();
}
isPlaying = false;
updatePlayButton(false);
updateStatus('โธ๏ธ Paused', '#ffffcc');
}
function stopPlay() {
if (currentPattern && typeof currentPattern.stop === 'function') {
currentPattern.stop();
currentPattern = null;
}
isPlaying = false;
updatePlayButton(false);
updateStatus('โน๏ธ Stopped', 'white');
}
// Add hover effects to buttons
document.addEventListener('DOMContentLoaded', function() {
const buttons = document.querySelectorAll('#player button');
buttons.forEach(btn => {
btn.addEventListener('mouseenter', function() {
this.style.transform = 'scale(1.05)';
this.style.boxShadow = '0 5px 15px rgba(0,0,0,0.2)';
});
btn.addEventListener('mouseleave', function() {
this.style.transform = 'scale(1)';
this.style.boxShadow = 'none';
});
});
// Initialize on first user interaction
document.addEventListener('click', initializeStrudel, { once: true });
});
// Handle page visibility changes
document.addEventListener('visibilitychange', function() {
if (document.hidden && isPlaying) {
pausePlay();
}
});
</script>
""")
# Quick examples
gr.HTML("<h3 style='margin-top: 30px;'>๐ŸŽจ Quick Examples:</h3>")
with gr.Row():
ex1 = gr.Button("๐ŸŽฎ Chiptune", size="sm")
ex2 = gr.Button("๐ŸŒ™ Lofi", size="sm")
ex3 = gr.Button("โšก Techno", size="sm")
ex4 = gr.Button("๐ŸŽน Jazz", size="sm")
# Event handlers
generate_btn.click(
fn=generate_music,
inputs=[description, tempo],
outputs=[code_output]
)
# Example handlers
ex1.click(lambda: "8-bit chiptune with bouncy melody and simple drums", outputs=[description])
ex2.click(lambda: "mellow lofi with soft piano chords and gentle drums", outputs=[description])
ex3.click(lambda: "driving techno with heavy bass and pounding kick", outputs=[description])
ex4.click(lambda: "smooth jazz with walking bass and chord progressions", outputs=[description])
gr.HTML("""
<div style='text-align: center; margin-top: 30px; opacity: 0.7;'>
<p>Made for National Make Music Day ๐ŸŽต</p>
</div>
""")
if __name__ == "__main__":
app.launch(
share=False,
server_name="0.0.0.0",
server_port=7860
)