baouws commited on
Commit
1235da2
Β·
verified Β·
1 Parent(s): 6b96f0e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +603 -137
app.py CHANGED
@@ -1,189 +1,655 @@
1
  import gradio as gr
2
  import requests
3
  import json
 
 
 
 
4
 
5
- def generate_strudel_code(user_text):
6
- """Generate Strudel code using Hugging Face API"""
 
 
 
 
7
 
8
- # Simple mapping for common requests to ensure it works
9
- simple_patterns = {
10
- "drum": 'sound("bd hh sd hh")',
11
- "beat": 'sound("bd ~ sd ~")',
12
- "melody": 'note("c d e f g a b c5")',
13
- "bass": 'note("c2 ~ c2 g2").sound("sawtooth")',
14
- "piano": 'note("c e g c5").sound("piano")',
15
- "ambient": 'note("c e g").slow(4).room(0.8)',
16
- "fast": 'note("c d e f g a b c5").fast(2)',
17
- "jazz": 'note("c e g b d5 f5").slow(2)'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  }
19
 
20
- # Check for keywords and return appropriate pattern
21
- user_lower = user_text.lower()
22
- for keyword, pattern in simple_patterns.items():
23
- if keyword in user_lower:
24
- return pattern
 
 
 
 
 
 
 
 
25
 
26
- # Default pattern
27
- return 'note("c d e f").sound("triangle")'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- def create_simple_player_html(strudel_code):
30
- """Create a simple HTML player"""
31
- return f'''
32
- <div style="background: #2d2d2d; padding: 20px; border-radius: 8px; color: white; font-family: Arial;">
33
- <h3>🎡 Your Strudel Code:</h3>
34
- <div style="background: #1a1a1a; padding: 15px; border-radius: 4px; font-family: monospace; margin: 10px 0;">
35
- {strudel_code}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  </div>
37
 
38
- <div style="text-align: center; margin: 20px 0;">
39
- <button onclick="playMusic()" style="background: #4CAF50; color: white; border: none; padding: 12px 24px; border-radius: 4px; font-size: 16px; margin: 5px; cursor: pointer;">
40
- ▢️ Play
41
- </button>
42
- <button onclick="stopMusic()" style="background: #f44336; color: white; border: none; padding: 12px 24px; border-radius: 4px; font-size: 16px; margin: 5px; cursor: pointer;">
43
- ⏹️ Stop
44
- </button>
45
  </div>
46
 
47
- <div id="status" style="text-align: center; margin: 10px 0;">Ready to play</div>
48
 
49
- <div style="margin-top: 20px; padding: 15px; background: #1a1a1a; border-radius: 4px;">
50
- <strong>How to use:</strong><br>
51
- 1. Click the ▢️ Play button<br>
52
- 2. Allow audio permissions if prompted<br>
53
- 3. Enjoy your generated music!<br>
54
- 4. Click ⏹️ Stop to stop playback
55
  </div>
56
  </div>
57
 
58
- <script src="https://unpkg.com/@strudel/core"></script>
59
- <script src="https://unpkg.com/@strudel/webaudio"></script>
60
-
61
  <script>
 
62
  let isPlaying = false;
 
63
  let currentPattern = null;
64
 
65
- async function playMusic() {{
66
- const status = document.getElementById('status');
67
-
68
- if (isPlaying) {{
69
- status.innerHTML = "Already playing!";
70
- return;
 
 
 
 
 
 
 
71
  }}
72
-
 
 
 
73
  try {{
74
- status.innerHTML = "Starting audio...";
 
 
75
 
76
- // Initialize Web Audio
77
- const audioContext = new (window.AudioContext || window.webkitAudioContext)();
78
- if (audioContext.state === 'suspended') {{
79
- await audioContext.resume();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }}
81
 
82
- // Simple audio generation using Web Audio API directly
83
- const oscillator = audioContext.createOscillator();
84
- const gainNode = audioContext.createGain();
85
-
86
- oscillator.connect(gainNode);
87
- gainNode.connect(audioContext.destination);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- // Create a simple melody based on the Strudel code
90
- const code = "{strudel_code}";
91
- let frequency = 440; // Default A4
 
 
 
 
92
 
93
- if (code.includes('c')) frequency = 261.63; // C4
94
- if (code.includes('d')) frequency = 293.66; // D4
95
- if (code.includes('e')) frequency = 329.63; // E4
96
- if (code.includes('f')) frequency = 349.23; // F4
97
- if (code.includes('g')) frequency = 392.00; // G4
98
 
99
- oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
100
- oscillator.type = code.includes('sawtooth') ? 'sawtooth' : 'sine';
 
 
 
 
 
 
 
 
 
101
 
102
- gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
103
- gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 3);
104
 
105
- oscillator.start(audioContext.currentTime);
106
- oscillator.stop(audioContext.currentTime + 3);
107
 
108
- currentPattern = oscillator;
109
- isPlaying = true;
110
- status.innerHTML = "🎡 Playing...";
111
 
112
- setTimeout(() => {{
113
- isPlaying = false;
114
- status.innerHTML = "Finished playing";
115
- }}, 3000);
 
 
 
 
 
 
 
 
 
 
116
 
117
  }} catch (error) {{
118
- console.error('Audio error:', error);
119
- status.innerHTML = "Audio error - try clicking play again";
120
  }}
121
  }}
122
 
123
- function stopMusic() {{
124
- if (currentPattern) {{
125
- try {{
126
- currentPattern.stop();
127
- }} catch (e) {{
128
- // Pattern might already be stopped
129
- }}
130
- currentPattern = null;
131
  }}
 
 
 
 
 
132
  isPlaying = false;
133
- document.getElementById('status').innerHTML = "Stopped";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  </script>
136
- '''
 
