🎉 Sprint 7 COMPLETADO - MIDI instruments funcionando, clear_project agregado, drum loop + harmony test exitoso

AVANCES CLAVE:
 B001 FIX: MIDI instruments cargan correctamente (Wavetable/Operator)
 API fix: app.view.selected_track → self._song.view.selected_track
 clear_project: Nuevo comando para limpiar Session + Arrangement View
 Drum loop + Harmony: 100bpm gata con progresión Am-F-C-G funcionando
 13 scenes production: Sistema completo operativo

Estado: MUY FELIZ, todo funciona perfectamente 🚀
This commit is contained in:
Administrator
2026-04-13 13:56:19 -03:00
parent 8e6d5cec9f
commit 3f3866f32e
26 changed files with 26593 additions and 328 deletions

179
add_fases_11_15.py Normal file
View File

@@ -0,0 +1,179 @@
#!/usr/bin/env python
"""Script to add Fases 11-15 advanced sample picker to __init__.py"""
import sys
def main():
filepath = r'C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\AbletonMCP_AI\__init__.py'
# Read the file
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Check if already exists
if '_pick_for_scene_advanced' in content:
print("ERROR: _pick_for_scene_advanced already exists!")
return 1
# The old function to find
old_function = ''' def _pick_for_scene(all_samples, scene_idx, total_scenes):
"""Distribute samples across scenes so each gets a different one."""
if not all_samples:
return None
if len(all_samples) <= total_scenes:
return all_samples[scene_idx % len(all_samples)]
step = len(all_samples) / total_scenes
idx = int(scene_idx * step) % len(all_samples)
return all_samples[idx]
# Sort drum loops by BPM proximity to tempo'''
# The new function to add
new_function = ''' def _pick_for_scene(all_samples, scene_idx, total_scenes):
"""Distribute samples across scenes so each gets a different one."""
if not all_samples:
return None
if len(all_samples) <= total_scenes:
return all_samples[scene_idx % len(all_samples)]
step = len(all_samples) / total_scenes
idx = int(scene_idx * step) % len(all_samples)
return all_samples[idx]
# ================================================================
# FASES 11-15: SISTEMA AVANZADO DE VARIACION MASIVA DE KICKS Y SNARES
# ================================================================
# Track samples used in previous scene to avoid repetition
_prev_scene_samples = {"kicks": [], "snares": []}
_scene_sample_usage = {"kicks": {}, "snares": {}} # Track usage count per sample
_all_kicks_used = [] # Track order of all kicks used
_all_snares_used = [] # Track order of all snares used
def _pick_for_scene_advanced(all_samples, scene_idx, total_scenes, energy, prev_samples, sample_type="kick"):
"""
Advanced sample selection with energy-based filtering and no-repetition policy.
Args:
all_samples: List of all available sample paths
scene_idx: Current scene index
total_scenes: Total number of scenes
energy: Energy level (0.0-1.0)
prev_samples: List of samples used in previous scene
sample_type: "kick" or "snare" for logging
Returns:
Selected sample path or None
"""
if not all_samples:
return None
# Energy-based keyword filtering
soft_keywords = ["soft", "light", "minimal", "gentle", "quiet", "smooth"]
hard_keywords = ["hard", "heavy", "punch", "kick", "strong", "aggressive", "tight", "solid"]
# Filter samples based on energy level
if energy < 0.3:
# Low energy: prefer soft/light samples
filtered = [s for s in all_samples if any(kw in s.lower() for kw in soft_keywords)]
selection_pool = filtered if filtered else all_samples
elif energy > 0.8:
# High energy: prefer hard/heavy/punch samples
filtered = [s for s in all_samples if any(kw in s.lower() for kw in hard_keywords)]
selection_pool = filtered if filtered else all_samples
else:
# Medium energy: use all samples
selection_pool = all_samples
# Remove samples used in previous scene (no repetition policy)
available = [s for s in selection_pool if s not in prev_samples]
# If not enough samples after filtering, fall back to all samples (excluding prev)
if len(available) < 1:
available = [s for s in all_samples if s not in prev_samples]
# If still no samples (all were used in prev), use full pool
if not available:
available = all_samples
# Select sample with least usage count for even rotation
min_usage = float('inf')
best_candidates = []
usage_dict = _scene_sample_usage.get(sample_type, {})
for sample in available:
usage_count = usage_dict.get(sample, 0)
if usage_count < min_usage:
min_usage = usage_count
best_candidates = [sample]
elif usage_count == min_usage:
best_candidates.append(sample)
# Pick first from best candidates (they have equal lowest usage)
selected = best_candidates[0] if best_candidates else available[0] if available else None
if selected:
# Update usage tracking
usage_dict[selected] = usage_dict.get(selected, 0) + 1
_scene_sample_usage[sample_type] = usage_dict
# Track global usage order
if sample_type == "kick" and selected not in _all_kicks_used:
_all_kicks_used.append(selected)
elif sample_type == "snare" and selected not in _all_snares_used:
_all_snares_used.append(selected)
return selected
def _get_velocity_for_energy(energy, drum_type="kick"):
"""
Get velocity range based on energy level and drum type.
Args:
energy: Energy level (0.0-1.0)
drum_type: "kick" or "snare"
Returns:
Tuple of (min_velocity, max_velocity)
"""
if energy < 0.4:
# Low energy: softer velocities
if drum_type == "kick":
return (70, 80)
else: # snare
return (65, 75)
elif energy <= 0.7:
# Medium energy
if drum_type == "kick":
return (85, 85) # Fixed at 85
else: # snare
return (80, 80) # Fixed at 80
else:
# High energy: loud velocities
if drum_type == "kick":
return (95, 110)
else: # snare
return (90, 100)
# Sort drum loops by BPM proximity to tempo'''
if old_function not in content:
print("ERROR: Could not find the old function!")
# Try to find it
idx = content.find('def _pick_for_scene')
if idx >= 0:
print(f"Found at position {idx}")
print("Context:", repr(content[idx:idx+300]))
return 1
# Replace
new_content = content.replace(old_function, new_function)
# Write back
with open(filepath, 'w', encoding='utf-8') as f:
f.write(new_content)
print("SUCCESS: Added _pick_for_scene_advanced and _get_velocity_for_energy")
print(f"File size changed from {len(content)} to {len(new_content)}")
return 0
if __name__ == '__main__':
sys.exit(main())