""" Human Feel Engine for AbletonMCP-AI T040-T050: Humanización y dinámicas """ import random from typing import List, Dict, Any class HumanFeelEngine: """ T040-T050: Engine de humanización y dinámica. Aplica variaciones de timing, velocity y groove a patrones MIDI. """ def __init__(self, seed: int = 42): self.rng = random.Random(seed) self._groove_templates = { 'straight': {'swing': 0.0, 'humanize': 0.0}, 'shuffle': {'swing': 0.33, 'humanize': 0.02}, 'triplet': {'swing': 0.66, 'humanize': 0.03}, 'latin': {'swing': 0.25, 'humanize': 0.04}, } def apply_timing_variation(self, notes: List[Dict], amount_ms: float = 5.0) -> List[Dict]: """T040: Micro-offsets de timing (-5ms a +5ms).""" result = [] for note in notes: offset = self.rng.uniform(-amount_ms, amount_ms) / 1000.0 new_note = dict(note) new_note['start'] = note.get('start', 0) + offset result.append(new_note) return result def apply_velocity_humanize(self, notes: List[Dict], variance: float = 0.05) -> List[Dict]: """T041: Humanización de velocity (±5% variación).""" result = [] for note in notes: vel = note.get('velocity', 100) variation = self.rng.uniform(-variance, variance) new_vel = int(vel * (1 + variation)) new_vel = max(1, min(127, new_vel)) new_note = dict(note) new_note['velocity'] = new_vel result.append(new_note) return result def apply_note_skip_probability(self, notes: List[Dict], prob: float = 0.02) -> List[Dict]: """T042: Probabilidad de skip nota (2% ghost notes).""" result = [] for note in notes: if self.rng.random() > prob: result.append(note) return result def apply_groove(self, notes: List[Dict], style: str = 'shuffle', amount: float = 0.5) -> List[Dict]: """T044-T046: Aplica groove template.""" template = self._groove_templates.get(style, self._groove_templates['straight']) swing = template['swing'] * amount result = [] for note in notes: start = note.get('start', 0) beat_pos = start % 1.0 if 0.4 < beat_pos < 0.6: delay = swing * 0.1 new_note = dict(note) new_note['start'] = start + delay result.append(new_note) else: result.append(note) return result def apply_section_dynamics(self, notes: List[Dict], section: str) -> List[Dict]: """T047-T050: Dinámica por sección (intro 70%, drop 100%, etc).""" section_scales = { 'intro': 0.70, 'build': 0.85, 'drop': 1.00, 'break': 0.75, 'outro': 0.60, } scale = section_scales.get(section.lower(), 1.0) result = [] for note in notes: vel = note.get('velocity', 100) new_vel = int(vel * scale) new_vel = max(1, min(127, new_vel)) new_note = dict(note) new_note['velocity'] = new_vel result.append(new_note) return result def process_notes(self, notes: List[Dict], section: str = 'drop', humanize: bool = True, groove_style: str = 'shuffle') -> List[Dict]: """Procesamiento completo con todos los efectos.""" result = list(notes) if humanize: result = self.apply_timing_variation(result) result = self.apply_velocity_humanize(result) result = self.apply_note_skip_probability(result) result = self.apply_groove(result, groove_style) result = self.apply_section_dynamics(result, section) return result