374 lines
13 KiB
Python
374 lines
13 KiB
Python
"""
|
|
DJ Structure Engine - Professional DJ Extended and Radio Edit song structures.
|
|
|
|
This engine generates professional song structures optimized for DJ mixing and radio play.
|
|
"""
|
|
|
|
from typing import Dict, List, Optional
|
|
|
|
|
|
class DJStructureEngine:
|
|
"""
|
|
Generates professional DJ Extended and Radio Edit song structures.
|
|
|
|
Creates song arrangements optimized for:
|
|
- DJ Extended: Long intros/outros for mixing, extended breaks
|
|
- Radio Edit: Compact structure, immediate hook, no long intros
|
|
"""
|
|
|
|
def __init__(self, bpm: int = 95):
|
|
"""
|
|
Initialize the DJ Structure Engine.
|
|
|
|
Args:
|
|
bpm: Beats per minute for the track (default 95 for reggaeton)
|
|
"""
|
|
self.bpm = bpm
|
|
self.beats_per_bar = 4
|
|
|
|
def generate_dj_extended_structure(self, duration_minutes: float = 6.5) -> Dict:
|
|
"""
|
|
Generate a professional DJ Extended structure (160 bars).
|
|
|
|
Optimized for DJ mixing with long intros/outros and extended mix section.
|
|
|
|
Args:
|
|
duration_minutes: Target duration in minutes (default 6.5)
|
|
|
|
Returns:
|
|
Dict with complete structure definition
|
|
"""
|
|
structure = {
|
|
"type": "dj_extended",
|
|
"bpm": self.bpm,
|
|
"target_duration_minutes": duration_minutes,
|
|
"total_bars": 160,
|
|
"estimated_duration": self._bars_to_minutes(160),
|
|
"sections": [
|
|
{
|
|
"name": "Intro",
|
|
"type": "intro",
|
|
"bars": 24,
|
|
"description": "Ambience build for DJ mixing",
|
|
"energy_level": 0.2,
|
|
"elements": ["ambience", "fx", "minimal_drums"]
|
|
},
|
|
{
|
|
"name": "Break 1",
|
|
"type": "break",
|
|
"bars": 16,
|
|
"description": "Minimal drums section",
|
|
"energy_level": 0.4,
|
|
"elements": ["drums", "bass", "minimal_synth"]
|
|
},
|
|
{
|
|
"name": "Build 1",
|
|
"type": "build",
|
|
"bars": 8,
|
|
"description": "Rising energy with riser",
|
|
"energy_level": 0.7,
|
|
"elements": ["drums", "bass", "riser", "sweep"]
|
|
},
|
|
{
|
|
"name": "Drop 1",
|
|
"type": "drop",
|
|
"bars": 16,
|
|
"description": "Full energy drop",
|
|
"energy_level": 1.0,
|
|
"elements": ["drums", "bass", "chords", "melody", "fx"]
|
|
},
|
|
{
|
|
"name": "Break 2",
|
|
"type": "break",
|
|
"bars": 16,
|
|
"description": "Melodic breakdown section",
|
|
"energy_level": 0.5,
|
|
"elements": ["minimal_drums", "bass", "chords", "melody"]
|
|
},
|
|
{
|
|
"name": "Build 2",
|
|
"type": "build",
|
|
"bars": 8,
|
|
"description": "Second riser build",
|
|
"energy_level": 0.75,
|
|
"elements": ["drums", "bass", "riser", "sweep", "snare_roll"]
|
|
},
|
|
{
|
|
"name": "Drop 2",
|
|
"type": "drop",
|
|
"bars": 16,
|
|
"description": "Variation drop",
|
|
"energy_level": 1.0,
|
|
"elements": ["drums", "bass", "chords", "melody_variation", "fx"]
|
|
},
|
|
{
|
|
"name": "Extended Mix",
|
|
"type": "extended",
|
|
"bars": 64,
|
|
"description": "DJ mixing section with variations",
|
|
"energy_level": 0.9,
|
|
"elements": ["drums", "bass", "chords", "melody", "vocal_chops", "fx"]
|
|
},
|
|
{
|
|
"name": "Outro",
|
|
"type": "outro",
|
|
"bars": 16,
|
|
"description": "Fade out for DJ mixing",
|
|
"energy_level": 0.3,
|
|
"elements": ["drums", "bass_fade", "ambience"]
|
|
}
|
|
]
|
|
}
|
|
|
|
return structure
|
|
|
|
def generate_radio_edit_structure(self, duration_minutes: float = 4.0) -> Dict:
|
|
"""
|
|
Generate a professional Radio Edit structure (96 bars).
|
|
|
|
Optimized for radio play with immediate hook and compact structure.
|
|
|
|
Args:
|
|
duration_minutes: Target duration in minutes (default 4.0)
|
|
|
|
Returns:
|
|
Dict with complete structure definition
|
|
"""
|
|
structure = {
|
|
"type": "radio_edit",
|
|
"bpm": self.bpm,
|
|
"target_duration_minutes": duration_minutes,
|
|
"total_bars": 96,
|
|
"estimated_duration": self._bars_to_minutes(96),
|
|
"sections": [
|
|
{
|
|
"name": "Intro",
|
|
"type": "intro",
|
|
"bars": 8,
|
|
"description": "Quick intro to hook",
|
|
"energy_level": 0.3,
|
|
"elements": ["hook_preview", "minimal_drums"]
|
|
},
|
|
{
|
|
"name": "Verse 1",
|
|
"type": "verse",
|
|
"bars": 16,
|
|
"description": "First verse with full groove",
|
|
"energy_level": 0.6,
|
|
"elements": ["drums", "bass", "chords", "verse_melody"]
|
|
},
|
|
{
|
|
"name": "Pre-Chorus",
|
|
"type": "pre_chorus",
|
|
"bars": 8,
|
|
"description": "Build to chorus",
|
|
"energy_level": 0.75,
|
|
"elements": ["drums", "bass", "building_chords", "riser"]
|
|
},
|
|
{
|
|
"name": "Chorus 1",
|
|
"type": "chorus",
|
|
"bars": 16,
|
|
"description": "Main hook/chorus",
|
|
"energy_level": 1.0,
|
|
"elements": ["drums", "bass", "chords", "hook_melody", "fx"]
|
|
},
|
|
{
|
|
"name": "Verse 2",
|
|
"type": "verse",
|
|
"bars": 16,
|
|
"description": "Second verse with variation",
|
|
"energy_level": 0.6,
|
|
"elements": ["drums", "bass", "chords", "verse_melody_variation"]
|
|
},
|
|
{
|
|
"name": "Pre-Chorus 2",
|
|
"type": "pre_chorus",
|
|
"bars": 8,
|
|
"description": "Build to final chorus",
|
|
"energy_level": 0.8,
|
|
"elements": ["drums", "bass", "building_chords", "riser", "sweep"]
|
|
},
|
|
{
|
|
"name": "Chorus 2",
|
|
"type": "chorus",
|
|
"bars": 16,
|
|
"description": "Final chorus with impact",
|
|
"energy_level": 1.0,
|
|
"elements": ["drums", "bass", "chords", "hook_melody", "impact", "fx"]
|
|
},
|
|
{
|
|
"name": "Outro",
|
|
"type": "outro",
|
|
"bars": 8,
|
|
"description": "Quick fade out",
|
|
"energy_level": 0.4,
|
|
"elements": ["drums", "bass_fade"]
|
|
}
|
|
]
|
|
}
|
|
|
|
return structure
|
|
|
|
def calculate_section_positions(self, structure: Dict) -> List[Dict]:
|
|
"""
|
|
Calculate bar positions for each section in the structure.
|
|
|
|
Args:
|
|
structure: Structure dict from generate_*_structure methods
|
|
|
|
Returns:
|
|
List of dicts with section positions
|
|
"""
|
|
positions = []
|
|
current_bar = 0
|
|
|
|
for section in structure["sections"]:
|
|
section_info = {
|
|
"name": section["name"],
|
|
"type": section["type"],
|
|
"start_bar": current_bar,
|
|
"end_bar": current_bar + section["bars"],
|
|
"duration_bars": section["bars"],
|
|
"start_time": self._bars_to_beats(current_bar),
|
|
"end_time": self._bars_to_beats(current_bar + section["bars"]),
|
|
"energy_level": section["energy_level"],
|
|
"elements": section["elements"]
|
|
}
|
|
positions.append(section_info)
|
|
current_bar += section["bars"]
|
|
|
|
return positions
|
|
|
|
def get_elements_for_section(self, section_type: str) -> List[str]:
|
|
"""
|
|
Get the recommended elements for a specific section type.
|
|
|
|
Args:
|
|
section_type: Type of section (intro, verse, chorus, build, drop, etc.)
|
|
|
|
Returns:
|
|
List of element names that should be active
|
|
"""
|
|
element_map = {
|
|
"intro": ["ambience", "fx", "minimal_drums", "fade_in"],
|
|
"verse": ["drums", "bass", "chords", "verse_melody"],
|
|
"pre_chorus": ["drums", "bass", "building_chords", "riser"],
|
|
"chorus": ["drums", "bass", "chords", "hook_melody", "fx", "impact"],
|
|
"build": ["drums", "bass", "riser", "sweep", "snare_roll"],
|
|
"drop": ["drums", "bass", "chords", "melody", "fx", "full_energy"],
|
|
"break": ["minimal_drums", "bass", "chords", "melody"],
|
|
"extended": ["drums", "bass", "chords", "melody", "vocal_chops", "fx"],
|
|
"outro": ["drums", "bass_fade", "ambience", "fade_out"]
|
|
}
|
|
|
|
return element_map.get(section_type, ["drums", "bass"])
|
|
|
|
def _bars_to_minutes(self, bars: int) -> float:
|
|
"""
|
|
Convert bars to minutes based on BPM.
|
|
|
|
Args:
|
|
bars: Number of bars
|
|
|
|
Returns:
|
|
Duration in minutes
|
|
"""
|
|
beats = bars * self.beats_per_bar
|
|
minutes = beats / self.bpm
|
|
return round(minutes, 2)
|
|
|
|
def _bars_to_beats(self, bars: int) -> float:
|
|
"""
|
|
Convert bars to beats.
|
|
|
|
Args:
|
|
bars: Number of bars
|
|
|
|
Returns:
|
|
Number of beats
|
|
"""
|
|
return bars * self.beats_per_bar
|
|
|
|
def generate_custom_structure(
|
|
self,
|
|
section_configs: List[Dict],
|
|
target_duration: Optional[float] = None
|
|
) -> Dict:
|
|
"""
|
|
Generate a custom structure from section configurations.
|
|
|
|
Args:
|
|
section_configs: List of dicts with 'name', 'type', 'bars', 'energy_level'
|
|
target_duration: Optional target duration in minutes
|
|
|
|
Returns:
|
|
Dict with complete custom structure
|
|
"""
|
|
total_bars = sum(cfg.get("bars", 8) for cfg in section_configs)
|
|
|
|
structure = {
|
|
"type": "custom",
|
|
"bpm": self.bpm,
|
|
"target_duration_minutes": target_duration,
|
|
"total_bars": total_bars,
|
|
"estimated_duration": self._bars_to_minutes(total_bars),
|
|
"sections": []
|
|
}
|
|
|
|
for cfg in section_configs:
|
|
section = {
|
|
"name": cfg.get("name", "Section"),
|
|
"type": cfg.get("type", "break"),
|
|
"bars": cfg.get("bars", 8),
|
|
"description": cfg.get("description", ""),
|
|
"energy_level": cfg.get("energy_level", 0.5),
|
|
"elements": self.get_elements_for_section(cfg.get("type", "break"))
|
|
}
|
|
structure["sections"].append(section)
|
|
|
|
return structure
|
|
|
|
def get_structure_summary(self, structure: Dict) -> str:
|
|
"""
|
|
Get a human-readable summary of a structure.
|
|
|
|
Args:
|
|
structure: Structure dict
|
|
|
|
Returns:
|
|
Formatted string summary
|
|
"""
|
|
lines = [
|
|
f"Structure: {structure['type']}",
|
|
f"BPM: {structure['bpm']}",
|
|
f"Total Bars: {structure['total_bars']}",
|
|
f"Estimated Duration: {structure['estimated_duration']} minutes",
|
|
"",
|
|
"Sections:",
|
|
"-" * 50
|
|
]
|
|
|
|
positions = self.calculate_section_positions(structure)
|
|
for pos in positions:
|
|
lines.append(
|
|
f" {pos['name']:20} | Bars {pos['start_bar']:3}-{pos['end_bar']:<3} | "
|
|
f"Energy: {pos['energy_level']:.1f}"
|
|
)
|
|
|
|
lines.append("-" * 50)
|
|
return "\n".join(lines)
|
|
|
|
|
|
# Convenience functions for quick access
|
|
def create_dj_extended_structure(bpm: int = 95, duration_minutes: float = 6.5) -> Dict:
|
|
"""Quick function to create DJ Extended structure."""
|
|
engine = DJStructureEngine(bpm)
|
|
return engine.generate_dj_extended_structure(duration_minutes)
|
|
|
|
|
|
def create_radio_edit_structure(bpm: int = 95, duration_minutes: float = 4.0) -> Dict:
|
|
"""Quick function to create Radio Edit structure."""
|
|
engine = DJStructureEngine(bpm)
|
|
return engine.generate_radio_edit_structure(duration_minutes)
|