baouws commited on
Commit
6c19b42
·
verified ·
1 Parent(s): 39789dc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +171 -668
app.py CHANGED
@@ -1,752 +1,255 @@
1
  import gradio as gr
2
- import json
3
- import re
4
  import random
5
- from typing import Dict, List, Tuple
6
 
7
- # Music generation logic
8
  class StrudelGenerator:
9
  def __init__(self):
10
- self.instrument_patterns = {
11
- 'drums': {
12
- 'kick': '"bd*4"',
13
- 'snare': '"~ sd ~ sd"',
14
- 'hihat': '"hh*8"',
15
- 'techno_kick': '"bd*4"',
16
- 'house_kick': '"bd ~ ~ ~ bd ~ ~ ~"',
17
- 'breakbeat': '"bd ~ sd ~ bd sd ~ sd"',
18
- 'trap': '"bd ~ ~ bd ~ sd ~ ~"',
19
- 'dnb': '"bd ~ ~ ~ sd ~ bd sd"'
20
- },
21
- 'bass': {
22
- 'basic': '"c2 [~ c2] f2 [~ f2]"',
23
- 'funk': '"c2 ~ c2 ~ f2 ~ f2 ~"',
24
- 'walking': '"c2 e2 g2 c3 f2 a2 c3 f3".slow(2)',
25
- 'techno': '"c2*4"',
26
- 'wobble': '"c2 c2 f2 f2".sound("sawtooth").lpf(sine.range(200,800))',
27
- 'deep': '"c1 ~ f1 ~ g1 ~ c1 ~".slow(2)'
28
- },
29
- 'chords': {
30
- 'jazz': '"<c\'maj7 f\'maj7 g\'7 c\'maj7>".voicing()',
31
- 'basic': '"<C^7 F^7 G^7 C^7>".voicing()',
32
- 'ambient': '"<c\'maj e\'min f\'maj g\'maj>".voicing()',
33
- 'emotional': '"<c\'min f\'maj g\'maj a\'min>".voicing()',
34
- 'uplifting': '"<C^7 F^7 Am7 G^7>".voicing()'
35
- },
36
- 'melody': {
37
- 'basic': '"c5 d5 e5 f5 g5 a5 b5 c6".slow(2)',
38
- 'ambient': '"c5 d5 e5 f5 g5 a5 b5 c6".slow(4)',
39
- 'fast': '"c5 d5 e5 f5 g5 a5 b5 c6"',
40
- 'anime': '"c5 e5 g5 c6 b5 g5 e5 c5".slow(1.5)',
41
- 'chiptune': '"c6 d6 e6 f6 g6 a6 b6 c7"'
42
- },
43
- 'arpeggios': {
44
- 'basic': '"c4 e4 g4 c5".fast(2)',
45
- 'dreamy': '"c4 e4 g4 b4 c5 b4 g4 e4".slow(2)',
46
- 'energetic': '"c4 e4 g4 c5".fast(4)'
47
- }
48
- }
49
-
50
- self.style_effects = {
51
- 'lofi': ['.room(0.7)', '.lpf(2000)', '.hpf(100)', '.gain(0.6)'],
52
- 'techno': ['.room(0.2)', '.lpf(sine.range(400,2000))', '.distort(0.1)'],
53
- 'house': ['.room(0.4)', '.lpf(3000)', '.compress(0.8)'],
54
- 'ambient': ['.room(0.9)', '.size(0.8)', '.lpf(1000)', '.gain(0.4)'],
55
- 'jazz': ['.room(0.6)', '.lpf(4000)', '.compress(0.6)'],
56
- 'cyberpunk': ['.room(0.3)', '.distort(0.2)', '.lpf(sine.range(300,1500))'],
57
- 'anime': ['.room(0.5)', '.lpf(3500)', '.delay(0.125)', '.gain(0.7)'],
58
- 'chiptune': ['.room(0.1)', '.lpf(8000)', '.bitcrush(8)', '.gain(0.8)']
59
  }
60
 
61
- def analyze_description(self, description: str) -> Dict[str, List[str]]:
62
- """Enhanced analysis with anime and electronic music focus"""
63
  desc = description.lower()
64
- elements = {
65
- 'drums': [],
66
- 'bass': [],
67
- 'chords': [],
68
- 'melody': [],
69
- 'arpeggios': [],
70
- 'style': [],
71
- 'mood': []
72
- }
73
-
74
- # Enhanced drum detection
75
- drum_keywords = {
76
- 'kick': ['kick', 'bass drum', 'bd'],
77
- 'snare': ['snare', 'sd', 'clap'],
78
- 'hihat': ['hi-hat', 'hihat', 'hh', 'cymbal'],
79
- 'trap': ['trap', '808'],
80
- 'dnb': ['drum and bass', 'dnb', 'breakbeat'],
81
- 'techno_kick': ['techno', 'four on the floor'],
82
- 'house_kick': ['house']
83
- }
84
 
85
- for pattern, keywords in drum_keywords.items():
86
- if any(keyword in desc for keyword in keywords):
87
- elements['drums'].append(pattern)
88
 
89
- if 'drum' in desc and not elements['drums']:
90
- elements['drums'].extend(['kick', 'snare', 'hihat'])
91
 
