Implement FASE 3, 4, 6 - 15 new MCP tools, 76/110 tasks complete
FASE 3 - Human Feel & Dynamics (10/11 tasks): - apply_clip_fades() - T041: Fade automation per section - write_volume_automation() - T042: Curves (linear, exp, s_curve, punch) - apply_sidechain_pump() - T045: Sidechain by intensity/style - inject_pattern_fills() - T048: Snare rolls, fills by density - humanize_set() - T050: Timing + velocity + groove automation FASE 4 - Key Compatibility & Tonal (9/12 tasks): - audio_key_compatibility.py: Full KEY_COMPATIBILITY_MATRIX - analyze_key_compatibility() - T053: Harmonic compatibility scoring - suggest_key_change() - T054: Circle of fifths modulation - validate_sample_key() - T055: Sample key validation - analyze_spectral_fit() - T057/T062: Spectral role matching FASE 6 - Mastering & QA (8/13 tasks): - calibrate_gain_staging() - T079: Auto gain by bus targets - run_mix_quality_check() - T085: LUFS, peaks, L/R balance - export_stem_mixdown() - T087: 24-bit/44.1kHz stem export New files: - audio_key_compatibility.py (T052) - bus_routing_fix.py (T101-T104) - validation_system_fix.py (T105-T106) Total: 76/110 tasks (69%), 71 MCP tools exposed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2306,6 +2306,106 @@ class StyleConfig:
|
||||
complexity: str # simple, moderate, complex
|
||||
|
||||
|
||||
|
||||
|
||||
class HumanFeelEngine:
|
||||
"""
|
||||
T040-T050: Engine de humanizacion y dinamica.
|
||||
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 # Convert to seconds
|
||||
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: Humanizacion de velocity (+-5% variacion)."""
|
||||
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)) # Clamp to MIDI range
|
||||
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: # Keep note with probability (1-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)
|
||||
# Apply swing to off-beat notes
|
||||
beat_pos = start % 1.0 # Position within beat
|
||||
if 0.4 < beat_pos < 0.6: # Off-beat
|
||||
delay = swing * 0.1 # Max 100ms delay
|
||||
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: Dinamica por seccion (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
|
||||
|
||||
class SongGenerator:
|
||||
"""Generador de configuraciones y patrones musicales"""
|
||||
|
||||
@@ -4936,7 +5036,8 @@ class SongGenerator:
|
||||
}
|
||||
|
||||
def generate_config(self, genre: str, style: str = "", bpm: float = 0,
|
||||
key: str = "", structure: str = "standard") -> Dict[str, Any]:
|
||||
key: str = "", structure: str = "standard",
|
||||
palette: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Genera una configuración completa de track
|
||||
|
||||
@@ -5013,6 +5114,7 @@ class SongGenerator:
|
||||
'buses': self._build_mix_bus_blueprint(profile, genre, style, reference_resolution),
|
||||
'returns': self._build_return_blueprint(profile, genre, style, reference_resolution),
|
||||
'master': self._build_master_blueprint(profile, genre, style, reference_resolution),
|
||||
'palette': palette or {},
|
||||
'tracks': [],
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user