🎵 Major Additions: 📁 2000s Pop Project Templates: - Chords & melody patterns - Drum patterns and rhythms - Synth bass configurations - Effects and mixing guides - Complete project structure documentation 🧬 ALS Generation System: - Fixed ALS generator with enhanced capabilities - Setup scripts for easy deployment - Comprehensive README and documentation - Quick start guide for users - Utility commands reference 🎼 Musical Projects: - Salsa project (Hector Lavoe inspired) with full documentation - 2000s Pop project with complete production guide 🔧 Utility Scripts: - generate_salsa_project.py: Salsa-specific generator - generate_versioned_als.py: Versioned project generation - register_project.py: Project registration system This significantly expands MusiaIA's capabilities with pre-built project templates and production-ready examples for multiple genres! Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
272 lines
9.3 KiB
Python
272 lines
9.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generador CORREGIDO de archivos .als (Ableton Live Set)
|
|
Versión 100% compatible basada en ingeniería inversa del archivo original.
|
|
"""
|
|
|
|
import gzip
|
|
import xml.etree.ElementTree as ET
|
|
import random
|
|
import os
|
|
import shutil
|
|
from typing import Dict, List, Any
|
|
|
|
class ALSGeneratorFixed:
|
|
def __init__(self):
|
|
self.template_file = "jukeblocks - Pop.als"
|
|
self.output_file = None
|
|
self.tree = None
|
|
self.root = None
|
|
|
|
def load_template(self):
|
|
"""Cargar archivo original como plantilla"""
|
|
try:
|
|
with gzip.open(self.template_file, 'rb') as f:
|
|
xml_data = f.read()
|
|
|
|
self.tree = ET.ElementTree(ET.fromstring(xml_data))
|
|
self.root = self.tree.getroot()
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error al cargar plantilla: {e}")
|
|
return False
|
|
|
|
def clean_project(self):
|
|
"""Limpiar el proyecto manteniendo solo la estructura base"""
|
|
# Encontrar y limpiar clips existentes de forma segura
|
|
arranger_automations = self.root.findall('.//ArrangerAutomation')
|
|
for arranger in arranger_automations:
|
|
# Limpiar eventos
|
|
events = arranger.find('Events')
|
|
if events is not None:
|
|
events.clear()
|
|
|
|
# Limpiar notas existentes
|
|
for notes_container in self.root.findall('.//Notes'):
|
|
# Solo limpiar si tiene contenido
|
|
if len(notes_container) > 0:
|
|
notes_container.clear()
|
|
|
|
def rename_tracks(self, track_names: List[str]):
|
|
"""Renombrar tracks con los nombres especificados"""
|
|
tracks = self.root.findall('.//MidiTrack')
|
|
group_tracks = self.root.findall('.//GroupTrack')
|
|
|
|
# Renombrar GroupTrack principal
|
|
if group_tracks:
|
|
name_elem = group_tracks[0].find('Name')
|
|
if name_elem is not None:
|
|
user_name = name_elem.find('UserName')
|
|
effective_name = name_elem.find('EffectiveName')
|
|
if user_name is not None:
|
|
user_name.set('Value', track_names[0] if len(track_names) > 0 else 'Drums')
|
|
if effective_name is not None:
|
|
effective_name.set('Value', track_names[0] if len(track_names) > 0 else 'Drums')
|
|
|
|
# Renombrar MidiTracks
|
|
for i, track in enumerate(tracks):
|
|
name_elem = track.find('Name')
|
|
if name_elem is not None:
|
|
user_name = name_elem.find('UserName')
|
|
effective_name = name_elem.find('EffectiveName')
|
|
if user_name is not None:
|
|
user_name.set('Value', track_names[i] if i < len(track_names) else f'Track {i+1}')
|
|
if effective_name is not None:
|
|
effective_name.set('Value', track_names[i] if i < len(track_names) else f'Track {i+1}')
|
|
|
|
def create_simple_clip_pattern(self, track_index: int, pattern_type: str = "Pattern 1"):
|
|
"""Crear un clip simple con patrón básico"""
|
|
tracks = self.root.findall('.//MidiTrack')
|
|
|
|
if track_index >= len(tracks):
|
|
return
|
|
|
|
track = tracks[track_index]
|
|
|
|
# Buscar ArrangerAutomation
|
|
arranger = track.find('.//ArrangerAutomation')
|
|
if arranger is None:
|
|
return
|
|
|
|
# Crear MidiClip
|
|
clip = ET.Element('MidiClip', {
|
|
'Id': '0',
|
|
'Time': '0'
|
|
})
|
|
|
|
# LomId
|
|
ET.SubElement(clip, 'LomId', Value='0')
|
|
ET.SubElement(clip, 'LomIdView', Value='0')
|
|
|
|
# Tiempo
|
|
ET.SubElement(clip, 'CurrentStart', Value='0')
|
|
ET.SubElement(clip, 'CurrentEnd', Value='4')
|
|
|
|
# Loop
|
|
loop = ET.SubElement(clip, 'Loop')
|
|
ET.SubElement(loop, 'LoopStart', Value='0')
|
|
ET.SubElement(loop, 'LoopEnd', Value='4')
|
|
ET.SubElement(loop, 'StartRelative', Value='0')
|
|
ET.SubElement(loop, 'LoopOn', Value='false')
|
|
ET.SubElement(loop, 'OutMarker', Value='32')
|
|
ET.SubElement(loop, 'HiddenLoopStart', Value='0')
|
|
ET.SubElement(loop, 'HiddenLoopEnd', Value='32')
|
|
|
|
# Nombre
|
|
ET.SubElement(clip, 'Name', Value=pattern_type)
|
|
ET.SubElement(clip, 'Annotation', Value='')
|
|
ET.SubElement(clip, 'ColorIndex', Value='36')
|
|
|
|
# Configuración básica
|
|
ET.SubElement(clip, 'LaunchMode', Value='0')
|
|
ET.SubElement(clip, 'LaunchQuantisation', Value='0')
|
|
|
|
# TimeSignature
|
|
time_sig = ET.SubElement(clip, 'TimeSignature')
|
|
signatures = ET.SubElement(time_sig, 'TimeSignatures')
|
|
remote_sig = ET.SubElement(signatures, 'RemoteableTimeSignature', Id='0')
|
|
ET.SubElement(remote_sig, 'Numerator', Value='4')
|
|
ET.SubElement(remote_sig, 'Denominator', Value='4')
|
|
ET.SubElement(remote_sig, 'Time', Value='0')
|
|
|
|
# Envelopes
|
|
envelopes = ET.SubElement(clip, 'Envelopes')
|
|
ET.SubElement(envelopes, 'Envelopes')
|
|
|
|
# ScrollerTimePreserver
|
|
scroller = ET.SubElement(clip, 'ScrollerTimePreserver')
|
|
ET.SubElement(scroller, 'LeftTime', Value='0')
|
|
ET.SubElement(scroller, 'RightTime', Value='32')
|
|
|
|
# TimeSelection
|
|
time_sel = ET.SubElement(clip, 'TimeSelection')
|
|
ET.SubElement(time_sel, 'AnchorTime', Value='2')
|
|
ET.SubElement(time_sel, 'OtherTime', Value='2')
|
|
|
|
# Elementos vacíos
|
|
ET.SubElement(clip, 'Legato')
|
|
ET.SubElement(clip, 'Ram')
|
|
|
|
# GrooveSettings
|
|
groove = ET.SubElement(clip, 'GrooveSettings')
|
|
ET.SubElement(groove, 'GrooveId', Value='0')
|
|
|
|
# Configuración final
|
|
ET.SubElement(clip, 'Disabled', Value='false')
|
|
ET.SubElement(clip, 'VelocityAmount', Value='0')
|
|
ET.SubElement(clip, 'FollowTime', Value='4')
|
|
ET.SubElement(clip, 'FollowActionA', Value='0')
|
|
ET.SubElement(clip, 'FollowActionB', Value='0')
|
|
ET.SubElement(clip, 'FollowChanceA', Value='1')
|
|
ET.SubElement(clip, 'FollowChanceB', Value='0')
|
|
|
|
# Grid
|
|
grid = ET.SubElement(clip, 'Grid')
|
|
ET.SubElement(grid, 'FixedNumerator', Value='1')
|
|
ET.SubElement(grid, 'FixedDenominator', Value='16')
|
|
ET.SubElement(grid, 'GridIntervalPixel', Value='20')
|
|
ET.SubElement(grid, 'Ntoles', Value='2')
|
|
ET.SubElement(grid, 'SnapToGrid', Value='true')
|
|
ET.SubElement(grid, 'Fixed', Value='false')
|
|
|
|
ET.SubElement(clip, 'FreezeStart', Value='0')
|
|
ET.SubElement(clip, 'FreezeEnd', Value='0')
|
|
ET.SubElement(clip, 'IsWarped', Value='true')
|
|
|
|
# Notas
|
|
notes = ET.SubElement(clip, 'Notes')
|
|
key_tracks = ET.SubElement(notes, 'KeyTracks')
|
|
key_track = ET.SubElement(key_tracks, 'KeyTrack', Id='60')
|
|
ET.SubElement(key_track, 'MidiKey', Value='60')
|
|
|
|
notes_container = ET.SubElement(key_track, 'Notes')
|
|
|
|
# Añadir 8 notas por defecto
|
|
for i in range(8):
|
|
ET.SubElement(notes_container, 'MidiNoteEvent', {
|
|
'Time': str(i),
|
|
'Duration': '0.5',
|
|
'Velocity': '100',
|
|
'OffVelocity': '64',
|
|
'IsEnabled': 'true'
|
|
})
|
|
|
|
arranger.append(clip)
|
|
|
|
def generate_project(self, output_name: str, track_names: List[str] = None):
|
|
"""Generar proyecto completo"""
|
|
if not self.load_template():
|
|
return False
|
|
|
|
if track_names is None:
|
|
track_names = ['Drums', 'Kick', 'Snare', 'HiHat', 'Bass']
|
|
|
|
# Limpiar proyecto
|
|
self.clean_project()
|
|
|
|
# Renombrar tracks
|
|
self.rename_tracks(track_names)
|
|
|
|
# Crear clips simples en cada track
|
|
for i in range(min(4, len(self.root.findall('.//MidiTrack')))):
|
|
self.create_simple_clip_pattern(i, f"Pattern {i+1}")
|
|
|
|
# Guardar
|
|
self.save_als(output_name)
|
|
return True
|
|
|
|
def save_als(self, filename: str):
|
|
"""Guardar archivo .als"""
|
|
try:
|
|
self.output_file = filename
|
|
tree = ET.ElementTree(self.root)
|
|
|
|
# Escribir a archivo temporal
|
|
temp_file = filename + '.tmp'
|
|
tree.write(temp_file, encoding='utf-8', xml_declaration=True)
|
|
|
|
# Leer y comprimir
|
|
with open(temp_file, 'rb') as f:
|
|
xml_data = f.read()
|
|
|
|
with gzip.open(filename, 'wb') as f:
|
|
f.write(xml_data)
|
|
|
|
# Eliminar temporal
|
|
os.remove(temp_file)
|
|
|
|
print(f"Archivo .als generado: {filename}")
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error al guardar: {e}")
|
|
return False
|
|
|
|
def main():
|
|
"""Función principal"""
|
|
print("=" * 70)
|
|
print("Generador de Archivos .als - Versión Corregida")
|
|
print("=" * 70)
|
|
|
|
# Verificar que existe el archivo original
|
|
if not os.path.exists("jukeblocks - Pop.als"):
|
|
print("❌ Error: No se encuentra 'jukeblocks - Pop.als'")
|
|
print(" Este archivo es necesario como plantilla.")
|
|
return
|
|
|
|
# Generar proyecto
|
|
generator = ALSGeneratorFixed()
|
|
|
|
success = generator.generate_project(
|
|
output_name="ren.als",
|
|
track_names=['Drums', 'Kick', 'Snare', 'HiHat', 'Bass', 'Lead']
|
|
)
|
|
|
|
if success:
|
|
print("\n✅ Archivo ren.als generado exitosamente")
|
|
print(" Compatible con Ableton Live 12 Suite")
|
|
else:
|
|
print("\n❌ Error al generar el archivo")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|