137
 
138
- def process_text_input(user_input):
139
- """Process user input and return playable HTML"""
140
- if not user_input.strip():
141
- return "Please enter a description of the music you want!"
142
-
143
- # Generate Strudel code
144
- strudel_code = generate_strudel_code(user_input)
145
 
146
- # Create player
147
- player_html = create_simple_player_html(strudel_code)
148
-
149
- return player_html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- # Create simple Gradio interface
152
- with gr.Blocks(title="Text to Strudel") as app:
153
-
154
- gr.Markdown("# 🎡 Simple Text to Music")
155
- gr.Markdown("Describe music and get a playable result!")
156
-
157
- with gr.Row():
158
- user_input = gr.Textbox(
159
- label="What music do you want?",
160
- placeholder="Try: 'drum beat', 'piano melody', 'bass line', 'ambient sounds'",
161
- lines=2
162
- )
163
 
164
- generate_btn = gr.Button("🎡 Make Music", variant="primary")
165
-
166
- output_html = gr.HTML(value="<p>Enter a description above and click 'Make Music'!</p>")
 
 
 
167
 
168
- generate_btn.click(
169
- fn=process_text_input,
170
- inputs=[user_input],
171
- outputs=[output_html]
172
- )
 
 
 
 
173
 
174
- # Quick examples
175
- gr.Markdown("### Try these:")
 
 
 
 
 
176
 
177
- with gr.Row():
178
- ex1 = gr.Button("πŸ₯ Drum Beat")
179
- ex2 = gr.Button("🎹 Piano Melody")
180
- ex3 = gr.Button("🎸 Bass Line")
181
- ex4 = gr.Button("πŸŒ™ Ambient")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
- ex1.click(lambda: ["drum beat", process_text_input("drum beat")], outputs=[user_input, output_html])
184
- ex2.click(lambda: ["piano melody", process_text_input("piano melody")], outputs=[user_input, output_html])
185
- ex3.click(lambda: ["bass line", process_text_input("bass line")], outputs=[user_input, output_html])
186
- ex4.click(lambda: ["ambient sounds", process_text_input("ambient sounds")], outputs=[user_input, output_html])
187
 
 
188
  if __name__ == "__main__":
189
- app.launch()
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import requests
3
  import json
4
+ import tempfile
5
+ import os
6
+ from typing import Optional
7
+ import random
8
 