92
- # Enhanced bass detection
93
- bass_keywords = {
94
- 'wobble': ['wobble', 'dubstep', 'bass drop'],
95
- 'deep': ['deep bass', 'sub bass'],
96
- 'funk': ['funk', 'slap bass'],
97
- 'walking': ['walking bass', 'jazz bass'],
98
- 'techno': ['techno bass', 'driving bass']
99
- }
100
-
101
- for pattern, keywords in bass_keywords.items():
102
- if any(keyword in desc for keyword in keywords):
103
- elements['bass'].append(pattern)
104
-
105
- if 'bass' in desc and not elements['bass']:
106
- elements['bass'].append('basic')
107
-
108
- # Enhanced harmony detection
109
- chord_keywords = {
110
- 'emotional': ['emotional', 'sad', 'melancholic', 'dramatic'],
111
- 'uplifting': ['uplifting', 'happy', 'energetic', 'positive'],
112
- 'jazz': ['jazz', 'sophisticated', 'complex'],
113
- 'ambient': ['ambient', 'atmospheric', 'spacey']
114
- }
115
-
116
- for pattern, keywords in chord_keywords.items():
117
- if any(keyword in desc for keyword in keywords):
118
- elements['chords'].append(pattern)
119
-
120
- if any(word in desc for word in ['chord', 'harmony', 'piano']) and not elements['chords']:
121
- elements['chords'].append('basic')
122
-
123
- # Enhanced melody detection
124
- melody_keywords = {
125
- 'anime': ['anime', 'japanese', 'kawaii', 'manga'],
126
- 'chiptune': ['8-bit', 'chiptune', 'retro', 'game'],
127
- 'ambient': ['ethereal', 'floating', 'dreamy'],
128
- 'fast': ['fast', 'rapid', 'quick', 'energetic']
129
- }
130
-
131
- for pattern, keywords in melody_keywords.items():
132
- if any(keyword in desc for keyword in keywords):
133
- elements['melody'].append(pattern)
134
-
135
- if any(word in desc for word in ['melody', 'lead', 'tune']) and not elements['melody']:
136
- elements['melody'].append('basic')
137
 
138
- # Arpeggio detection
139
- if any(word in desc for word in ['arpeggio', 'arpeggiated', 'broken chord']):
140
- if 'dreamy' in desc or 'floating' in desc:
141
- elements['arpeggios'].append('dreamy')
142
- elif 'energetic' in desc or 'fast' in desc:
143
- elements['arpeggios'].append('energetic')
144
- else:
145
- elements['arpeggios'].append('basic')
146
-
147
- # Style detection
148
- styles = ['lofi', 'techno', 'house', 'ambient', 'jazz', 'cyberpunk', 'anime', 'chiptune', 'trap', 'dubstep']
149
- for style in styles:
150
- if style in desc:
151
- elements['style'].append(style)
152
-
153
- return elements
154
-
155
- def generate_code(self, description: str, tempo: int = 120, style: str = None) -> Tuple[str, str]:
156
- """Generate Strudel code and return code + analysis"""
157
- elements = self.analyze_description(description)
158
 
159
- # Use provided style or detected style
160
- if style and style.lower() != 'auto-detect':
161
- main_style = style.lower()
162
- elif elements['style']:
163
- main_style = elements['style'][0]
164
- else:
165
- main_style = 'basic'
166
-
167
- patterns = []
168
- analysis_parts = []
169
-
170
- # Build patterns based on analysis
171
- for category in ['drums', 'bass', 'chords', 'melody', 'arpeggios']:
172
- for element in elements[category]:
173
- if category in self.instrument_patterns and element in self.instrument_patterns[category]:
174
- pattern = self.instrument_patterns[category][element]
175
-
176
- # Add appropriate sound and effects
177
- if category == 'bass':
178
- pattern += '.sound("sawtooth").lpf(800)'
179
- analysis_parts.append(f"🎸 Bass: {element}")
180
- elif category == 'chords':
181
- pattern += '.sound("piano")'
182
- analysis_parts.append(f"🎹 Chords: {element}")
183
- elif category == 'melody':
184
- pattern += '.sound("sine").lpf(2000)'
185
- analysis_parts.append(f"🎵 Melody: {element}")
186
- elif category == 'arpeggios':
187
- pattern += '.sound("triangle").lpf(3000)'
188
- analysis_parts.append(f"🌸 Arpeggios: {element}")
189
- elif category == 'drums':
190
- analysis_parts.append(f"🥁 Drums: {element}")
191
-
192
- # Add gain
193
- if category == 'drums':
194
- pattern += '.gain(0.7)'
195
- else:
196
- pattern += '.gain(0.4)'
197
-
198
- patterns.append(pattern)
199
-
200
- # If no patterns generated, create default
201
  if not patterns:
202
  patterns = [
203
  '"bd ~ sd ~".gain(0.7)',
204
  '"<C^7 F^7 G^7 C^7>".voicing().sound("piano").gain(0.4)'
205
  ]
