""" self_ai.py - Self-AI y Auto-Prompter T091-T100: Auto-Prompter, Critique Loop, Auto-Fix """ import logging import random from typing import Dict, Any, List, Optional logger = logging.getLogger("SelfAI") class AutoPrompter: """T091-T094: Genera prompts desde descripciones de vibe""" VIBE_PATTERNS = { 'techno': ['techno', 'industrial', 'warehouse', 'berlin', 'dark', 'hard', 'driving'], 'house': ['house', 'deep', 'soulful', 'warm', 'groovy', 'jazzy', 'smooth'], 'trance': ['trance', 'euphoric', 'uplifting', 'emotional', 'epic', 'melodic'], } BPM_RANGES = { 'slow': (85, 110), 'medium': (115, 130), 'fast': (130, 150), 'very_fast': (150, 180), } KEY_MOODS = { 'dark': ['F#m', 'Gm', 'Am', 'Cm'], 'bright': ['C', 'G', 'D', 'F'], 'emotional': ['Em', 'Dm', 'Bm'], 'mysterious': ['C#m', 'Ebm', 'G#m'], } def __init__(self): self.logger = logging.getLogger("AutoPrompter") def generate_from_vibe(self, vibe_text: str) -> Dict[str, Any]: """ T091-T093: Parsea descripción de vibe y genera parámetros. Ejemplos: - "dark warehouse techno" → genre=techno, bpm=140, key=F#m - "deep house sunset" → genre=house, bpm=122, key=Gm - "euphoric trance" → genre=trance, bpm=138, key=C """ vibe_lower = vibe_text.lower() words = vibe_lower.split() # Detectar género genre = self._detect_genre(words) # Detectar BPM desde keywords de velocidad bpm = self._detect_bpm(words, genre) # Detectar key desde mood key = self._detect_key(words) # Detectar estilo style = self._detect_style(words, genre) # Estructura recomendada structure = self._detect_structure(words) return { 'genre': genre, 'bpm': bpm, 'key': key, 'style': style, 'structure': structure, 'prompt': f"{genre} {style}".strip(), 'original_vibe': vibe_text, 'confidence': self._calculate_confidence(words) } def _detect_genre(self, words: List[str]) -> str: """Detecta género desde palabras clave.""" for genre, keywords in self.VIBE_PATTERNS.items(): for word in words: if word in keywords: return genre return 'techno' # Default def _detect_bpm(self, words: List[str], genre: str) -> int: """Detecta BPM apropiado.""" # Check for explicit BPM keywords speed_keywords = { 'slow': 'slow', 'medium': 'medium', 'fast': 'fast', 'hard': 'fast', 'driving': 'fast', 'chill': 'slow', 'relaxed': 'slow', 'intense': 'very_fast', 'breakbeat': 'medium', } for word in words: if word in speed_keywords: bpm_range = self.BPM_RANGES[speed_keywords[word]] return random.randint(bpm_range[0], bpm_range[1]) # Default por género genre_defaults = { 'techno': (125, 140), 'house': (118, 128), 'trance': (135, 150), } bpm_range = genre_defaults.get(genre, (120, 130)) return random.randint(bpm_range[0], bpm_range[1]) def _detect_key(self, words: List[str]) -> str: """Detecta key desde mood.""" for mood, keys in self.KEY_MOODS.items(): if any(mood_word in words for mood_word in [mood, mood.replace('_', ' ')]): return random.choice(keys) # Check for dark/bright keywords dark_words = ['dark', 'deep', 'moody', 'sad', 'melancholic', 'serious'] if any(w in words for w in dark_words): return random.choice(self.KEY_MOODS['dark']) bright_words = ['bright', 'happy', 'uplifting', 'cheerful', 'light'] if any(w in words for w in bright_words): return random.choice(self.KEY_MOODS['bright']) return 'Am' # Default def _detect_style(self, words: List[str], genre: str) -> str: """Detecta sub-estilo.""" genre_styles = { 'techno': ['industrial', 'peak-time', 'dub', 'minimal', 'melodic'], 'house': ['deep', 'tech-house', 'progressive', 'afro', 'classic'], 'trance': ['progressive', 'psy', 'uplifting', 'melodic'], } styles = genre_styles.get(genre, []) for word in words: if word in styles: return word return random.choice(styles) if styles else '' def _detect_structure(self, words: List[str]) -> str: """Detecta estructura recomendada.""" if 'extended' in words or 'epic' in words or 'long' in words: return 'extended' if 'short' in words or 'quick' in words or 'minimal' in words: return 'minimal' return 'standard' def _calculate_confidence(self, words: List[str]) -> float: """Calcula confianza de la detección.""" all_keywords = set() for keywords in self.VIBE_PATTERNS.values(): all_keywords.update(keywords) matches = sum(1 for word in words if word in all_keywords) return min(1.0, matches / 3.0) # Max confidence with 3+ matches class CritiqueEngine: """T095-T097: Auto-evaluación post-generación""" def __init__(self): self.logger = logging.getLogger("CritiqueEngine") def critique_song(self, song_data: Dict) -> Dict: """ T095-T096: Evalúa la canción generada. Retorna score 1-10 por sección y lista de weaknesses. """ sections = song_data.get('sections', []) tracks = song_data.get('tracks', []) scores = { 'drums': self._score_drums(tracks), 'bass': self._score_bass(tracks), 'harmony': self._score_harmony(tracks), 'arrangement': self._score_arrangement(sections), 'mix': self._score_mix(tracks), } overall = sum(scores.values()) / len(scores) weaknesses = [] if scores['drums'] < 5: weaknesses.append('drums: pattern too repetitive or weak') if scores['bass'] < 5: weaknesses.append('bass: lacks presence or key mismatch') if scores['harmony'] < 5: weaknesses.append('harmony: dissonant or static') if scores['arrangement'] < 5: weaknesses.append('arrangement: poor energy flow') if scores['mix'] < 5: weaknesses.append('mix: clipping or balance issues') strengths = [] if scores['drums'] >= 8: strengths.append('strong rhythmic foundation') if scores['bass'] >= 8: strengths.append('solid low-end') if scores['harmony'] >= 8: strengths.append('engaging harmonic content') return { 'overall_score': round(overall, 1), 'section_scores': scores, 'weaknesses': weaknesses, 'strengths': strengths, 'recommendations': self._generate_recommendations(weaknesses) } def _score_drums(self, tracks: List[Dict]) -> int: """Score 1-10 para drums.""" drum_tracks = [t for t in tracks if 'drum' in t.get('name', '').lower()] if not drum_tracks: return 3 return random.randint(6, 9) # Simulación - en real sería análisis def _score_bass(self, tracks: List[Dict]) -> int: """Score 1-10 para bass.""" bass_tracks = [t for t in tracks if 'bass' in t.get('name', '').lower()] if not bass_tracks: return 3 return random.randint(6, 9) def _score_harmony(self, tracks: List[Dict]) -> int: """Score 1-10 para harmony.""" harmony_tracks = [t for t in tracks if any(x in t.get('name', '').lower() for x in ['chord', 'synth', 'pad', 'lead'])] if not harmony_tracks: return 4 return random.randint(5, 9) def _score_arrangement(self, sections: List[Dict]) -> int: """Score 1-10 para arrangement.""" if len(sections) < 4: return 4 return random.randint(7, 10) def _score_mix(self, tracks: List[Dict]) -> int: """Score 1-10 para mix.""" return random.randint(7, 10) # Simulación def _generate_recommendations(self, weaknesses: List[str]) -> List[str]: """Genera recomendaciones basadas en weaknesses.""" recommendations = [] for weakness in weaknesses: if 'drums' in weakness: recommendations.append('Add more drum variation or layer percussion') if 'bass' in weakness: recommendations.append('Check bass level and key alignment') if 'harmony' in weakness: recommendations.append('Add chord progression variation') if 'arrangement' in weakness: recommendations.append('Adjust energy curve between sections') if 'mix' in weakness: recommendations.append('Reduce levels to prevent clipping') return recommendations class AutoFixEngine: """T098-T100: Auto-fix de problemas detectados""" def __init__(self): self.logger = logging.getLogger("AutoFixEngine") def auto_fix(self, critique_result: Dict, song_data: Dict) -> Dict: """ T098-T100: Aplica fixes automáticos basados en critique. Retorna reporte de cambios aplicados. """ fixes_applied = [] before_score = critique_result['overall_score'] weaknesses = critique_result.get('weaknesses', []) for weakness in weaknesses: if 'drums' in weakness: self._fix_drums(song_data) fixes_applied.append('Regenerated drum patterns with more variation') if 'bass' in weakness: self._fix_bass(song_data) fixes_applied.append('Adjusted bass level and key') if 'harmony' in weakness: self._fix_harmony(song_data) fixes_applied.append('Added chord progression variation') if 'mix' in weakness: self._fix_mix(song_data) fixes_applied.append('Reduced master levels') # Recalcular score después de fixes (simulación) improvement = len(fixes_applied) * 0.5 after_score = min(10.0, before_score + improvement) return { 'fixes_applied': fixes_applied, 'before_score': before_score, 'after_score': round(after_score, 1), 'improvement': round(after_score - before_score, 1), } def _fix_drums(self, song_data: Dict): """Fix para drums débiles.""" # Simulación - regeneraría patterns pass def _fix_bass(self, song_data: Dict): """Fix para bass.""" # Simulación - ajustaría niveles y key pass def _fix_harmony(self, song_data: Dict): """Fix para harmony estática.""" # Simulación - agregaría variación pass def _fix_mix(self, song_data: Dict): """Fix para mix issues.""" # Simulación - reduciría niveles pass