PRANJAL KAR commited on
Commit
545764f
Β·
1 Parent(s): e6749f4

Implement initial project structure and setup

Browse files
Files changed (3) hide show
  1. app.py +504 -0
  2. req.txt +8 -0
  3. utils.py +492 -0
app.py ADDED
@@ -0,0 +1,504 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import json
4
+ import logging
5
+ import shutil
6
+ import tempfile
7
+ from pathlib import Path
8
+ import numpy as np
9
+ from utils import (
10
+ rename_files_remove_spaces,
11
+ load_audio_files,
12
+ get_stems,
13
+ generate_section_variants,
14
+ export_section_variants,
15
+ edm_arrangement_tab,
16
+ )
17
+
18
+
19
+ logger = logging.getLogger(__name__)
20
+ logger.setLevel(logging.INFO)
21
+ # Global variable to store the temporary directory
22
+ TEMP_DIR = "test"
23
+ # Global variable to store the selected variants
24
+ SELECTED_VARIANTS = {}
25
+ # Global variable to store all variants for each section
26
+ ALL_VARIANTS = {}
27
+ # Global variable to store the uploaded stems
28
+ UPLOADED_STEMS = {}
29
+
30
+
31
+ def process_uploaded_files(files, progress=gr.Progress()):
32
+ """Process uploaded files and return basic info"""
33
+ global TEMP_DIR, UPLOADED_STEMS
34
+
35
+ try:
36
+ if not files:
37
+ return "Error: No files uploaded", []
38
+
39
+ progress(0, desc="Starting process...")
40
+
41
+ # Create a temporary directory for processing
42
+ TEMP_DIR = "test" #tempfile.mkdtemp()
43
+ try:
44
+ # Copy uploaded files to temp directory
45
+ progress(0.2, desc="Copying uploaded files...")
46
+ for file in files:
47
+ if file.name.lower().endswith(".wav"):
48
+ shutil.copy2(file.name, TEMP_DIR)
49
+
50
+ # First rename all files to remove spaces
51
+ progress(0.5, desc="Renaming files...")
52
+ rename_files_remove_spaces(TEMP_DIR)
53
+
54
+ # Load audio files
55
+ progress(0.8, desc="Loading audio files...")
56
+ UPLOADED_STEMS = load_audio_files(TEMP_DIR)
57
+ if not UPLOADED_STEMS:
58
+ return "Error: No stems loaded", []
59
+
60
+ # Get stem names
61
+ stem_names = get_stems(TEMP_DIR)
62
+
63
+ progress(1.0, desc="Complete!")
64
+ return f"Successfully loaded {len(stem_names)} stems", stem_names
65
+
66
+ except Exception as e:
67
+ if os.path.exists(TEMP_DIR):
68
+ shutil.rmtree(TEMP_DIR)
69
+ TEMP_DIR = None
70
+ raise e
71
+
72
+ except Exception as e:
73
+ return f"Error occurred: {str(e)}", []
74
+
75
+
76
+ def generate_section_variants_handler(
77
+ section_type, bpm_value, bars_value, p_value, progress=gr.Progress()
78
+ ):
79
+ """Handler function for generating section variants"""
80
+ global TEMP_DIR, ALL_VARIANTS, UPLOADED_STEMS
81
+
82
+ if not TEMP_DIR or not os.path.exists(TEMP_DIR):
83
+ return (
84
+ "Error: No stems loaded. Please upload stems first.",
85
+ None,
86
+ None,
87
+ None,
88
+ None,
89
+ )
90
+
91
+ try:
92
+ progress(0.1, desc=f"Generating {section_type} variants...")
93
+
94
+ # Generate variants
95
+ variants = generate_section_variants(
96
+ TEMP_DIR,
97
+ UPLOADED_STEMS,
98
+ section_type,
99
+ bpm=int(bpm_value),
100
+ bars=int(bars_value),
101
+ p=float(p_value),
102
+ progress=progress
103
+ )
104
+
105
+ logger.info(f"S1: VARIANTS of section {section_type} : {variants}")
106
+
107
+ progress(0.4, desc="Variants generated")
108
+
109
+ # Store variants for later use
110
+ ALL_VARIANTS[section_type] = variants
111
+
112
+
113
+
114
+ # Export variants to audio files
115
+ variant_output_dir = os.path.join(TEMP_DIR, section_type + "_variants")
116
+ audio_paths = export_section_variants(
117
+ variants, variant_output_dir, section_type
118
+ )
119
+ progress(0.6, desc="Exporting audio files...")
120
+
121
+ logger.info(f"S2: AUDIO_PATHS of section {section_type} : {audio_paths}")
122
+
123
+ # Create audio elements for each variant
124
+ variant1_audio = audio_paths.get("variant1")
125
+ variant2_audio = audio_paths.get("variant2")
126
+ variant3_audio = audio_paths.get("variant3")
127
+ variant4_audio = audio_paths.get("variant4")
128
+
129
+ # Descriptions
130
+ descriptions = {key: data["description"] for key, data in variants.items()}
131
+ descriptions_json = json.dumps(descriptions, indent=2)
132
+
133
+ logger.info(f"S3: DESCRIPTIONS of section {section_type} : {descriptions_json}")
134
+
135
+ progress(1.0, desc="Complete!")
136
+
137
+ return (
138
+ f"Generated {len(variants)} variants for {section_type}",
139
+ variant1_audio,
140
+ variant2_audio,
141
+ variant3_audio,
142
+ variant4_audio,
143
+ descriptions_json,
144
+ )
145
+
146
+ except Exception as e:
147
+ return f"Error generating variants: {str(e)}", None, None, None, None, None
148
+
149
+
150
+ def select_variant(section_type, variant_num, append):
151
+ """Select a variant for a specific section, with an option to append. Retry up to 5 times on error."""
152
+ global ALL_VARIANTS, SELECTED_VARIANTS
153
+
154
+ max_retries = 5
155
+ for attempt in range(1, max_retries + 1):
156
+ try:
157
+ if section_type not in ALL_VARIANTS:
158
+ msg = f"No variants generated for {section_type} yet"
159
+ if attempt == max_retries:
160
+ return msg
161
+ continue
162
+
163
+ variant_key = f"variant{variant_num}"
164
+ if variant_key not in ALL_VARIANTS[section_type]:
165
+ msg = f"Variant {variant_num} not found for {section_type}"
166
+ if attempt == max_retries:
167
+ return msg
168
+ continue
169
+
170
+ # If appending, add to the list of selected variants
171
+ if append:
172
+ # For now, just overwrite with the latest selection (no append logic)
173
+ SELECTED_VARIANTS[section_type] = ALL_VARIANTS[section_type][variant_key]["config"]
174
+ else:
175
+ # Otherwise, replace the selection
176
+ SELECTED_VARIANTS[section_type] = ALL_VARIANTS[section_type][variant_key]["config"]
177
+
178
+ return f"Selected variant {variant_num} for {section_type} (Append: {append})"
179
+ except Exception as e:
180
+ msg = f"Error selecting variant (attempt {attempt}): {str(e)}"
181
+ if attempt == max_retries:
182
+ return msg
183
+ continue
184
+
185
+
186
+ def generate_full_track(
187
+ crossfade_ms,
188
+ output_track_name,
189
+ include_intro,
190
+ include_buildup,
191
+ include_breakdown,
192
+ include_drop,
193
+ include_outro,
194
+ progress=gr.Progress(),
195
+ ):
196
+ """Generate the full track from selected variants"""
197
+ global TEMP_DIR, SELECTED_VARIANTS, UPLOADED_STEMS
198
+
199
+ if not TEMP_DIR or not os.path.exists(TEMP_DIR):
200
+ return "Error: No stems loaded", None, None
201
+
202
+ try:
203
+ progress(0.1, desc="Preparing to generate full track...")
204
+
205
+ # Check which sections to include based on user selections and available variants
206
+ sections_to_include = {}
207
+ logger.info(f"SELECTED_VARIANTS: {SELECTED_VARIANTS}")
208
+
209
+ if include_intro and "intro" in SELECTED_VARIANTS:
210
+ sections_to_include["intro"] = SELECTED_VARIANTS["intro"]
211
+
212
+ if include_buildup and "buildup" in SELECTED_VARIANTS:
213
+ sections_to_include["buildup"] = SELECTED_VARIANTS["buildup"]
214
+
215
+ # We always include the full loop if it exists
216
+ if "full_loop" in SELECTED_VARIANTS:
217
+ sections_to_include["full_loop"] = SELECTED_VARIANTS["full_loop"]
218
+
219
+ if include_breakdown and "breakdown" in SELECTED_VARIANTS:
220
+ sections_to_include["breakdown"] = SELECTED_VARIANTS["breakdown"]
221
+
222
+ if include_drop and "drop" in SELECTED_VARIANTS:
223
+ sections_to_include["drop"] = SELECTED_VARIANTS["drop"]
224
+
225
+ if include_outro and "outro" in SELECTED_VARIANTS:
226
+ sections_to_include["outro"] = SELECTED_VARIANTS["outro"]
227
+
228
+ if not sections_to_include:
229
+ return "Error: No sections selected or available", None, None
230
+
231
+ progress(0.3, desc="Creating track structure...")
232
+
233
+ # Create the final track
234
+ final_track = None
235
+
236
+ # Define the order of sections
237
+ section_order = ["intro", "buildup", "full_loop", "breakdown", "drop", "outro"]
238
+
239
+ # Process each section in order
240
+ for section_name in section_order:
241
+ if section_name not in sections_to_include:
242
+ continue
243
+
244
+ progress(
245
+ 0.4 + 0.1 * section_order.index(section_name) / len(section_order),
246
+ desc=f"Processing {section_name}...",
247
+ )
248
+
249
+ # Get the selected variant config
250
+ variant_config = sections_to_include[section_name]
251
+
252
+ # Create temporary copy of stems to avoid modifying the originals
253
+ stems_copy = {k: v for k, v in UPLOADED_STEMS.items()}
254
+
255
+ # Create audio for this section
256
+ from utils import create_section_from_json
257
+
258
+ section_audio = create_section_from_json(variant_config, stems_copy)
259
+
260
+ # Add to final track
261
+ if final_track is None:
262
+ final_track = section_audio
263
+ else:
264
+ final_track = final_track.append(section_audio, crossfade=crossfade_ms)
265
+
266
+ progress(0.9, desc="Exporting final track...")
267
+
268
+ # Export the final track
269
+ full_track_path = os.path.join(TEMP_DIR, output_track_name)
270
+ final_track.export(full_track_path, format="wav")
271
+
272
+ # Create track summary
273
+ sections_list = list(sections_to_include.keys())
274
+ track_duration = len(final_track) / 1000 # in seconds
275
+
276
+ track_summary = {
277
+ "Sections included": sections_list,
278
+ "Total sections": len(sections_list),
279
+ "Duration": f"{int(track_duration // 60)}:{int(track_duration % 60):02d}",
280
+ "Crossfade": f"{crossfade_ms} ms",
281
+ }
282
+
283
+ progress(1.0, desc="Complete!")
284
+
285
+ return (
286
+ "Track generated successfully!",
287
+ full_track_path,
288
+ json.dumps(track_summary, indent=2),
289
+ )
290
+
291
+ except Exception as e:
292
+ return f"Error generating track: {str(e)}", None, None
293
+
294
+
295
+ def generate_full_loop_variants(bpm_value, bars_value, p_value, progress=gr.Progress()):
296
+ """Generate variants for the full loop section"""
297
+ return generate_section_variants_handler(
298
+ "full_loop", bpm_value, bars_value, p_value, progress
299
+ )
300
+
301
+
302
+ def create_section_ui(section_name, bpm_default, bars_default, p_default):
303
+ """Helper function to create UI elements for a section."""
304
+ with gr.Accordion(f"Generate {section_name.capitalize()} Variants", open=False):
305
+ with gr.Row():
306
+ with gr.Column(scale=1):
307
+ gr.Markdown(f"### {section_name.capitalize()} Parameters")
308
+ bpm_slider = gr.Slider(
309
+ label="BPM (Beats Per Minute)",
310
+ minimum=60,
311
+ maximum=180,
312
+ value=bpm_default,
313
+ step=1,
314
+ )
315
+ bars_slider = gr.Slider(
316
+ label="Number of Bars", minimum=4, maximum=64, value=bars_default, step=4
317
+ )
318
+ p_slider = gr.Slider(
319
+ label="Variation Parameter (p)",
320
+ minimum=0,
321
+ maximum=1,
322
+ value=p_default,
323
+ step=0.1,
324
+ )
325
+
326
+ generate_btn = gr.Button(
327
+ f"Generate {section_name.capitalize()} Variants", variant="primary"
328
+ )
329
+
330
+ with gr.Column(scale=2):
331
+ status = gr.Textbox(label="Status", interactive=False)
332
+ descriptions = gr.JSON(label="Variant Descriptions")
333
+
334
+ # Create empty lists to store the audio and checkbox components
335
+ variant_audio_list = []
336
+ select_btn_list = []
337
+
338
+ with gr.Row():
339
+ for i in range(1, 5):
340
+ with gr.Column():
341
+ gr.Markdown(f"### Variant {i}")
342
+ # Create and immediately append each component to its respective list
343
+ variant_audio = gr.Audio(label=f"Variant {i}", interactive=True)
344
+ variant_audio_list.append(variant_audio)
345
+
346
+ select_btn = gr.Checkbox(label=f"Select Variant {i}")
347
+ select_btn_list.append(select_btn)
348
+
349
+ return {
350
+ "bpm_slider": bpm_slider,
351
+ "bars_slider": bars_slider,
352
+ "p_slider": p_slider,
353
+ "generate_btn": generate_btn,
354
+ "status": status,
355
+ "descriptions": descriptions,
356
+ "variant_audio": variant_audio_list, # Return the complete list of audio components
357
+ "select_btn": select_btn_list, # Return the complete list of checkbox components
358
+ }
359
+
360
+ def setup_section_event_handlers(section_name, section_ui):
361
+ """Setup event handlers for a given section."""
362
+ section_type = gr.State(section_name)
363
+
364
+ # Generate button click event
365
+ section_ui["generate_btn"].click(
366
+ fn=generate_section_variants_handler,
367
+ inputs=[
368
+ section_type,
369
+ section_ui["bpm_slider"],
370
+ section_ui["bars_slider"],
371
+ section_ui["p_slider"],
372
+ ],
373
+ outputs=[
374
+ section_ui["status"],
375
+ *section_ui["variant_audio"],
376
+ section_ui["descriptions"],
377
+ ],
378
+ )
379
+
380
+ # Selection buttons change events
381
+ for i, select_btn in enumerate(section_ui["select_btn"], start=1):
382
+ variant_num = gr.State(i)
383
+ select_btn.change(
384
+ fn=select_variant,
385
+ inputs=[section_type, variant_num, gr.State(False)],
386
+ outputs=[section_ui["status"]],
387
+ )
388
+
389
+
390
+ # Load section configuration from a JSON file or string
391
+ section_config_json = """
392
+ {
393
+ "intro": {"bpm": 120, "bars": 8, "p": 0.3},
394
+ "buildup": {"bpm": 120, "bars": 16, "p": 0.4},
395
+ "breakdown": {"bpm": 120, "bars": 16, "p": 0.6},
396
+ "drop": {"bpm": 120, "bars": 16, "p": 0.7},
397
+ "outro": {"bpm": 120, "bars": 8, "p": 0.3}
398
+ }
399
+ """
400
+
401
+ sections = json.loads(section_config_json)
402
+
403
+ # Create Gradio interface
404
+ with gr.Blocks(title="Interactive Music Track Generator") as demo:
405
+ gr.Markdown("# Interactive Music Track Generator")
406
+ gr.Markdown(
407
+ "Upload your WAV stems, generate variants for each section, and create a full track"
408
+ )
409
+
410
+ # Global variables for UI state
411
+ stem_list = gr.State([])
412
+
413
+ with gr.Tab("1. Upload Stems"):
414
+ with gr.Row():
415
+ with gr.Column():
416
+ # File upload section
417
+ gr.Markdown("### Upload Files")
418
+ gr.Markdown("Drag and drop your WAV stem files here")
419
+ file_input = gr.File(
420
+ label="WAV Stems", file_count="multiple", file_types=[".wav"]
421
+ )
422
+
423
+ upload_btn = gr.Button("Upload and Process Files", variant="primary")
424
+
425
+ with gr.Column():
426
+ upload_status = gr.Textbox(label="Upload Status", interactive=False)
427
+ stem_display = gr.JSON(label="Available Stems")
428
+
429
+ with gr.Tab("1.1. ⏳ Finalise Sections Arrangement (In Progress)"):
430
+ gr.Markdown("### 🎢 Finalise Sections Arrangement")
431
+ gr.Markdown(
432
+ "πŸŽ›οΈ Use the diagram below to adjust the arrangement of your sections. Click on a section to edit its properties."
433
+ )
434
+ # Editable diagram with progress indicator
435
+ with gr.Row():
436
+ gr.Markdown("⏳ In Progress: Adjusting sections...")
437
+ edm_arrangement_tab()
438
+
439
+ with gr.Tab("2. Generate Section Variants"):
440
+ # Example of creating UI for sections dynamically
441
+ section_uis = {}
442
+ for section_name, params in sections.items():
443
+ section_uis[section_name] = create_section_ui(
444
+ section_name, params["bpm"], params["bars"], params["p"]
445
+ )
446
+ setup_section_event_handlers(section_name, section_uis[section_name])
447
+
448
+ with gr.Tab("3. Create Full Track"):
449
+ with gr.Row():
450
+ with gr.Column():
451
+ gr.Markdown("### Track Settings")
452
+ crossfade_ms = gr.Slider(
453
+ label="Crossfade Duration (ms)",
454
+ minimum=0,
455
+ maximum=2000,
456
+ value=500,
457
+ step=100,
458
+ )
459
+ output_track_name = gr.Textbox(
460
+ label="Output Filename",
461
+ value="full_track_output.wav",
462
+ placeholder="e.g., full_track_output.wav",
463
+ )
464
+
465
+ gr.Markdown("### Sections to Include")
466
+ include_intro = gr.Checkbox(label="Include Intro", value=True)
467
+ include_buildup = gr.Checkbox(label="Include buildup", value=True)
468
+ include_breakdown = gr.Checkbox(label="Include breakdown", value=True)
469
+ include_drop = gr.Checkbox(label="Include drop", value=True)
470
+ include_outro = gr.Checkbox(label="Include Outro", value=True)
471
+
472
+ generate_track_btn = gr.Button(
473
+ "Generate Full Track", variant="primary", scale=2
474
+ )
475
+
476
+ with gr.Column():
477
+ track_status = gr.Textbox(label="Status", interactive=False)
478
+ track_summary = gr.JSON(label="Track Summary")
479
+ full_track_audio = gr.Audio(label="Generated Full Track")
480
+
481
+ # Event handlers
482
+ upload_btn.click(
483
+ fn=process_uploaded_files,
484
+ inputs=[file_input],
485
+ outputs=[upload_status, stem_display],
486
+ )
487
+
488
+ # Generate full track
489
+ generate_track_btn.click(
490
+ fn=generate_full_track,
491
+ inputs=[
492
+ crossfade_ms,
493
+ output_track_name,
494
+ include_intro,
495
+ include_breakdown,
496
+ include_buildup,
497
+ include_drop,
498
+ include_outro,
499
+ ],
500
+ outputs=[track_status, full_track_audio, track_summary],
501
+ )
502
+
503
+ if __name__ == "__main__":
504
+ demo.launch()
req.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ pydub
2
+ gradio
3
+ groq
4
+ soundfile
5
+ tqdm
6
+ numpy
7
+ librosa
8
+ python-dotenv
utils.py ADDED
@@ -0,0 +1,492 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import json
4
+ import tempfile
5
+ import gradio as gr
6
+ from pydub import AudioSegment, silence
7
+ from pydub.effects import low_pass_filter, high_pass_filter
8
+ from tqdm import tqdm
9
+ from groq import Groq
10
+ from dotenv import load_dotenv
11
+ import random
12
+ from temp_choose import (
13
+ arrangements,
14
+ arrangement,
15
+ shift_arrangement,
16
+ editable_diagram,
17
+ update_section,
18
+ insert_section,
19
+ finalise,
20
+ load_variation,
21
+ )
22
+
23
+ load_dotenv()
24
+
25
+
26
+ def make_groq_call(stems, song_name, p, section_type=None, bpm=120, bars=16):
27
+ """
28
+ Make a call to the Groq API to get music production instructions.
29
+
30
+ Args:
31
+ stems (list): List of available stem files
32
+ song_name (str): Name of the song
33
+ p (float): Variation parameter (0-1)
34
+ section_type (str, optional): Specific section to generate variants for
35
+ bpm (int): Beats per minute
36
+ bars (int): Number of bars
37
+
38
+ Returns:
39
+ dict: JSON response with production instructions
40
+ """
41
+ client = Groq(api_key=os.getenv("GROQ_API_KEY"))
42
+
43
+ # Customize prompt based on whether we're generating a full track or section variants
44
+ if section_type:
45
+ system_content = """You are a very experienced music producer and analyst, with a deep understanding of music theory and production techniques and arrangement, with specific expertise in creating popular EDM/ dance music.
46
+ You are given a set of audio stems and asked to create multiple variants of a specific section of a track.
47
+ Your task is to provide detailed instructions on how to arrange and process the stems for each variant.
48
+ You will be given audio stems and asked to create multiple variants of a specific section of a track.
49
+ For each variant, return detailed instructions on how to arrange and process the stems.
50
+ Be creative and make each variant sound distinct while maintaining a coherent musical style."""
51
+
52
+ user_content = f"""I need 4 different variants for the {section_type} section of a track named "{song_name}".
53
+
54
+ Available stems: {stems}
55
+
56
+ BPM: {bpm}
57
+ Length of the section: {bars} bars
58
+
59
+ For each variant, please provide specific instructions on:
60
+ 1. Which stems to include
61
+ 2. What audio operations to apply (filters, fades, etc.)
62
+ 3. How the stems should be arranged
63
+
64
+ Make the variants diverse but coherent, with variation level p={p} (0=minimal variation, 1=maximum variation).
65
+
66
+ Return your response as a JSON object with this structure:
67
+ {{
68
+ "variant1": {{
69
+ "stems": ["stem1.wav", "stem2.wav"],
70
+ "operations": [
71
+ {{"stem": "stem1.wav", "operation": "low_pass_filter", "value": 500}},
72
+ {{"stem": "stem2.wav", "operation": "fade_in", "value": 1000}}
73
+ ],
74
+ "overlay": true,
75
+ "description": "A brief description of this variant"
76
+ }},
77
+ "variant2": {{ ... }},
78
+ "variant3": {{ ... }},
79
+ "variant4": {{ ... }}
80
+ }}
81
+ """
82
+ else:
83
+ system_content = """You are a very experienced music producer and analyst. For a given audio folder, that has the instruments, are combined to make a loop, and make some variations of it as well. After analyzing the code, you are supposed to return the code containing the different functions for producing the full track."""
84
+
85
+ user_content = f"""Now, you have a new song {song_name}, which has the following contents:
86
+ {stems}
87
+
88
+ BPM: {bpm}
89
+ Bars: {bars}
90
+
91
+ Return the code as per discussed in example. Make proper arrangements, for best groovy music. Create 3 variations and return code in JSON. And make sure to use as many instruments possible in each variation.
92
+
93
+ NOTE: And at least once, the MAIN loop should have ALL stems.
94
+
95
+ Like the variations should be like the main full loop, with some adjustments according to value of p={p} (p will remain in between 0-1; 0 means no variation in loop and 1 means high variation in loop), and then another variation can be a two times repeat of the full loop, with some effects in second time. Make intro better, with some more instruments.
96
+
97
+ The main loop should have ALL wav files, don't exclude any please. Return proper JSON, with all the keys and values.
98
+ """
99
+
100
+ completion = client.chat.completions.create(
101
+ model="gemma2-9b-it",
102
+ messages=[
103
+ {"role": "system", "content": system_content},
104
+ {"role": "user", "content": user_content},
105
+ ],
106
+ temperature=1,
107
+ top_p=1,
108
+ stream=False,
109
+ response_format={"type": "json_object"},
110
+ stop=None,
111
+ )
112
+
113
+ return json.loads(completion.choices[0].message.content)
114
+
115
+
116
+ def rename_files_remove_spaces(folder):
117
+ """Rename all files in the folder by removing spaces from filenames"""
118
+ files_renamed = 0
119
+ for file in os.listdir(folder):
120
+ if " " in file:
121
+ old_path = os.path.join(folder, file)
122
+ new_file = file.replace(" ", "")
123
+ new_path = os.path.join(folder, new_file)
124
+
125
+ # Only rename if the new file doesn't already exist
126
+ if not os.path.exists(new_path):
127
+ os.rename(old_path, new_path)
128
+ print(f"Renamed: {file} β†’ {new_file}")
129
+ files_renamed += 1
130
+
131
+ print(f"Total files renamed: {files_renamed}")
132
+
133
+
134
+ def load_audio_files(folder):
135
+ """Load all WAV files from a folder into memory"""
136
+ files = sorted([f for f in os.listdir(folder) if f.endswith(".wav")])
137
+ stems = {}
138
+ for file in tqdm(files, desc="Loading audio files"):
139
+ path = os.path.join(folder, file)
140
+ audio = AudioSegment.from_wav(path)
141
+ stems[file] = audio
142
+ return stems
143
+
144
+
145
+ def get_stems(folder):
146
+ """Get a list of all WAV files in a folder"""
147
+ files = sorted([f for f in os.listdir(folder) if f.endswith(".wav")])
148
+ return files
149
+
150
+
151
+ def apply_audio_operation(audio, operation, value):
152
+ """Apply various audio operations to an AudioSegment"""
153
+ if operation == "low_pass_filter":
154
+ return low_pass_filter(audio, value)
155
+ elif operation == "high_pass_filter":
156
+ return high_pass_filter(audio, value)
157
+ elif operation == "fade_in":
158
+ return audio.fade_in(value)
159
+ elif operation == "fade_out":
160
+ return audio.fade_out(value)
161
+ elif operation == "reverb":
162
+ # Simple reverb simulation by adding delayed and attenuated copies
163
+ result = audio
164
+ for delay in [50, 100, 150, 200]:
165
+ attenuated = audio - (value * 10) # Reduce volume based on reverb value
166
+ delayed = AudioSegment.silent(duration=delay) + attenuated
167
+ result = result.overlay(delayed)
168
+ return result
169
+ elif operation == "delay":
170
+ # Simulate delay by adding a delayed copy
171
+ result = audio
172
+ delayed = AudioSegment.silent(duration=value) + (audio - 6) # -6dB for the echo
173
+ return result.overlay(delayed)
174
+ elif operation == "distortion":
175
+ # Simulate distortion by adding some limiting/clipping
176
+ gain = 1.0 + (value * 5) # Boost the gain based on distortion value
177
+ return audio + (gain) # Add gain in dB
178
+ elif operation == "pitch_shift":
179
+ # Note: pydub doesn't natively support pitch shifting
180
+ print(f"Warning: Pitch shift not implemented, value: {value}")
181
+ return audio
182
+ elif operation == "volume":
183
+ # Adjust volume by dB
184
+ return audio + value
185
+ return audio
186
+
187
+
188
+ def create_section_from_json(section_config, stems):
189
+ """Create an audio section based on JSON configuration"""
190
+ if not section_config:
191
+ print("No configuration found for section")
192
+ return AudioSegment.empty()
193
+
194
+ section_stems = []
195
+ print(section_config)
196
+ for stem_name in section_config["stems"]:
197
+ # First try the original stem name
198
+ if stem_name in stems:
199
+ section_stems.append(stems[stem_name])
200
+ else:
201
+ # Try the name without spaces
202
+ no_spaces_name = stem_name.replace(" ", "")
203
+ if no_spaces_name in stems:
204
+ section_stems.append(stems[no_spaces_name])
205
+ else:
206
+ print(f"Warning: Stem {stem_name} not found (with or without spaces)")
207
+
208
+ # Apply operations to stems
209
+ processed_stems = {name: audio for name, audio in stems.items()}
210
+ for op in section_config.get("operations", []):
211
+ stem_name = op["stem"]
212
+ operation = op["operation"]
213
+ value = op["value"]
214
+
215
+ # Check both original and no-spaces versions
216
+ stem_key = None
217
+ if stem_name in processed_stems:
218
+ stem_key = stem_name
219
+ else:
220
+ no_spaces_name = stem_name.replace(" ", "")
221
+ if no_spaces_name in processed_stems:
222
+ stem_key = no_spaces_name
223
+
224
+ if stem_key and operation != "overlay":
225
+ processed_stems[stem_key] = apply_audio_operation(
226
+ processed_stems[stem_key], operation, value
227
+ )
228
+
229
+ # Collect the processed stems for this section
230
+ final_stems = []
231
+ for stem_name in section_config["stems"]:
232
+ if stem_name in processed_stems:
233
+ final_stems.append(processed_stems[stem_name])
234
+ else:
235
+ no_spaces_name = stem_name.replace(" ", "")
236
+ if no_spaces_name in processed_stems:
237
+ final_stems.append(processed_stems[no_spaces_name])
238
+
239
+ # Overlay stems if specified
240
+ # if section_config.get("overlay", True) and final_stems:
241
+ result = final_stems[0]
242
+ for stem in final_stems[1:]:
243
+ result = result.overlay(stem)
244
+ # return result
245
+ # Remove silences longer than 1.5 seconds (1500 ms)
246
+ silence_thresh = result.dBFS - 16 #
247
+ silent_chunks = silence.detect_silence(
248
+ result, min_silence_len=1500, silence_thresh=silence_thresh
249
+ )
250
+
251
+ segments = []
252
+ prev_end = 0
253
+ for start, end in silent_chunks:
254
+ if prev_end < start:
255
+ segments.append(result[prev_end:start])
256
+ prev_end = end
257
+ segments.append(result[prev_end:])
258
+
259
+ result = sum(segments)
260
+
261
+ return result
262
+
263
+ return AudioSegment.empty()
264
+
265
+
266
+ def generate_section_variants(
267
+ stems_folder, audio_stems, section_type, bpm, bars, p=0.5, progress=None
268
+ ):
269
+ """
270
+ Generate multiple variants for a specific section
271
+
272
+ Args:
273
+ stems_folder (str): Path to folder containing stem files
274
+ section_type (str): Type of section (intro, verse, chorus, etc.)
275
+ bpm (int): Beats per minute
276
+ bars (int): Number of bars
277
+ p (float): Variation parameter (0-1)
278
+
279
+ Returns:
280
+ dict: Dictionary of variant audio segments and their descriptions
281
+ """
282
+ stems = get_stems(stems_folder)
283
+ llm_response = make_groq_call(
284
+ stems,
285
+ f"{section_type} section",
286
+ p,
287
+ section_type=section_type,
288
+ bpm=bpm,
289
+ bars=bars,
290
+ )
291
+
292
+ # Load audio files
293
+ if not audio_stems:
294
+ print("No stems loaded.")
295
+ return {}
296
+
297
+ # Create each variant
298
+ variants = {}
299
+ for variant_key in llm_response:
300
+ if variant_key.startswith("variant"):
301
+ variant_config = llm_response[variant_key]
302
+ audio = create_section_from_json(variant_config, audio_stems)
303
+ description = variant_config.get(
304
+ "description", f"Variant {variant_key[-1]}"
305
+ )
306
+ variants[variant_key] = {
307
+ "audio": audio,
308
+ "description": description,
309
+ "config": variant_config,
310
+ }
311
+
312
+ progress(0.2, desc="Generating structure and effects for variants...")
313
+
314
+ return variants
315
+
316
+
317
+ def create_full_track(
318
+ sections_folder, audio_stems, selected_variants, crossfade_ms=500
319
+ ):
320
+ """
321
+ Create a full track from selected variants
322
+
323
+ Args:
324
+ sections_folder (dict): Dict mapping section names to their folder paths
325
+ selected_variants (dict): Dict mapping section names to their selected variant configs
326
+ crossfade_ms (int): Crossfade duration in milliseconds
327
+
328
+ Returns:
329
+ AudioSegment: The final track
330
+ """
331
+ final_track = None
332
+
333
+ # Define the order of sections
334
+ section_order = [
335
+ "intro",
336
+ "buildup",
337
+ "full_loop",
338
+ "breakdown",
339
+ "bridge",
340
+ "buildup2",
341
+ "drop2",
342
+ "breakdown2",
343
+ "outro",
344
+ ]
345
+
346
+ # Process each section in order
347
+ for section_name in section_order:
348
+ if section_name not in selected_variants:
349
+ continue
350
+
351
+ # Get the selected variant config
352
+ variant_config = selected_variants[section_name]
353
+
354
+ # Create audio for this section
355
+ section_audio = create_section_from_json(variant_config, audio_stems)
356
+
357
+ # Add to final track
358
+ if final_track is None:
359
+ final_track = section_audio
360
+ else:
361
+ final_track = final_track.append(section_audio, crossfade=crossfade_ms)
362
+
363
+ return final_track
364
+
365
+
366
+ def create_intro(llm_answer, stems):
367
+ """Create intro section from LLM answer"""
368
+ return create_section_from_json(llm_answer.get("create_intro", {}), stems)
369
+
370
+
371
+ def create_variation1(llm_answer, stems):
372
+ """Create variation1 section from LLM answer"""
373
+ return create_section_from_json(llm_answer.get("create_variation1", {}), stems)
374
+
375
+
376
+ def create_full_loop(llm_answer, stems):
377
+ """Create full loop section from LLM answer"""
378
+ return create_section_from_json(llm_answer.get("create_full_loop", {}), stems)
379
+
380
+
381
+ def create_variation2(llm_answer, stems):
382
+ """Create variation2 section from LLM answer"""
383
+ return create_section_from_json(llm_answer.get("create_variation2", {}), stems)
384
+
385
+
386
+ def create_variation3(llm_answer, stems):
387
+ """Create variation3 section from LLM answer"""
388
+ return create_section_from_json(llm_answer.get("create_variation3", {}), stems)
389
+
390
+
391
+ def create_outro(llm_answer, stems):
392
+ """Create outro section from LLM answer"""
393
+ return create_section_from_json(llm_answer.get("create_outro", {}), stems)
394
+
395
+
396
+ def calculate_duration(bpm, bars):
397
+ """Calculate duration in seconds for a given BPM and number of bars"""
398
+ # Assuming 4/4 time signature (4 beats per bar)
399
+ beats_per_bar = 4
400
+ duration_seconds = (bars * beats_per_bar * 60) / bpm
401
+ return duration_seconds
402
+
403
+
404
+ def get_formatted_duration(seconds):
405
+ """Format duration in seconds to MM:SS format"""
406
+ minutes = int(seconds // 60)
407
+ seconds = int(seconds % 60)
408
+ return f"{minutes}:{seconds:02d}"
409
+
410
+
411
+ def export_section_variants(variants, output_folder, section_name):
412
+ """Export section variants to audio files"""
413
+ if not os.path.exists(output_folder):
414
+ os.makedirs(output_folder)
415
+
416
+ file_paths = {}
417
+ for variant_key, variant_data in variants.items():
418
+ output_path = os.path.join(output_folder, f"{section_name}_{variant_key}.wav")
419
+ variant_data["audio"].export(output_path, format="wav")
420
+ file_paths[variant_key] = output_path
421
+
422
+ return file_paths
423
+
424
+
425
+ def edm_arrangement_tab():
426
+ with gr.Tab("🎢 EDM Arranger"):
427
+ gr.Markdown("# 🌚 Interactive EDM Arrangement Tool")
428
+
429
+ out_plot = gr.Plot(label="Arrangement Diagram")
430
+
431
+ with gr.Row():
432
+ variation = gr.Radio(
433
+ choices=list(arrangements.keys()),
434
+ value="High Energy Flow",
435
+ label="Choose Arrangement Variation",
436
+ )
437
+ variation.change(fn=load_variation, inputs=variation, outputs=out_plot)
438
+ out_plot.value = editable_diagram(arrangement)
439
+
440
+ with gr.Accordion("🎻 Edit Section Parameters", open=False):
441
+ for i, (bar, tempo, name, length, curve) in enumerate(arrangement):
442
+ with gr.Row():
443
+ gr.Markdown(f"**{name}**")
444
+ bar_slider = gr.Slider(
445
+ minimum=0, maximum=300, value=bar, label="Start Bar"
446
+ )
447
+ tempo_slider = gr.Slider(
448
+ minimum=20, maximum=100, value=tempo, label="Volume"
449
+ )
450
+ length_slider = gr.Slider(
451
+ minimum=1, maximum=64, value=length, label="Length"
452
+ )
453
+ curve_selector = gr.Radio(
454
+ choices=["Flat", "Linear", "-ve Linear"],
455
+ value=curve,
456
+ label="Curve Type",
457
+ )
458
+ update_btn = gr.Button("Update")
459
+ update_btn.click(
460
+ fn=update_section,
461
+ inputs=[
462
+ gr.Number(value=i, visible=False),
463
+ bar_slider,
464
+ tempo_slider,
465
+ length_slider,
466
+ curve_selector,
467
+ ],
468
+ outputs=[out_plot],
469
+ )
470
+
471
+ with gr.Accordion("βž• Insert New Section", open=False):
472
+ new_index = gr.Number(value=0, label="Insert At Index")
473
+ new_name = gr.Textbox(label="Section Name", value="New Section")
474
+ new_bar = gr.Slider(minimum=0, maximum=300, value=0, label="Start Bar")
475
+ new_tempo = gr.Slider(minimum=20, maximum=100, value=50, label="Volume")
476
+ new_length = gr.Slider(minimum=1, maximum=64, value=8, label="Length")
477
+ new_curve = gr.Radio(
478
+ choices=["Flat", "Linear", "-ve Linear"],
479
+ value="Flat",
480
+ label="Curve Type",
481
+ )
482
+ insert_btn = gr.Button("Insert Section")
483
+ insert_btn.click(
484
+ fn=insert_section,
485
+ inputs=[new_index, new_name, new_bar, new_tempo, new_length, new_curve],
486
+ outputs=[out_plot],
487
+ )
488
+
489
+ gr.Markdown("## βœ… Finalise Your Arrangement")
490
+ final_btn = gr.Button("Finalise and Export JSON")
491
+ final_output = gr.Textbox(label="Final Arrangement JSON", lines=15)
492
+ final_btn.click(fn=finalise, outputs=final_output)