206
- analysis_parts = ["🥁 Basic drums", "🎹 Simple chords"]
207
-
208
- # Build final code
209
- code_lines = [
210
- f'// 🎵 Generated from: "{description}"',
211
- f'// 🎭 Style: {style or main_style}, ⏱️ Tempo: {tempo} BPM',
212
- f'// 🎨 Elements: {", ".join(analysis_parts)}',
213
- '',
214
- ]
215
 
 
216
  if len(patterns) == 1:
217
- code_lines.append(patterns[0])
218
  else:
219
- code_lines.append('stack(')
220
  for i, pattern in enumerate(patterns):
221
- comma = ',' if i < len(patterns) - 1 else ''
222
- code_lines.append(f' {pattern}{comma}')
223
- code_lines.append(')')
224
-
225
- code_lines.append(f'.cpm({tempo})')
226
-
227
- # Add style-specific effects
228
- if main_style in self.style_effects:
229
- effects = self.style_effects[main_style]
230
- for effect in effects[:2]: # Limit effects to avoid overload
231
- code_lines.append(effect)
232
 
233
- analysis = f"**Detected Elements:** {', '.join(analysis_parts)}\n**Style:** {main_style}"
234
-
235
- return '\n'.join(code_lines), analysis
236
-
237
- def add_variation(self, code: str) -> str:
238
- """Add random variations to existing code"""
239
- if not code.strip():
240
- return code
241
-
242
- variations = [
243
- ".sometimes(x=>x.rev())",
244
- ".every(4, x=>x.fast(2))",
245
- ".rarely(x=>x.gain(0.1))",
246
- ".jux(x=>x.rev())",
247
- ".every(8, x=>x.slow(2))",
248
- ".sometimes(x=>x.ply(2))",
249
- ".degradeBy(0.1)",
250
- ".when(rand.range(0,1) > 0.7, x=>x.lpf(500))"
251
- ]
252
-
253
- variation = random.choice(variations)
254
- return code + variation
255
 
256
- # Initialize generator
257
  generator = StrudelGenerator()
258
 
259
- # Example descriptions with anime themes
260
- examples = {
261
- "🌸 Kawaii Lofi": "Soft kawaii lofi with gentle piano chords, mellow drums, and a dreamy floating melody",
262
- "⚡ Cyberpunk Action": "High energy cyberpunk techno with driving bass, glitchy effects, and dramatic synths",
263
- "🎮 8-bit Adventure": "Retro chiptune with bouncy 8-bit melody, simple drums, and nostalgic video game vibes",
264
- "🌙 Anime Ballad": "Emotional anime ballad with beautiful piano, soft strings, and a soaring melody",
265
- "🔥 Epic Battle": "Intense battle music with powerful drums, dramatic chords, and heroic melody",
266
- "💫 Future Bass Drop": "Melodic future bass with emotional buildup, punchy drums, and ethereal pads",
267
- "🌊 Peaceful Ambient": "Tranquil ambient soundscape with gentle pads, soft percussion, and floating tones",
268
- "🎪 Funky Anime OP": "Upbeat anime opening with funky bass, energetic drums, and catchy melody"
269
- }
270
-
271
- def generate_music(description, tempo, style):
272
- """Main generation function"""
273
  if not description.strip():
274
- return "", "Please describe your music first! 🎵", ""
275
 
276
- try:
277
- code, analysis = generator.generate_code(description, tempo, style)
278
- success_msg = "🎉 Music code generated successfully! Click the Play button below to hear it!"
279
- return code, success_msg, analysis
280
- except Exception as e:
281
- error_msg = f"❌ Error generating music: {str(e)}"
282
- return "", error_msg, ""
283
 
284
- def add_magic(code):
285
- """Add variations to existing code"""
286
- if not code.strip():
287
- return code, "Generate some code first! 🎼"
288
 