9
+ # Free Hugging Face LLM model for text generation
10
+ MODEL_NAME = "microsoft/DialoGPT-medium"
11
+ HF_API_URL = f"https://api-inference.huggingface.co/models/{MODEL_NAME}"
12
+
13
+ def generate_strudel_code(music_style: str, api_token: Optional[str] = None) -> str:
14
+ """Generate Strudel code using expanded pattern library and optional LLM"""
15
 
16
+ # Expanded Strudel examples with more variety
17
+ strudel_examples = {
18
+ "jazz": [
19
+ 'note("c3 eb3 g3 bb3").slow(2).gain(0.8)',
20
+ 'note("f3 a3 c4 e4").slow(3).lpf(800)',
21
+ 'note("bb2 d3 f3 a3").slow(2.5).room(0.3)'
22
+ ],
23
+ "techno": [
24
+ 's("bd hh").fast(2).gain(0.9)',
25
+ 's("bd*2 ~ bd ~").stack(s("~ hh*4")).lpf(2000)',
26
+ 's("bd ~ ~ bd").stack(s("hh*8")).distort(0.1)'
27
+ ],
28
+ "ambient": [
29
+ 'note("c4 d4 e4 f4").slow(4).lpf(400).room(0.8)',
30
+ 'note("g3 bb3 d4 f4").slow(6).gain(0.6).delay(0.3)',
31
+ 'sine(200).slow(8).lpf(300).room(0.9)'
32
+ ],
33
+ "drum and bass": [
34
+ 's("bd*2 [~ sn] bd sn").fast(4)',
35
+ 's("bd ~ bd sn").fast(3).stack(s("hh*16").gain(0.3))',
36
+ 's("bd*2 ~ sn ~").fast(4).lpf(1200)'
37
+ ],
38
+ "house": [
39
+ 's("bd ~ ~ ~ bd ~ ~ ~").stack(s("~ hh ~ hh"))',
40
+ 's("bd*2 ~ ~ bd ~ ~ ~").stack(s("hh*4")).lpf(800)',
41
+ 's("bd ~ bd ~").stack(s("~ hh*2")).gain(0.8)'
42
+ ],
43
+ "classical": [
44
+ 'note("c4 d4 e4 f4 g4 a4 b4 c5").slow(8)',
45
+ 'note("g3 b3 d4 g4").slow(4).stack(note("d2 g2").slow(2))',
46
+ 'note("c4 e4 g4 c5").slow(6).gain(0.7)'
47
+ ],
48
+ "hip hop": [
49
+ 's("bd sn bd sn").stack(s("hh*8"))',
50
+ 's("bd*2 ~ sn ~").stack(s("hh ~ hh ~")).slow(0.8)',
51
+ 's("bd ~ sn bd").stack(s("hh*4")).lpf(1000)'
52
+ ],
53
+ "rock": [
54
+ 's("bd ~ sn ~").stack(note("e2 ~ g2 ~"))',
55
+ 's("bd bd sn ~").stack(note("a2 c3 e3").slow(2))',
56
+ 's("bd ~ sn bd").stack(note("e2 g2").slow(1.5))'
57
+ ],
58
+ "blues": [
59
+ 'note("c3 eb3 f3 g3 bb3").slow(3)',
60
+ 'note("e2 a2 b2 e3").slow(4).gain(0.8)',
61
+ 'note("g2 bb2 d3 f3").slow(3.5).room(0.2)'
62
+ ],
63
+ "reggae": [
64
+ 's("~ bd ~ sn").slow(2)',
65
+ 's("~ bd*2 ~ sn").stack(s("hh ~ hh ~")).slow(1.5)',
66
+ 's("~ bd ~ sn ~").slow(2.5).gain(0.7)'
67
+ ],
68
+ "electronic": [
69
+ 'square(220).slow(4).lpf(800)',
70
+ 's("bd*4").stack(sawtooth(110).slow(2))',
71
+ 'sine(440).fast(2).lpf(1000).delay(0.2)'
72
+ ],
73
+ "minimal": [
74
+ 's("bd ~ ~ ~").slow(2)',
75
+ 'note("c4").slow(8).gain(0.5)',
76
+ 's("~ ~ bd ~").stack(sine(200).slow(16))'
77
+ ],
78
+ "funk": [
79
+ 's("bd ~ sn bd").stack(note("e2 ~ a2 ~"))',
80
+ 's("bd*2 sn ~ bd").fast(1.2).stack(s("hh*8"))',
81
+ 'note("a2 c3 e3").slow(2).stack(s("bd sn"))'
82
+ ]
83
  }
