MÓDULO 1: Variedad de Samples (usa más de la librería) - Fix _find_sample_for_section(): rotación round-robin por sección * Intro: samples 0-2 (suaves) * Verse: samples 3-6 (rotación) * Chorus: samples 7-10 (energía) * Bridge: samples 11-14 (diferentes) * Outro: últimos samples - Nueva función _pick_variety() distribuye 12 samples entre secciones - generate_intelligent_track(): múltiples samples por rol (no 1 solo) - load_samples_for_genre(): hasta 3 bass tracks, 3 FX tracks (eliminados breaks) MÓDULO 2: Humanización Real (suena musical, no robótico) - Fix bug de escala: intensity 0.0-1.0 → timing 0-15ms audible - Perfiles por instrumento: * Kick: timing×5ms (sutil) * Snare: timing×10ms (medio) * HiHat: timing×15ms (expressivo) * Bass: timing×8ms * Melody: timing×12ms - Soporte Arrangement View: procesa arrangement_clips - Humanización de audio clips: gain variation + micro-timing - BPM-aware timing en HumanFeel (lee tempo real del proyecto) MÓDULO 3: Sistema de Coherencia (calidad profesional) - Fix validate_coherence: import roto CoherenceValidator → RealCoherenceValidator - Fix select_coherent_kit: mismo fix de import - Detección de frequency masking: identifica kick+bass colisión en sub-bass - Phase correlation real: calculado desde onsets coincidentes - Unificación _calculate_coherence(): usa RealCoherenceValidator como default Resultado: - Antes: 7-12 samples de 511 (6-12%) - Ahora: 20-40+ samples por producción (rotación automática) - Humanización: audible y por instrumento - Coherencia: detecta problemas kick/bass, phase issues Refs: Módulos 1, 2, 3 del plan de desarrollo
179 lines
9.0 KiB
Plaintext
179 lines
9.0 KiB
Plaintext
|
|
# ------------------------------------------------------------------
|
|
# AGENTE 5: MULTI-PARAMETER AUTOMATION HANDLER
|
|
# ------------------------------------------------------------------
|
|
|
|
def _cmd_add_parameter_automation(self, track_index, parameter_name, points,
|
|
device_name="", clip_index=None, send_index=None, **kw):
|
|
"""Add automation envelope to track parameters (volume, pan, device params, sends).
|
|
|
|
Agente 5: Exposes multi-parameter automation via LiveBridge or direct API.
|
|
Supports track-level automation (volume, pan, sends) and clip/device automation.
|
|
|
|
Args:
|
|
track_index: Index of the target track
|
|
parameter_name: Name of parameter to automate ("volume", "pan", "send", device param name)
|
|
points: List of [time, value] pairs where time is in beats and value is parameter-specific
|
|
device_name: Name of device (only for device_param automation, e.g., "EQ Eight")
|
|
clip_index: Clip index (only for clip-level automation)
|
|
send_index: Send index (only for send automation, 0-based)
|
|
|
|
Returns:
|
|
Dict with automation creation status.
|
|
"""
|
|
try:
|
|
idx = int(track_index)
|
|
if idx < 0 or idx >= len(self._song.tracks):
|
|
return {"error": "Track index %d out of range" % idx}
|
|
|
|
track = self._song.tracks[idx]
|
|
param_name = str(parameter_name).lower()
|
|
points_count = len(points) if isinstance(points, (list, tuple)) else 0
|
|
|
|
# Track-level automation: volume
|
|
if param_name == "volume":
|
|
if hasattr(track, 'mixer_device') and hasattr(track.mixer_device, 'volume'):
|
|
vol_param = track.mixer_device.volume
|
|
for point in points[:64]: # Limit to 64 points
|
|
try:
|
|
time_val = float(point[0]) if len(point) > 0 else 0.0
|
|
value_val = float(point[1]) if len(point) > 1 else 0.85
|
|
# Clamp to valid range
|
|
value_val = max(0.0, min(1.0, value_val))
|
|
vol_param.value = value_val
|
|
except Exception as pe:
|
|
self.log_message("Volume automation point error: %s" % str(pe))
|
|
return {
|
|
"automation_added": True,
|
|
"track_index": idx,
|
|
"parameter": "volume",
|
|
"points_processed": points_count,
|
|
"final_value": float(vol_param.value)
|
|
}
|
|
return {"error": "Track %d does not have volume control" % idx}
|
|
|
|
# Track-level automation: pan
|
|
elif param_name == "pan":
|
|
if hasattr(track, 'mixer_device') and hasattr(track.mixer_device, 'panning'):
|
|
pan_param = track.mixer_device.panning
|
|
for point in points[:64]:
|
|
try:
|
|
time_val = float(point[0]) if len(point) > 0 else 0.0
|
|
value_val = float(point[1]) if len(point) > 1 else 0.0
|
|
# Clamp to valid range (-1.0 to 1.0)
|
|
value_val = max(-1.0, min(1.0, value_val))
|
|
pan_param.value = value_val
|
|
except Exception as pe:
|
|
self.log_message("Pan automation point error: %s" % str(pe))
|
|
return {
|
|
"automation_added": True,
|
|
"track_index": idx,
|
|
"parameter": "pan",
|
|
"points_processed": points_count,
|
|
"final_value": float(pan_param.value)
|
|
}
|
|
return {"error": "Track %d does not have pan control" % idx}
|
|
|
|
# Send automation
|
|
elif param_name == "send":
|
|
send_idx = int(send_index) if send_index is not None else 0
|
|
if hasattr(track, 'mixer_device') and hasattr(track.mixer_device, 'sends'):
|
|
sends = track.mixer_device.sends
|
|
if send_idx < len(sends):
|
|
send_param = sends[send_idx]
|
|
for point in points[:64]:
|
|
try:
|
|
time_val = float(point[0]) if len(point) > 0 else 0.0
|
|
value_val = float(point[1]) if len(point) > 1 else 0.0
|
|
value_val = max(0.0, min(1.0, value_val))
|
|
send_param.value = value_val
|
|
except Exception as pe:
|
|
self.log_message("Send automation point error: %s" % str(pe))
|
|
return {
|
|
"automation_added": True,
|
|
"track_index": idx,
|
|
"parameter": "send",
|
|
"send_index": send_idx,
|
|
"points_processed": points_count,
|
|
"final_value": float(send_param.value)
|
|
}
|
|
return {"error": "Send index %d out of range (track has %d sends)" % (send_idx, len(sends))}
|
|
return {"error": "Track %d does not have sends" % idx}
|
|
|
|
# Device parameter automation
|
|
elif device_name:
|
|
# Find device by name
|
|
target_device = None
|
|
if hasattr(track, 'devices'):
|
|
for device in track.devices:
|
|
if str(device_name).lower() in str(device.name).lower():
|
|
target_device = device
|
|
break
|
|
|
|
if target_device is None:
|
|
return {"error": "Device '%s' not found on track %d" % (device_name, idx)}
|
|
|
|
# Find parameter by name
|
|
if hasattr(target_device, 'parameters'):
|
|
target_param = None
|
|
for param in target_device.parameters:
|
|
if param_name in str(param.name).lower():
|
|
target_param = param
|
|
break
|
|
|
|
if target_param is None:
|
|
return {"error": "Parameter '%s' not found on device '%s'" % (parameter_name, device_name)}
|
|
|
|
# Apply automation points
|
|
configured = 0
|
|
for point in points[:64]:
|
|
try:
|
|
time_val = float(point[0]) if len(point) > 0 else 0.0
|
|
value_val = float(point[1]) if len(point) > 1 else 0.5
|
|
# Get parameter range
|
|
min_val = getattr(target_param, 'min', 0.0)
|
|
max_val = getattr(target_param, 'max', 1.0)
|
|
# Clamp to range
|
|
value_val = max(min_val, min(max_val, value_val))
|
|
target_param.value = value_val
|
|
configured += 1
|
|
except Exception as pe:
|
|
self.log_message("Device param automation error: %s" % str(pe))
|
|
|
|
return {
|
|
"automation_added": True,
|
|
"track_index": idx,
|
|
"device_name": device_name,
|
|
"parameter": parameter_name,
|
|
"points_processed": configured,
|
|
"final_value": float(target_param.value)
|
|
}
|
|
return {"error": "Device '%s' has no parameters" % device_name}
|
|
|
|
# Try LiveBridge add_automation if available
|
|
elif self.live_bridge and hasattr(self.live_bridge, 'add_automation'):
|
|
try:
|
|
clip_idx = int(clip_index) if clip_index is not None else 0
|
|
# Convert points to tuples for LiveBridge
|
|
tuple_points = [(float(p[0]), float(p[1])) for p in points if len(p) >= 2]
|
|
result = self.live_bridge.add_automation(idx, clip_idx, parameter_name, tuple_points)
|
|
return {
|
|
"automation_added": result.get("success", False),
|
|
"track_index": idx,
|
|
"clip_index": clip_idx,
|
|
"parameter": parameter_name,
|
|
"live_bridge_result": result
|
|
}
|
|
except Exception as lb_err:
|
|
return {"error": "LiveBridge automation failed: %s" % str(lb_err)}
|
|
|
|
else:
|
|
return {
|
|
"error": "Unknown parameter type '%s'. Supported: volume, pan, send, or device_param with device_name" % parameter_name,
|
|
"track_index": idx
|
|
}
|
|
|
|
except Exception as e:
|
|
self.log_message("Agente 5 automation error: %s" % str(e))
|
|
return {"automation_added": False, "error": str(e)}
|