PRANJAL KAR
commited on
Commit
Β·
545764f
1
Parent(s):
e6749f4
Implement initial project structure and setup
Browse files
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)
|