84
 
85
+ # Find matching style
86
+ best_match = None
87
+ for style, patterns in strudel_examples.items():
88
+ if style.lower() in music_style.lower():
89
+ best_match = random.choice(patterns)
90
+ break
91
+
92
+ # If no direct match, try partial matches
93
+ if not best_match:
94
+ for style, patterns in strudel_examples.items():
95
+ if any(word in music_style.lower() for word in style.split()):
96
+ best_match = random.choice(patterns)
97
+ break
98
 
99
+ # Try API generation if token provided and no pattern match
100
+ if not best_match and api_token:
101
+ try:
102
+ headers = {
103
+ "Authorization": f"Bearer {api_token}",
104
+ "Content-Type": "application/json"
105
+ }
106
+
107
+ prompt = f"Create Strudel live coding music pattern for {music_style}. Use functions: note(), s(), sound(), sine(), square(), sawtooth(), slow(), fast(), stack(), lpf(), gain(), room(), delay(). Example: s('bd hh').fast(2)"
108
+
109
+ payload = {
110
+ "inputs": prompt,
111
+ "parameters": {
112
+ "max_length": 80,
113
+ "temperature": 0.8,
114
+ "return_full_text": False,
115
+ "num_return_sequences": 1
116
+ }
117
+ }
118
+
119
+ response = requests.post(HF_API_URL, headers=headers, json=payload, timeout=15)
120
+ if response.status_code == 200:
121
+ result = response.json()
122
+ if isinstance(result, list) and len(result) > 0:
123
+ generated_text = result[0].get('generated_text', '').strip()
124
+ # Validate that it contains Strudel functions
125
+ if generated_text and any(func in generated_text for func in
126
+ ['note(', 's(', 'sound(', 'sine(', 'square(', 'sawtooth(']):
127
+ return generated_text
128
+ except Exception as e:
129
+ print(f"API Error: {e}")
130
+
131
+ # Return best match or default
132
+ return best_match or 'note("c4 d4 e4 f4").slow(2).gain(0.7)'
133
 