289
- try:
290
- new_code = generator.add_variation(code)
291
- return new_code, "✨ Added magical variation!"
292
- except Exception as e:
293
- return code, f"❌ Error adding variation: {str(e)}"
294
-
295
- def load_example(example_key):
296
- """Load example description"""
297
- return examples.get(example_key, "")
298
-
299
- def create_strudel_player():
300
- """Create HTML component with Strudel.js player"""
301
- return gr.HTML("""
302
- <div id="strudel-container" style="
303
- background: linear-gradient(135deg, rgba(255, 107, 157, 0.1), rgba(168, 85, 247, 0.1));
304
- border: 2px solid rgba(255, 107, 157, 0.3);
305
- border-radius: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
306
  padding: 20px;
307
- margin: 10px 0;
308
- backdrop-filter: blur(10px);
309
- ">
310
- <div style="text-align: center; margin-bottom: 15px;">
311
- <h3 style="color: #FF6B9D; margin: 0 0 10px 0;">🎵 Strudel Audio Player</h3>
312
- <p style="color: #E2E8F0; margin: 0; opacity: 0.8;">Generate code above, then click play to hear your music!</p>
313
- </div>
314
-
315
- <div id="strudel-controls" style="text-align: center; margin-bottom: 15px;">
316
- <button id="play-btn" onclick="playStrudel()" style="
317
- background: linear-gradient(45deg, #FF6B9D, #A855F7);
318
- color: white;
319
- border: none;
320
- border-radius: 25px;
321
- padding: 12px 24px;
322
- margin: 5px;
323
- font-weight: 600;
324
- cursor: pointer;
325
- transition: all 0.3s ease;
326
- box-shadow: 0 4px 15px rgba(255, 107, 157, 0.4);
327
- ">▶️ Play</button>
328
-
329
- <button id="stop-btn" onclick="stopStrudel()" style="
330
- background: linear-gradient(45deg, #EF4444, #F59E0B);
331
- color: white;
332
- border: none;
333
- border-radius: 25px;
334
- padding: 12px 24px;
335
- margin: 5px;
336
- font-weight: 600;
337
- cursor: pointer;
338
- transition: all 0.3s ease;
339
- box-shadow: 0 4px 15px rgba(239, 68, 68, 0.4);
340
- ">⏹️ Stop</button>
341
-
342
- <button id="volume-btn" onclick="toggleVolume()" style="
343
- background: linear-gradient(45deg, #06B6D4, #3B82F6);
344
- color: white;
345
- border: none;
346
- border-radius: 25px;
347
- padding: 12px 24px;
348
- margin: 5px;
349
- font-weight: 600;
350
- cursor: pointer;
351
- transition: all 0.3s ease;
352
- box-shadow: 0 4px 15px rgba(6, 182, 212, 0.4);
353
- ">🔊 Volume</button>
354
- </div>
355
-
356
- <div id="strudel-status" style="
357
- text-align: center;
358
- color: #E2E8F0;
359
- font-size: 14px;
360
- opacity: 0.8;
361
- margin-bottom: 10px;
362
- ">Ready to play music ✨</div>
363
-
364
- <div id="volume-control" style="display: none; text-align: center; margin: 10px 0;">
365
- <input type="range" id="volume-slider" min="0" max="100" value="70" style="
366
- width: 200px;
367
- height: 6px;
368
- border-radius: 5px;
369
- background: linear-gradient(to right, #FF6B9D, #A855F7);
370
- outline: none;
371
- opacity: 0.8;
372
- transition: opacity 0.2s;
373
- ">
374
- <div style="color: #E2E8F0; font-size: 12px; margin-top: 5px;">Volume: <span id="volume-display">70</span>%</div>
375
  </div>
376
  </div>
377
 
378
  <script>
379
- let currentPattern = null;
380
  let isPlaying = false;
381
- let audioContext = null;
382
- let currentCode = '';
383
- let volume = 0.7;
384
-
385
- function updateStatus(message, color = '#E2E8F0') {
386
- const status = document.getElementById('strudel-status');
 
 
 
 
 
 
387
  if (status) {
388
- status.innerHTML = message;
389
  status.style.color = color;
390
  }
391
  }
392
-
393
  function updatePlayButton(playing) {
394
- const btn = document.getElementById('play-btn');
395
  if (btn) {
396
- if (playing) {
397
- btn.innerHTML = '⏸️ Pause';
398
- btn.onclick = pauseStrudel;
399
- } else {
400
- btn.innerHTML = '▶️ Play';
401
- btn.onclick = playStrudel;
402
- }
403
  }
404
  }
405
-
406
- function getCurrentCode() {
407
- // Get current code from Gradio's code component
408
- const codeElements = document.querySelectorAll('textarea, .code-container code');
409
- for (let element of codeElements) {
410
- const text = element.textContent || element.value || '';
411
- if (text && text.includes('//') && text.includes('cpm')) {
412
- return text;
413
- }
414
  }
415
- return '';
416
  }
417
-
418
- async function playStrudel() {
419
- currentCode = getCurrentCode();
420
 
421
- if (!currentCode || !currentCode.trim()) {
422
- updateStatus('No code to play! Generate some music first.', '#EF4444');
423
  return;
424
  }
425
-
426
- try {
427
- // Stop any currently playing pattern
428
- if (currentPattern) {
429
- currentPattern.stop();
430
- }
431
-
432
- // Demo mode with enhanced feedback
433
- updateStatus('🎮 Demo Mode: Playing your creation...', '#06B6D4');
434
- isPlaying = true;
435
- updatePlayButton(true);
436
-
437
- // Show the code being "played"
438
- const firstLine = currentCode.split('\\n').find(line => line.includes('Generated from:')) || 'Generated music';
439
- console.log('Playing:', firstLine);
440
-
441
- // Simulate realistic playback duration
442
- setTimeout(() => {
443
- updateStatus('🎵 Demo playback complete! Integrate Strudel.js for real audio.', '#FCD34D');
444
- isPlaying = false;
445
- updatePlayButton(false);
446
- }, 8000);
447
-
448
- } catch (error) {
449
- console.error('Playback error:', error);
450
- updateStatus('❌ Error: ' + error.message, '#EF4444');
451
- isPlaying = false;
452
- updatePlayButton(false);
453
- }
454
  }
455
-
456
- function pauseStrudel() {
457
- if (isPlaying) {
458
- isPlaying = false;
459
- updatePlayButton(false);
460
- updateStatus('⏸️ Music paused', '#FCD34D');
 
 
 
461
  }
462
  }
463
-
464
- function stopStrudel() {
465
- if (currentPattern) {
466
- currentPattern.stop();
467
- currentPattern = null;
468
- }
469
  isPlaying = false;
470
  updatePlayButton(false);
471
- updateStatus('⏹️ Music stopped', '#6B7280');
472
- }
473
-
474
- function toggleVolume() {
475
- const volumeControl = document.getElementById('volume-control');
476
- if (volumeControl) {
477
- volumeControl.style.display = volumeControl.style.display === 'none' ? 'block' : 'none';
478
  }
479
  }
480
-
481
- // Volume control
482
  document.addEventListener('DOMContentLoaded', function() {
483
- const volumeSlider = document.getElementById('volume-slider');
484
- const volumeDisplay = document.getElementById('volume-display');
485
-
486
- if (volumeSlider && volumeDisplay) {
487
- volumeSlider.oninput = function() {
488
- volume = this.value / 100;
489
- volumeDisplay.textContent = this.value;
490
- };
491
- }
492
-
493
- // Anime-style button hover effects
494
- const buttons = document.querySelectorAll('#strudel-controls button');
495
  buttons.forEach(btn => {
496
  btn.addEventListener('mouseenter', function() {
497
- this.style.transform = 'translateY(-2px) scale(1.05)';
498
- this.style.boxShadow = '0 8px 25px rgba(255, 107, 157, 0.6)';
499
  });
500
  btn.addEventListener('mouseleave', function() {
501
- this.style.transform = 'translateY(0) scale(1)';
502
- this.style.boxShadow = '0 4px 15px rgba(255, 107, 157, 0.4)';
503
  });
504
  });
505
-
506
- updateStatus('🎵 Audio player ready! Generate code and click play!', '#06B6D4');
507
  });
508
  </script>
509
- """, elem_id="strudel-player")
510
-
511
- # Custom CSS for anime theme
512
- anime_css = """
513
- <style>
514
- @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap');
515
-
516
- :root {
517
- --primary-pink: #FF6B9D;
518
- --primary-purple: #A855F7;
519
- --primary-blue: #3B82F6;
520
- --accent-cyan: #06B6D4;
521
- --accent-yellow: #FCD34D;
522
- --dark-bg: #1A1B2E;
523
- --darker-bg: #16213E;
524
- --card-bg: rgba(255, 255, 255, 0.1);
525
- --text-light: #E2E8F0;
526
- }
527
-
528
- .gradio-container {
529
- font-family: 'Poppins', sans-serif !important;
530
- background: linear-gradient(135deg, #1A1B2E 0%, #16213E 25%, #0F3460 50%, #16213E 75%, #1A1B2E 100%) !important;
531
- min-height: 100vh;
532
- }
533
-
534
- .main-header {
535
- text-align: center;
536
- background: linear-gradient(45deg, var(--primary-pink), var(--primary-purple), var(--primary-blue));
537
- -webkit-background-clip: text;
538
- -webkit-text-fill-color: transparent;
539
- background-clip: text;
540
- font-size: 3rem;
541
- font-weight: 700;
542
- margin: 1rem 0;
543
- text-shadow: 0 0 30px rgba(255, 107, 157, 0.5);
544
- }
545
-
546
- .subtitle {
547
- text-align: center;
548
- color: var(--text-light);
549
- font-size: 1.2rem;
550
- margin-bottom: 2rem;
551
- opacity: 0.9;
552
- }
553
-
554
- .btn-primary {
555
- background: linear-gradient(45deg, var(--primary-pink), var(--primary-purple)) !important;
556
- border: none !important;
557
- border-radius: 25px !important;
558
- padding: 0.75rem 2rem !important;
559
- font-weight: 600 !important;
560
- transition: all 0.3s ease !important;
561
- box-shadow: 0 4px 15px rgba(255, 107, 157, 0.4) !important;
562
- }
563
-
564
- .btn-primary:hover {
565
- transform: translateY(-2px) !important;
566
- box-shadow: 0 8px 25px rgba(255, 107, 157, 0.6) !important;
567
- }
568
-
569
- textarea, input {
570
- background: rgba(255, 255, 255, 0.1) !important;
571
- border: 2px solid transparent !important;
572
- border-radius: 15px !important;
573
- color: var(--text-light) !important;
574
- transition: all 0.3s ease !important;
575
- }
576
-
577
- textarea:focus, input:focus {
578
- border-color: var(--primary-pink) !important;
579
- box-shadow: 0 0 20px rgba(255, 107, 157, 0.3) !important;
580
- }
581
-
582
- @keyframes sparkle {
583
- 0%, 100% { opacity: 1; transform: scale(1); }
584
- 50% { opacity: 0.5; transform: scale(1.2); }
585
- }
586
- </style>
587
- """
588
-
589
- # Create Gradio interface
590
- with gr.Blocks(
591
- theme=gr.themes.Soft(
592
- primary_hue="pink",
593
- secondary_hue="purple",
594
- neutral_hue="slate"
595
- ),
596
- css=anime_css,
597
- title="🎵 Strudel Music Generator"
598
- ) as app:
599
-
600
- # Header
601
- gr.HTML("""
602
- <div class="main-header">🎵 Strudel Music Generator</div>
603
- <div class="subtitle">Transform your musical dreams into Strudel code with anime-powered AI! ✨</div>
604
  """)
