Files
musica-ia/docs/generador_als.md
renato97 2442673496 🎵 Initial commit: MusiaIA - AI Music Generator
 Features:
- ALS file generator (creates Ableton Live projects)
- ALS parser (reads and analyzes projects)
- AI clients (GLM4.6 + Minimax M2)
- Multiple music genres (House, Techno, Hip-Hop)
- Complete documentation

🤖 Ready to generate music with AI!
2025-12-01 19:26:24 +00:00

13 KiB

Generador ALS - Documentación Técnica

🎯 Overview

El generador ALS es el corazón del sistema. Su función es crear archivos .als válidos programáticamente parseando y generando XML compatible con Ableton Live.

📋 Estructura de Archivos ALS

Descompresión

archivo.als (gzip) → XML → Modificación → gzip → nuevo.als

Estructura XML (Ableton Live 12.x)

<?xml version="1.0" encoding="UTF-8"?>
<Ableton MajorVersion="5" MinorVersion="12.0_12203" SchemaChangeCount="3" Creator="Ableton Live 12.2" Revision="...">
    <LiveSet>
        <!-- Metadatos del proyecto -->
        <NextPointeeId Value="..."/>
        <OverwriteProtectionNumber Value="..."/>

        <!-- Tracks -->
        <Tracks>
            <!-- AudioTrack, MidiTrack, ReturnTrack, MasterTrack -->
        </Tracks>

        <!-- Clips -->
        <Scene>
            <Scene/>
        </Scene>

        <!-- Automation -->
        <AutomationEnvelopes/>

        <!-- Devices y efectos -->
        <DevicesList/>
    </LiveSet>
</Ableton>

🛠️ Componentes del Generador

1. ALS Parser (als_parser.py)

class ALSParser:
    """Parsea archivos ALS existentes"""

    def __init__(self):
        self.tree = None
        self.root = None

    def load_from_file(self, filepath: str):
        """Carga archivo ALS (descomprime + parse XML)"""
        with gzip.open(filepath, 'rt', encoding='utf-8') as f:
            tree = et.parse(f)
        return tree

    def parse_tracks(self):
        """Extrae información de tracks"""
        tracks = []
        for track in self.root.findall('.//Tracks/*'):
            track_info = {
                'id': track.get('Id'),
                'type': track.tag,
                'name': self._get_track_name(track),
                'devices': self._get_devices(track),
                'clips': self._get_clips(track)
            }
            tracks.append(track_info)
        return tracks

    def extract_samples_used(self):
        """Lista samples referenciados en el proyecto"""
        samples = []
        for clip in self.root.findall('.//AudioClip'):
            file_ref = clip.find('.//FileRef')
            if file_ref is not None:
                samples.append(file_ref.get('FilePath'))
        return samples

2. ALS Generator (als_generator.py)

class ALSGenerator:
    """Genera archivos ALS desde cero"""

    def __init__(self):
        self.builder = ALSBuilder()
        self.sample_manager = SampleManager()

    def create_project(self, project_config: dict):
        """
        Crea un proyecto ALS completo

        Args:
            project_config: {
                'name': str,
                'bpm': int,
                'key': str,
                'tracks': [
                    {
                        'type': 'AudioTrack' | 'MidiTrack',
                        'name': str,
                        'samples': [list of sample paths],
                        'effects': [list of effects],
                        'automation': {...}
                    }
                ],
                'scenes': [...]
            }
        """
        # 1. Crear estructura base
        root = self.builder.create_root()

        # 2. Configurar LiveSet
        liveset = self.builder.create_liveset()

        # 3. Crear tracks
        for track_config in project_config['tracks']:
            track = self._create_track(track_config)
            liveset.append(track)

        # 4. Crear scenes
        self._create_scenes(liveset, project_config.get('scenes', []))

        # 5. Agregar master track
        master = self._create_master_track()
        liveset.append(master)

        root.append(liveset)

        # 6. Serializar y comprimir
        return self._serialize_and_compress(root)

    def _create_track(self, config: dict):
        """Crea un track individual"""
        if config['type'] == 'AudioTrack':
            return self._create_audio_track(config)
        elif config['type'] == 'MidiTrack':
            return self._create_midi_track(config)

    def _create_audio_track(self, config: dict):
        """Crea un track de audio"""
        track = Element('AudioTrack')
        track.set('Id', str(self.builder.get_next_id()))

        # Nombre del track
        name = SubElement(track, 'Name')
        SubElement(name, 'EffectiveName', Value=config['name'])
        SubElement(name, 'UserName', Value=config['name'])

        # Color aleatorio
        SubElement(track, 'Color', Value=str(random.randint(0, 100)))

        # Dispositivos (efectos, etc.)
        devices = SubElement(track, 'DevicesListWrapper')
        for effect in config.get('effects', []):
            device = self._create_effect(effect)
            devices.append(device)

        # Clips (referencias a samples)
        clip_slots = SubElement(track, 'ClipSlotsListWrapper')
        for sample_path in config['samples']:
            clip_slot = self._create_clip_slot(sample_path)
            clip_slots.append(clip_slot)

        return track

