Initial commit: AbletonMCP_AI v3.0 Senior Architecture
This commit is contained in:
373
AbletonMCP_AI/mcp_server/engines/dj_structure_engine.py
Normal file
373
AbletonMCP_AI/mcp_server/engines/dj_structure_engine.py
Normal file
@@ -0,0 +1,373 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user