605
 
606
- with gr.Row():
607
- with gr.Column(scale=1):
608
- gr.Markdown("### 🎼 Describe Your Music")
609
-
610
- description = gr.Textbox(
611
- label="Musical Description",
612
- placeholder="e.g., 'A dreamy lofi track with soft piano chords, mellow drums, and a gentle melody that feels like floating on clouds...'",
613
- lines=4,
614
- elem_id="description"
615
- )
616
-
617
- with gr.Row():
618
- tempo = gr.Slider(
619
- minimum=60,
620
- maximum=200,
621
- value=120,
622
- step=5,
623
- label="🎵 Tempo (BPM)"
624
- )
625
-
626
- style = gr.Dropdown(
627
- choices=["Auto-detect", "Lofi", "Techno", "House", "Ambient", "Jazz", "Cyberpunk", "Anime", "Chiptune"],
628
- value="Auto-detect",
629
- label="🎭 Style Override"
630
- )
631
-
632
- with gr.Row():
633
- generate_btn = gr.Button(
634
- "🚀 Generate Strudel Code",
635
- variant="primary",
636
- elem_id="generate-btn"
637
- )
638
- magic_btn = gr.Button(
639
- "✨ Add Magic",
640
- elem_id="magic-btn"
641
- )
642
-
643
- with gr.Column(scale=1):
644
- gr.Markdown("### 🎹 Generated Strudel Code")
645
-
646
- code_output = gr.Code(
647
- label="Your Strudel Code (Editable)",
648
- language="javascript",
649
- lines=15,
650
- elem_id="code-output"
651
- )
652
-
653
- status_output = gr.Textbox(
654
- label="Status",
655
- interactive=False,
656
- elem_id="status"
657
- )
658
-
659
- analysis_output = gr.Markdown(
660
- label="🔍 Musical Analysis"
661
- )
662
-
663
- # Audio Player Section
664
- gr.Markdown("### 🎵 Audio Playback")
665
- strudel_player = create_strudel_player()
666
-
667
- # Examples section
668
- gr.Markdown("### 🎨 Try These Anime-Inspired Examples")
669
 