3. ALS Builder (als_builder.py)

class ALSBuilder:
    """Construye elementos XML válidos para ALS"""

    def __init__(self):
        self.next_id = 1000

    def create_root(self):
        """Crea el elemento root <Ableton>"""
        root = Element('Ableton')
        root.set('MajorVersion', '5')
        root.set('MinorVersion', '12.0_12203')
        root.set('SchemaChangeCount', '3')
        root.set('Creator', 'Ableton Live 12.2')
        root.set('Revision', self._generate_revision())
        return root

    def create_liveset(self):
        """Crea el elemento <LiveSet>"""
        liveset = Element('LiveSet')
        SubElement(liveset, 'NextPointeeId', Value=str(self.next_id))
        SubElement(liveset, 'OverwriteProtectionNumber', Value='3074')

        # Tracks container
        SubElement(liveset, 'Tracks')

        # Scenes
        scenes = SubElement(liveset, 'Scenes')
        SubElement(scenes, 'Scene', Id=str(self._next_id()))

        return liveset

    def create_clip_slot(self, sample_path: str):
        """Crea un ClipSlot con referencia a sample"""
        clip_slot = Element('AudioClipSlot')

        # FileRef - referencia al archivo
        file_ref = SubElement(clip_slot, 'FileRef')
        file_ref.set('FilePath', sample_path)
        file_ref.set('RelativePath', 'true')

        return clip_slot

    def create_effect(self, effect_type: str):
        """Crea un dispositivo/efecto"""
        devices_map = {
            'reverb': ReverbDevice,
            'delay': DelayDevice,
            'eq': EQDevice,
            'compressor': CompressorDevice,
        }

        device_class = devices_map.get(effect_type, BasicDevice)
        return device_class().create_xml()

4. Sample Manager (sample_manager.py)

class SampleManager:
    """Gestiona la biblioteca de samples"""

    def __init__(self, source_dir: str):
        self.source_dir = source_dir
        self.db = SampleDatabase()

    def find_samples(self, criteria: dict):
        """
        Encuentra samples basados en criterios

        Args:
            criteria: {
                'type': 'kick' | 'snare' | 'bass' | etc,
                'bpm_range': [min, max],
                'key': 'C' | 'Am' | etc,
                'mood': 'dark' | 'bright' | etc,
                'count': int
            }
        """
        return self.db.search(criteria)

    def get_sample_path(self, sample_id: str):
        """Retorna la ruta absoluta de un sample"""
        sample = self.db.get(sample_id)
        return os.path.join(self.source_dir, sample.type, sample.filename)

🎵 Motor de Generación Musical

Musical Intelligence (musical_intelligence.py)