134
+ def create_strudel_html(strudel_code: str) -> str:
135
+ """Create simplified HTML with Strudel player that works better in HF Spaces"""
136
+ return f"""
137
+ <!DOCTYPE html>
138
+ <html lang="en">
139
+ <head>
140
+ <meta charset="UTF-8">
141
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
142
+ <title>Strudel Music Player</title>
143
+ <script src="https://unpkg.com/tone@latest/build/Tone.js"></script>
144
+ <style>
145
+ * {{
146
+ box-sizing: border-box;
147
+ margin: 0;
148
+ padding: 0;
149
+ }}
150
+
151
+ body {{
152
+ font-family: 'Courier New', Arial, sans-serif;
153
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
154
+ color: white;
155
+ min-height: 100vh;
156
+ display: flex;
157
+ flex-direction: column;
158
+ align-items: center;
159
+ justify-content: center;
160
+ padding: 20px;
161
+ }}
162
+
163
+ .container {{
164
+ background: rgba(0, 0, 0, 0.3);
165
+ backdrop-filter: blur(10px);
166
+ border-radius: 15px;
167
+ padding: 30px;
168
+ max-width: 600px;
169
+ width: 100%;
170
+ text-align: center;
171
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
172
+ }}
173
+
174
+ h1 {{
175
+ margin-bottom: 20px;
176
+ font-size: 2.5em;
177
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
178
+ }}
179
+
180
+ .code-display {{
181
+ background: rgba(0, 0, 0, 0.6);
182
+ padding: 20px;
183
+ border-radius: 10px;
184
+ margin: 20px 0;
185
+ font-family: 'Courier New', monospace;
186
+ font-size: 16px;
187
+ border-left: 4px solid #4CAF50;
188
+ word-break: break-all;
189
+ text-align: left;
190
+ }}
191
+
192
+ .controls {{
193
+ margin: 30px 0;
194
+ display: flex;
195
+ gap: 15px;
196
+ justify-content: center;
197
+ flex-wrap: wrap;
198
+ }}
199
+
200
+ button {{
201
+ padding: 12px 24px;
202
+ font-size: 16px;
203
+ font-weight: bold;
204
+ background: linear-gradient(45deg, #4CAF50, #45a049);
205
+ color: white;
206
+ border: none;
207
+ border-radius: 25px;
208
+ cursor: pointer;
209
+ transition: all 0.3s ease;
210
+ box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);
211
+ min-width: 100px;
212
+ }}
213
+
214
+ button:hover:not(:disabled) {{
215
+ transform: translateY(-2px);
216
+ box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);
217
+ }}
218
+
219
+ button:disabled {{
220
+ background: #666;
221
+ cursor: not-allowed;
222
+ transform: none;
223
+ box-shadow: none;
224
+ }}
225
+
226
+ .status {{
227
+ font-size: 18px;
228
+ font-weight: bold;
229
+ margin: 20px 0;
230
+ padding: 10px;
231
+ border-radius: 5px;
232
+ transition: all 0.3s ease;
233
+ }}
234
+
235
+ .playing {{
236
+ background: rgba(76, 175, 80, 0.2);
237
+ color: #4CAF50;
238
+ }}
239
+
240
+ .stopped {{
241
+ background: rgba(244, 67, 54, 0.2);
242
+ color: #f44336;
243
+ }}
244
+
245
+ .info {{
246
+ margin-top: 20px;
247
+ font-size: 14px;
248
+ opacity: 0.8;
249
+ line-height: 1.5;
250
+ }}
251
+
252
+ .error {{
253
+ background: rgba(244, 67, 54, 0.2);
254
+ color: #f44336;
255
+ padding: 10px;
256
+ border-radius: 5px;
257
+ margin: 10px 0;
258
+ }}
259
+ </style>
260
+ </head>
261
+ <body>
262
+ <div class="container">
263
+ <h1>🎡 Strudel Player</h1>
264
+
265
+ <div class="code-display">
266
+ <strong>Generated Pattern:</strong><br>
267
+ <code id="codeDisplay">{strudel_code}</code>
268
  </div>
269
 
270
+ <div class="controls">
271
+ <button id="playBtn" onclick="playMusic()">▢️ Play</button>
272
+ <button id="pauseBtn" onclick="pauseMusic()" disabled>⏸️ Pause</button>
273
+ <button id="stopBtn" onclick="stopMusic()" disabled>⏹️ Stop</button>
 
 
 
274
  </div>
275
 
276
+ <div id="status" class="status stopped">Click Play to start audio</div>
277
 
278
+ <div class="info">
279
+ <p>🎧 Use headphones for best experience</p>
280
+ <p>⚑ Powered by Web Audio API & Tone.js</p>
 
 
 
281
  </div>
282
  </div>
283
 
 
 
 
284
  <script>
285
+ // Simple audio player using Tone.js as fallback
286
  let isPlaying = false;
287
+ let audioContext = null;
288
  let currentPattern = null;
289
 
290
+ // Initialize audio context on first user interaction
291
+ async function initAudio() {{
292
+ try {{
293
+ if (!audioContext) {{
294
+ await Tone.start();
295
+ audioContext = Tone.getContext();
296
+ updateStatus('Audio initialized - Ready to play', 'stopped');
297
+ }}
298
+ return true;
299
+ }} catch (error) {{
300
+ console.error('Audio initialization failed:', error);
301
+ updateStatus('Audio initialization failed: ' + error.message, 'error');
302
+ return false;
303
  }}
304
+ }}
305
+
306
+ // Simple pattern interpreter (basic Strudel-inspired)
307
+ function interpretPattern(code) {{
308
  try {{
309
+ // Very basic pattern parsing - in a real implementation you'd want full Strudel
310
+ const synth = new Tone.Synth().toDestination();
311
+ const player = new Tone.Player().toDestination();
312
 
313
+ // Simple note pattern recognition
314
+ const noteMatch = code.match(/note\\(["']([^"']+)["']\\)/);
315
+ if (noteMatch) {{
316
+ const notes = noteMatch[1].split(' ');
317
+ let time = 0;
318
+ const interval = 0.5; // Default timing
319
+
320
+ // Schedule notes
321
+ notes.forEach((note, index) => {{
322
+ if (note.trim()) {{
323
+ Tone.Transport.schedule((time) => {{
324
+ synth.triggerAttackRelease(note + '4', '8n', time);
325
+ }}, time);
326
+ time += interval;
327
+ }}
328
+ }});
329
+
330
+ return {{ duration: time, type: 'notes' }};
331
  }}
332
 
333
+ // Simple drum pattern recognition
334
+ const drumMatch = code.match(/s\\(["']([^"']+)["']\\)/);
335
+ if (drumMatch) {{
336
+ // Simulate basic drum sounds with synth
337
+ const kicks = drumMatch[1].split(' ');
338
+ let time = 0;
339
+ const interval = 0.25;
340
+
341
+ kicks.forEach((drum, index) => {{
342
+ if (drum.includes('bd')) {{
343
+ Tone.Transport.schedule((time) => {{
344
+ synth.triggerAttackRelease('C2', '8n', time);
345
+ }}, time);
346
+ }} else if (drum.includes('sn')) {{
347
+ Tone.Transport.schedule((time) => {{
348
+ synth.triggerAttackRelease('E4', '16n', time);
349
+ }}, time);
350
+ }} else if (drum.includes('hh')) {{
351
+ Tone.Transport.schedule((time) => {{
352
+ synth.triggerAttackRelease('A5', '32n', time);
353
+ }}, time);
354
+ }}
355
+ time += interval;
356
+ }});
357
+
358
+ return {{ duration: time, type: 'drums' }};
359
+ }}
360
 
361
+ // Fallback - play a simple melody
362
+ const melody = ['C4', 'D4', 'E4', 'F4'];
363
+ melody.forEach((note, index) => {{
364
+ Tone.Transport.schedule((time) => {{
365
+ synth.triggerAttackRelease(note, '4n', time);
366
+ }}, index * 0.5);
367
+ }});
368
 
369
+ return {{ duration: 2, type: 'fallback' }};
 
 
 
 
370
 
371
+ }} catch (error) {{
372
+ console.error('Pattern interpretation error:', error);
373
+ updateStatus('Pattern error: ' + error.message, 'error');
374
+ return null;
375
+ }}
376
+ }}
377
+
378
+ async function playMusic() {{
379
+ try {{
380
+ const audioReady = await initAudio();
381
+ if (!audioReady) return;
382
 
383
+ if (isPlaying) return;
 
384
 
385
+ updateStatus('🎡 Playing pattern...', 'playing');
 
386
 
387
+ const code = document.getElementById('codeDisplay').textContent;
388
+ currentPattern = interpretPattern(code);
 
389
 
390
+ if (currentPattern) {{
391
+ Tone.Transport.start();
392
+ isPlaying = true;
393
+ updateControls();
394
+
395
+ // Auto-stop after pattern duration
396
+ setTimeout(() => {{
397
+ if (isPlaying) {{
398
+ stopMusic();
399
+ }}
400
+ }}, (currentPattern.duration + 1) * 1000);
401
+ }} else {{
402
+ updateStatus('Failed to parse pattern', 'error');
403
+ }}
404
 
405
  }} catch (error) {{
406
+ console.error('Playback error:', error);
407
+ updateStatus('Playback error: ' + error.message, 'error');
408
  }}
409
  }}
410
 
411
+ function pauseMusic() {{
412
+ if (isPlaying) {{
413
+ Tone.Transport.pause();
414
+ isPlaying = false;
415
+ updateStatus('⏸️ Paused', 'stopped');
416
+ updateControls();
 
 
417
  }}
418
+ }}
419
+
420
+ function stopMusic() {{
421
+ Tone.Transport.stop();
422
+ Tone.Transport.cancel();
423
  isPlaying = false;
424
+ currentPattern = null;
425
+ updateStatus('⏹️ Stopped', 'stopped');
426
+ updateControls();
427
+ }}
428
+
429
+ function updateControls() {{
430
+ document.getElementById('playBtn').disabled = isPlaying;
431
+ document.getElementById('pauseBtn').disabled = !isPlaying;
432
+ document.getElementById('stopBtn').disabled = !isPlaying;
433
+ }}
434
+
435
+ function updateStatus(message, type) {{
436
+ const status = document.getElementById('status');
437
+ status.textContent = message;
438
+ status.className = `status ${{type}}`;
439
  }}
440
+
441
+ // Cleanup on page unload
442
+ window.addEventListener('beforeunload', () => {{
443
+ if (isPlaying) {{
444
+ stopMusic();
445
+ }}
446
+ }});
447
+
448
+ // Handle visibility changes
449
+ document.addEventListener('visibilitychange', () => {{
450
+ if (document.hidden && isPlaying) {{
451
+ pauseMusic();
452
+ }}
453
+ }});
454
+
455
+ console.log('Strudel player initialized');
456
  </script>
457
+ </body>
458
+ </html>"""
459
 