670
  with gr.Row():
671
- example_buttons = []
672
- for i, (title, desc) in enumerate(list(examples.items())[:4]):
673
- btn = gr.Button(title, elem_id=f"example-{i}")
674
- example_buttons.append((btn, desc))
675
-
676
- with gr.Row():
677
- for i, (title, desc) in enumerate(list(examples.items())[4:]):
678
- btn = gr.Button(title, elem_id=f"example-{i+4}")
679
- example_buttons.append((btn, desc))
680
-
681
- # Instructions and tips
682
- with gr.Accordion("🎓 Music Theory Helper & Strudel Tips", open=False):
683
- gr.Markdown("""
684
- **🎵 Musical Elements You Can Describe:**
685
- - **Drums**: "kick drums", "snare", "hi-hats", "trap beats", "breakbeats"
686
- - **Bass**: "deep bass", "funky bass", "wobble bass", "walking bass"
687
- - **Harmony**: "jazz chords", "emotional chords", "uplifting progression"
688
- - **Melody**: "anime melody", "8-bit tune", "ethereal lead", "fast arpeggios"
689
-
690
- **🎭 Styles & Moods:**
691
- - **Lofi**: relaxed, mellow, nostalgic, cozy
692
- - **Cyberpunk**: dark, futuristic, glitchy, intense
693
- - **Anime**: emotional, dramatic, uplifting, kawaii
694
- - **Chiptune**: retro, 8-bit, nostalgic, bouncy
695
-
696
- **⚡ Strudel Code Tips:**
697
- - Use `stack()` to layer multiple patterns
698
- - Add `.sound()` to choose different instruments
699
- - Apply effects like `.lpf()`, `.room()`, `.delay()`
700
- - Control timing with `.slow()`, `.fast()`, `.every()`
701
- - Experiment with `.sometimes()` for variations
702
-
703
- **🎮 Pro Tips:**
704
- - Be descriptive about the mood and energy you want
705
- - Mention specific instruments or sounds
706
- - Describe the genre or style for better results
707
- - Use the "Add Magic" button for instant variations!
708
- - Click the Play button to hear your generated music!
709
-
710
- **🎵 Audio Playbook:**
711
- - Generate code first, then click the ▶️ Play button
712
- - Use ⏹️ Stop to halt playback
713
- - Adjust volume with the 🔊 Volume button
714
- - The player works in demo mode and with full Strudel.js integration
715
- """)
716
 
717
  # Event handlers
718
  generate_btn.click(
719
  fn=generate_music,
720
- inputs=[description, tempo, style],
721
- outputs=[code_output, status_output, analysis_output]
722
  )
723
 
724
- magic_btn.click(
725
- fn=add_magic,
726
- inputs=[code_output],
727
- outputs=[code_output, status_output]
728
- )
729
-
730
- # Example button handlers
731
- for btn, desc in example_buttons:
732
- btn.click(
733
- fn=lambda x=desc: x,
734
- outputs=[description]
735
- )
736
-
737
- # Footer
738
  gr.HTML("""
739
- <div style='text-align: center; margin-top: 2rem; opacity: 0.7; color: #E2E8F0;'>
740
- <p>Made with 💖 for National Make Music Day | Powered by Strudel.js & Gradio</p>
741
- <p><small>🎵 Turn your musical dreams into algorithmic reality! ✨</small></p>
742
  </div>
743
  """)