class MusicalIntelligence:
    """Analiza requests y genera estructuras musicales"""

    def __init__(self, ai_client):
        self.ai = ai_client

    def analyze_request(self, user_input: str) -> dict:
        """
        Analiza el input del usuario y extrae parámetros musicales

        Returns: {
            'style': 'house' | 'techno' | 'hip-hop' | etc,
            'bpm': int,
            'key': str,
            'mood': str,
            'instruments': [list],
            'structure': [verse, chorus, etc],
            'duration': int (beats)
        }
        """
        prompt = f"""
        Analiza este request musical y extrae parámetros estructurados:
        "{user_input}"

        Responde en formato JSON con:
        - style: género musical
        - bpm: tempo sugerido (80-140)
        - key: tonalidad
        - mood: estado de ánimo
        - instruments: lista de instrumentos
        - structure: estructura de la canción
        """
        response = self.ai.complete(prompt)
        return json.loads(response)

    def generate_track_layout(self, analysis: dict) -> list:
        """
        Genera la disposición de tracks basada en el análisis
        """
        tracks = []

        # Track de drums (siempre)
        tracks.append({
            'type': 'AudioTrack',
            'name': 'Drums',
            'samples': self._select_samples('drums', analysis),
            'effects': ['compressor', 'eq']
        })

        # Track de bass (según estilo)
        if analysis['style'] in ['house', 'techno', 'hip-hop']:
            tracks.append({
                'type': 'MidiTrack',
                'name': 'Bass',
                'midi_pattern': self._generate_bass_pattern(analysis),
                'effects': ['saturator', 'eq']
            })

        # Tracks adicionales según instrumentos
        for instrument in analysis.get('instruments', []):
            tracks.append({
                'type': 'AudioTrack' if instrument == 'vocals' else 'MidiTrack',
                'name': instrument.title(),
                'samples': self._select_samples(instrument, analysis),
            })

        return tracks

🔄 Pipeline Completo

def generate_als_project(user_message: str, user_id: str) -> str:
    """
    Pipeline completo de generación de proyecto ALS
    """
    # 1. Analizar request con IA
    ai_client = AIClient()  # GLM4.6 o Minimax M2
    musical_ai = MusicalIntelligence(ai_client)
    analysis = musical_ai.analyze_request(user_message)

    # 2. Generar layout de tracks
    track_layout = musical_ai.generate_track_layout(analysis)

    # 3. Configurar proyecto
    project_config = {
        'name': f"AI Project {datetime.now().strftime('%Y%m%d_%H%M%S')}",
        'bpm': analysis['bpm'],
        'key': analysis['key'],
        'tracks': track_layout
    }

    # 4. Generar archivo ALS
    generator = ALSGenerator()
    als_path = generator.create_project(project_config)

    # 5. Guardar en DB
    db.save_project(user_id, project_config, als_path)

    return als_path

Validación

def validate_als_file(filepath: str) -> bool:
    """Valida que un archivo ALS sea válido"""
    try:
        # Intentar descomprimir
        with gzip.open(filepath, 'rt') as f:
            tree = et.parse(f)

        # Validar estructura XML
        root = tree.getroot()
        if root.tag != 'Ableton':
            return False

        # Verificar elementos requeridos
        if not root.find('.//LiveSet'):
            return False

        return True
    except Exception as e:
        logger.error(f"Validation error: {e}")
        return False

📝 Ejemplo de Uso

# Crear un proyecto house básico
config = {
    'name': 'My House Track',
    'bpm': 124,
    'key': 'Am',
    'tracks': [
        {
            'type': 'AudioTrack',
            'name': 'Drums',
            'samples': ['kicks/kick_001.wav', 'snares/snare_001.wav'],
            'effects': ['compressor', 'eq']
        },
        {
            'type': 'MidiTrack',
            'name': 'Bass',
            'midi_pattern': [60, 62, 64, 65],
            'effects': ['saturator']
        }
    ]
}

generator = ALSGenerator()
als_file = generator.create_project(config)

# El archivo está listo para Ableton Live!
print(f"Generated: {als_file}")

🎯 Próximos Pasos

  1. Implementar parser completo - Mapear todos los elementos XML
  2. Crear templates - Plantillas para diferentes géneros
  3. Integrar IA musical - Análisis y generación más sofisticada
  4. Sistema de samples - Base de datos y gestión automática
  5. Validación robusta - Verificación de integridad de archivos

⚠️ Consideraciones Técnicas

  • XML Encoding: UTF-8 siempre
  • Gzip compression: nivel 9 (máxima compresión)
  • File paths: usar rutas relativas en FileRef
  • IDs: mantener secuencia única
  • Version compatibility: mayor versión 5 (Live 11+)
  • Memory: cargar samples lazily, no en memoria
  • Threading: generación asíncrona para múltiples requests