460
+ def generate_and_play(music_style: str, api_token: Optional[str] = None):
461
+ """Generate Strudel code and return playable HTML"""
462
+ if not music_style.strip():
463
+ return "❌ Please enter a music style description.", "<p>Enter a music style to generate audio</p>"
 
 
 
464
 
465
+ try:
466
+ # Generate the Strudel code
467
+ strudel_code = generate_strudel_code(music_style, api_token)
468
+
469
+ # Create HTML file
470
+ html_content = create_strudel_html(strudel_code)
471
+
472
+ # Save to temporary file for Gradio
473
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as f:
474
+ f.write(html_content)
475
+ html_file_path = f.name
476
+
477
+ success_msg = f"βœ… Generated Strudel pattern: `{strudel_code}`\n\n🎡 Click Play in the player below to hear your music!"
478
+
479
+ return success_msg, html_content
480
+
481
+ except Exception as e:
482
+ error_msg = f"❌ Error generating music: {str(e)}"
483
+ return error_msg, "<p>Error generating audio player</p>"
484
 
485
+ def create_interface():
486
+ """Create the Gradio interface"""
 
 
 
 
 
 
 
 
 
 
487
 
488
+ # Custom CSS for better appearance
489
+ custom_css = """
490
+ .gradio-container {
491
+ max-width: 900px !important;
492
+ margin: auto !important;
493
+ }
494
 
495
+ .main-header {
496
+ text-align: center;
497
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
498
+ -webkit-background-clip: text;
499
+ -webkit-text-fill-color: transparent;
500
+ font-size: 2.5em;
501
+ font-weight: bold;
502
+ margin-bottom: 20px;
503
+ }
504
 
505
+ .examples-section {
506
+ background: #f8f9fa;
507
+ border-radius: 10px;
508
+ padding: 15px;
509
+ margin: 10px 0;
510
+ }
511
+ """
512
 
