feat: Complete music project templates and generation system
🎵 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>
This commit is contained in:
271
als_gen/als_generator_fixed.py
Normal file
271
als_gen/als_generator_fixed.py
Normal file
@@ -0,0 +1,271 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user