744
 
745
- # Launch configuration
746
  if __name__ == "__main__":
747
  app.launch(
748
  share=False,
749
  server_name="0.0.0.0",
750
- server_port=7860,
751
- show_error=True
752
  )
 
1
  import gradio as gr
 
 
2
  import random
 
3
 
4
+ # Simple music generation logic
5
  class StrudelGenerator:
6
  def __init__(self):
7
+ self.patterns = {
8
+ 'drums': ['"bd*4"', '"bd ~ sd ~"', '"bd*2 sd*2"'],
9
+ 'bass': ['"c2 [~ c2] f2 [~ f2]"', '"c2*4"', '"c1 ~ f1 ~"'],
10
+ 'chords': ['"<C^7 F^7 G^7 C^7>".voicing()', '"<c\'maj7 f\'maj7 g\'7 c\'maj7>".voicing()'],
11
+ 'melody': ['"c5 d5 e5 f5"', '"c5 e5 g5 c6"']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  }
13
 
14
+ def generate_code(self, description: str, tempo: int = 120) -> str:
15
+ """Generate simple Strudel code"""
16
  desc = description.lower()
17
+ patterns = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ # Simple keyword detection
20
+ if any(word in desc for word in ['drum', 'beat', 'kick']):
21
+ patterns.append(random.choice(self.patterns['drums']) + '.gain(0.8)')
22
 
23
+ if any(word in desc for word in ['bass', 'low']):
24
+ patterns.append(random.choice(self.patterns['bass']) + '.sound("sawtooth").gain(0.6)')
25
 
26
+ if any(word in desc for word in ['chord', 'harmony', 'piano']):
27
+ patterns.append(random.choice(self.patterns['chords']) + '.sound("piano").gain(0.4)')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ if any(word in desc for word in ['melody', 'lead', 'tune']):
30
+ patterns.append(random.choice(self.patterns['melody']) + '.sound("sine").gain(0.5)')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ # Default if nothing detected
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  if not patterns:
34
  patterns = [
35
  '"bd ~ sd ~".gain(0.7)',
36
  '"<C^7 F^7 G^7 C^7>".voicing().sound("piano").gain(0.4)'
37
  ]
 
 
 
 
 
 
 
 
 
38
 
39
+ # Build code
40
  if len(patterns) == 1:
41
+ code = patterns[0]
42
  else:
43
+ code = "stack(\n"
44
  for i, pattern in enumerate(patterns):
45
+ comma = "," if i < len(patterns) - 1 else ""
46
+ code += f" {pattern}{comma}\n"
47
+ code += ")"
 
 
 
 
 
 
 
 
48
 
49
+ code += f".cpm({tempo})"
50
+ return code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
 
52
  generator = StrudelGenerator()
53
 
54
+ def generate_music(description, tempo):
55
+ """Generate music code"""
 
 
 
 
 
 
 
 
 
 
 
 
56
  if not description.strip():
57
+ return "// Please describe your music first!"
58
 
59
+ code = generator.generate_code(description, tempo)
60
+ return code
 
 
 
 
 
61
 
62
+ # Create simple interface
63
+ with gr.Blocks(title="🎵 Strudel Generator") as app:
 
 
64
 