513
+ with gr.Blocks(
514
+ title="🎡 Strudel Music Generator",
515
+ theme=gr.themes.Soft(primary_hue="blue", secondary_hue="purple"),
516
+ css=custom_css
517
+ ) as demo:
518
+
519
+ gr.HTML("""
520
+ <div class="main-header">
521
+ 🎡 Strudel Music Generator
522
+ </div>
523
+ """)
524
+
525
+ gr.Markdown("""
526
+ Generate algorithmic music patterns using [Strudel.cc](https://strudel.cc/) syntax!
527
+
528
+ **How to use:**
529
+ 1. 🎼 Describe your desired music style
530
+ 2. πŸ”‘ (Optional) Add your Hugging Face API token for AI-generated patterns
531
+ 3. 🎡 Click "Generate Music" to create your pattern
532
+ 4. ▢️ Use the audio player controls to play your music
533
+ """)
534
+
535
+ with gr.Row():
536
+ with gr.Column(scale=2):
537
+ music_input = gr.Textbox(
538
+ label="🎼 Music Style Description",
539
+ placeholder="Try: 'jazz piano', 'techno beats', 'ambient soundscape', 'funky bass'...",
540
+ lines=2,
541
+ max_lines=3
542
+ )
543
+
544
+ api_token_input = gr.Textbox(
545
+ label="πŸ”‘ Hugging Face API Token (Optional)",
546
+ placeholder="hf_... (for AI-generated patterns)",
547
+ type="password",
548
+ info="Get your free token at https://huggingface.co/settings/tokens"
549
+ )
550
+
551
+ generate_btn = gr.Button(
552
+ "🎡 Generate Music",
553
+ variant="primary",
554
+ size="lg"
555
+ )
556
+
557
+ with gr.Column(scale=1):
558
+ gr.Markdown("""
559
+ <div class="examples-section">
560
+
561
+ ### 🎨 Style Examples:
562
+ - **Jazz**: swing, bebop, smooth
563
+ - **Electronic**: techno, house, ambient
564
+ - **Classical**: piano, strings, orchestral
565
+ - **Urban**: hip hop, reggae, funk
566
+ - **Rock**: blues, metal, indie
567
+ - **Experimental**: minimal, drone, glitch
568
+
569
+ </div>
570
+ """)
571
+
572
+ with gr.Row():
573
+ code_output = gr.Textbox(
574
+ label="πŸ“ Generated Code & Status",
575
+ interactive=False,
576
+ lines=4,
577
+ show_copy_button=True
578
+ )
579
+
580
+ with gr.Row():
581
+ audio_player = gr.HTML(
582
+ label="🎡 Interactive Audio Player",
583
+ value="<div style='text-align: center; padding: 40px; background: #f0f0f0; border-radius: 10px;'><h3>🎼 Generate music to see the player</h3><p>Your interactive audio player will appear here</p></div>",
584
+ elem_id="audio-player"
585
+ )
586
+
587
+ # Event handlers
588
+ generate_btn.click(
589
+ fn=generate_and_play,
590
+ inputs=[music_input, api_token_input],
591
+ outputs=[code_output, audio_player],
592
+ show_progress=True
593
+ )
594
+
595
+ # Enter key support
596
+ music_input.submit(
597
+ fn=generate_and_play,
598
+ inputs=[music_input, api_token_input],
599
+ outputs=[code_output, audio_player]
600
+ )
601
+
602
+ # Example interactions
603
+ gr.Examples(
604
+ examples=[
605
+ ["jazz piano with swing rhythm"],
606
+ ["minimal techno beat"],
607
+ ["ambient electronic soundscape"],
608
+ ["funky bass line groove"],
609
+ ["classical piano arpeggios"],
610
+ ["drum and bass breakbeat"],
611
+ ["reggae dub rhythm"],
612
+ ["experimental glitch sounds"]
613
+ ],
614
+ inputs=[music_input],
615
+ label="🎯 Quick Start Examples"
616
+ )
617
+
618
+ gr.Markdown("""
619
+ ---
620
+
621
+ ### πŸŽ“ About Strudel
622
+
623
+ **Strudel** is a live coding language for algorithmic music composition. This app generates Strudel patterns and plays them using Web Audio API.
624
+
625
+ **Key Functions:**
626
+ - `note("c d e f")` - Play musical notes
627
+ - `s("bd hh sn")` - Play drum samples (bd=bass drum, hh=hi-hat, sn=snare)
628
+ - `sound("piano")` - Use instrument sounds
629
+ - `.slow(2)` / `.fast(2)` - Change tempo
630
+ - `.stack()` - Layer multiple patterns
631
+ - `.lpf(800)` - Low-pass filter
632
+ - `.gain(0.5)` - Volume control
633
+
634
+ **πŸ”§ Technical Notes:**
635
+ - Audio runs entirely in your browser
636
+ - No audio files needed - everything is synthesized
637
+ - Works best with modern browsers and headphones
638
+ - Patterns are simplified for web compatibility
639
+
640
+ **πŸš€ Want full Strudel?** Visit [strudel.cc](https://strudel.cc/) for the complete live coding environment!
641
+ """)
642
 
643
+ return demo
 
 
 
644
 
645
+ # Launch configuration
646
  if __name__ == "__main__":
647
+ demo = create_interface()
648
+ demo.launch(
649
+ server_name="0.0.0.0",
650
+ server_port=7860,
651
+ share=False,
652
+ show_error=True,
653
+ show_tips=False,
654
+ enable_queue=True
655
+ )