65
+ gr.HTML("""
66
+ <div style='text-align: center; margin-bottom: 30px;'>
67
+ <h1 style='color: #FF6B9D; font-size: 2.5rem; margin: 0;'>🎵 Strudel Music Generator</h1>
68
+ <p style='color: #666; font-size: 1.1rem;'>Describe your music and generate Strudel code!</p>
69
+ </div>
70
+ """)
71
+
72
+ # Simple input
73
+ description = gr.Textbox(
74
+ label="Describe your music",
75
+ placeholder="e.g., 'upbeat drums with piano chords'",
76
+ lines=2
77
+ )
78
+
79
+ tempo = gr.Slider(60, 180, 120, step=5, label="Tempo (BPM)")
80
+
81
+ generate_btn = gr.Button("🚀 Generate Code", variant="primary")
82
+
83
+ # Code output
84
+ code_output = gr.Code(
85
+ label="Generated Strudel Code",
86
+ language="javascript",
87
+ lines=8
88
+ )
89
+
90
+ # Simple audio player
91
+ gr.HTML("""
92
+ <div id="player" style='
93
+ background: linear-gradient(135deg, #FF6B9D, #A855F7);
94
  padding: 20px;
95
+ border-radius: 15px;
96
+ text-align: center;
97
+ margin: 20px 0;
98
+ '>
99
+ <h3 style='color: white; margin: 0 0 15px 0;'>🎵 Audio Player</h3>
100
+ <button id="playBtn" onclick="togglePlay()" style='
101
+ background: white;
102
+ color: #FF6B9D;
103
+ border: none;
104
+ padding: 12px 30px;
105
+ border-radius: 25px;
106
+ font-size: 16px;
107
+ font-weight: bold;
108
+ cursor: pointer;
109
+ margin: 5px;
110
+ transition: all 0.3s;
111
+ '>▶️ Play</button>
112
+ <button onclick="stopPlay()" style='
113
+ background: rgba(255,255,255,0.8);
114
+ color: #FF6B9D;
115
+ border: none;
116
+ padding: 12px 30px;
117
+ border-radius: 25px;
118
+ font-size: 16px;
119
+ font-weight: bold;
120
+ cursor: pointer;
121
+ margin: 5px;
122
+ transition: all 0.3s;
123
+ '>⏹️ Stop</button>
124
+ <div id="status" style='color: white; margin-top: 10px; font-size: 14px;'>
125
+ Generate code and click play!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  </div>
127
  </div>
128
 
129
  <script>
 
130
  let isPlaying = false;
131
+ let playTimer = null;
132
+
133
+ function getCurrentCode() {
134
+ // Get code from the textarea
135
+ const codeArea = document.querySelector('textarea[data-testid*="code"]') ||
136
+ document.querySelector('.code-container textarea') ||
137
+ document.querySelector('textarea');
138
+ return codeArea ? codeArea.value : '';
139
+ }
140
+
141
+ function updateStatus(message, color = 'white') {
142
+ const status = document.getElementById('status');
143
  if (status) {
144
+ status.textContent = message;
145
  status.style.color = color;
146
  }
147
  }
148
+
149
  function updatePlayButton(playing) {
150
+ const btn = document.getElementById('playBtn');
151
  if (btn) {
152
+ btn.textContent = playing ? '⏸️ Pause' : '▶️ Play';
 
 
 
 
 
 
153
  }
154
  }
155
+
156
+ function togglePlay() {
157
+ if (isPlaying) {
158
+ pausePlay();
159
+ } else {
160
+ startPlay();
 
 
 
161
  }
 
162
  }
163
+
164
+ function startPlay() {
165
+ const code = getCurrentCode();
166
 
167
+ if (!code || code.trim() === '' || code.includes('Please describe')) {
168
+ updateStatus('No code to play! Generate some first.', '#ffcccc');
169
  return;
170
  }
171
+
172
+ isPlaying = true;
173
+ updatePlayButton(true);
174
+ updateStatus('🎵 Playing your music...', '#ccffcc');
175
+
176
+ // Simulate playback for 10 seconds
177
+ playTimer = setTimeout(() => {
178
+ stopPlay();
179
+ updateStatus('Playback finished!', 'white');
180
+ }, 10000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  }
182
+
183
+ function pausePlay() {
184
+ isPlaying = false;
185
+ updatePlayButton(false);
186
+ updateStatus('⏸️ Paused', '#ffffcc');
187
+
188
+ if (playTimer) {
189
+ clearTimeout(playTimer);
190
+ playTimer = null;
191
  }
192
  }
193
+
194
+ function stopPlay() {
 
 
 
 
195
  isPlaying = false;
196
  updatePlayButton(false);
197
+ updateStatus('⏹️ Stopped', 'white');
198
+
199
+ if (playTimer) {
200
+ clearTimeout(playTimer);
201
+ playTimer = null;
 
 
202
  }
203
  }
204
+
205
+ // Add hover effects to buttons
206
  document.addEventListener('DOMContentLoaded', function() {
207
+ const buttons = document.querySelectorAll('#player button');
 
 
 
 
 
 
 
 
 
 
 
208
  buttons.forEach(btn => {
209
  btn.addEventListener('mouseenter', function() {
210
+ this.style.transform = 'scale(1.05)';
211
+ this.style.boxShadow = '0 5px 15px rgba(0,0,0,0.2)';
212
  });
213
  btn.addEventListener('mouseleave', function() {
214
+ this.style.transform = 'scale(1)';
215
+ this.style.boxShadow = 'none';
216
  });
217
  });
 
 
218
  });
219
  </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  """)
221
 
222
+ # Quick examples
223
+ gr.HTML("<h3 style='margin-top: 30px;'>🎨 Quick Examples:</h3>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
  with gr.Row():
226
+ ex1 = gr.Button("🎮 Chiptune", size="sm")
227
+ ex2 = gr.Button("🌙 Lofi", size="sm")
228
+ ex3 = gr.Button("⚡ Techno", size="sm")
229
+ ex4 = gr.Button("🎹 Jazz", size="sm")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
  # Event handlers
232
  generate_btn.click(
233
  fn=generate_music,
234
+ inputs=[description, tempo],
235
+ outputs=[code_output]
236
  )
237
 
238
+ # Example handlers
239
+ ex1.click(lambda: "8-bit chiptune with bouncy melody and simple drums", outputs=[description])
240
+ ex2.click(lambda: "mellow lofi with soft piano chords and gentle drums", outputs=[description])
241
+ ex3.click(lambda: "driving techno with heavy bass and pounding kick", outputs=[description])
242
+ ex4.click(lambda: "smooth jazz with walking bass and chord progressions", outputs=[description])
243
+
 
 
 
 
 
 
 
 
244
  gr.HTML("""
245
+ <div style='text-align: center; margin-top: 30px; opacity: 0.7;'>
246
+ <p>Made for National Make Music Day 🎵</p>
 
247
  </div>
248
  """)
249
 
 
250
  if __name__ == "__main__":
251
  app.launch(
252
  share=False,
253
  server_name="0.0.0.0",
254
+ server_port=7860
 
255
  )