🎵 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!
This commit is contained in:
renato97
2025-12-01 19:26:24 +00:00
commit 2442673496
35 changed files with 4037 additions and 0 deletions

547
docs/api_chatbot.md Normal file
View File

@@ -0,0 +1,547 @@
# API & Chatbot - Documentación
## 🤖 Integración con IA (GLM4.6 & Minimax M2)
### Proveedores de IA
```python
# ai_providers.py
class GLM46Provider:
"""Cliente para GLM4.6 API"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://open.bigmodel.cn/api/paas/v4"
def complete(self, prompt: str, **kwargs) -> str:
response = requests.post(
f"{self.base_url}/chat/completions",
headers={"Authorization": f"Bearer {self.api_key}"},
json={
"model": "glm-4-plus",
"messages": [{"role": "user", "content": prompt}],
**kwargs
}
)
return response.json()['choices'][0]['message']['content']
class MinimaxM2Provider:
"""Cliente para Minimax M2 API"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.minimax.chat/v1"
def complete(self, prompt: str, **kwargs) -> str:
# Implementar según documentación de Minimax
pass
class AIOrchestrator:
"""Orquestador que usa múltiples proveedores"""
def __init__(self):
self.providers = {
'glm46': GLM46Provider(os.getenv('GLM46_API_KEY')),
'minimax': MinimaxM2Provider(os.getenv('MINIMAX_API_KEY'))
}
async def chat(self, message: str, context: list) -> str:
# Determinar qué modelo usar
model = self._select_model(message)
# Obtener respuesta
provider = self.providers[model]
return await provider.complete(message, context=context)
def _select_model(self, message: str) -> str:
"""Selecciona el mejor modelo para la query"""
# Lógica para elegir entre GLM4.6 y Minimax M2
# Ejemplo: usar Minimax para conversación, GLM para análisis técnico
if 'generar' in message.lower() or 'crear' in message.lower():
return 'glm46' # Mejor para generación estructurada
return 'minimax' # Mejor para conversación
```
## 💬 Sistema de Chat
### WebSocket Handler (Real-time)
```python
# chat_websocket.py
from fastapi import WebSocket, WebSocketDisconnect
import json
class ChatManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket, user_id: str):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_message(self, message: str, websocket: WebSocket):
await websocket.send_text(json.dumps({
"type": "message",
"content": message,
"timestamp": datetime.now().isoformat()
}))
async def broadcast_progress(self, progress: dict):
"""Envía actualizaciones de progreso"""
for connection in self.active_connections:
await connection.send_text(json.dumps({
"type": "progress",
"data": progress
}))
@router.websocket("/chat/{user_id}")
async def chat_endpoint(websocket: WebSocket, user_id: str):
chat_manager = ChatManager()
await chat_manager.connect(websocket, user_id)
try:
while True:
# Recibir mensaje
data = await websocket.receive_text()
message_data = json.loads(data)
# Procesar mensaje
processor = ChatProcessor(user_id)
response = await processor.process_message(message_data['content'])
# Enviar respuesta
await chat_manager.send_message(response, websocket)
except WebSocketDisconnect:
chat_manager.disconnect(websocket)
```
### Procesador de Chat
```python
# chat_processor.py
class ChatProcessor:
"""Procesa mensajes y coordina generación"""
def __init__(self, user_id: str):
self.user_id = user_id
self.ai_orchestrator = AIOrchestrator()
self.project_generator = ProjectGenerator()
async def process_message(self, message: str) -> str:
# 1. Determinar intención
intent = await self._analyze_intent(message)
# 2. Responder según intención
if intent['type'] == 'generate_project':
return await self._handle_generation(message, intent)
elif intent['type'] == 'chat':
return await self._handle_chat(message)
elif intent['type'] == 'modify_project':
return await self._handle_modification(message, intent)
async def _analyze_intent(self, message: str) -> dict:
"""Analiza la intención del mensaje"""
prompt = f"""
Analiza este mensaje y determina la intención:
"{message}"
Clasifica como:
- generate_project: quiere crear un nuevo proyecto
- modify_project: quiere modificar un proyecto existente
- chat: conversación general
Responde en JSON: {{"type": "valor", "params": {{}}}}
"""
response = await self.ai_orchestrator.chat(message, [])
return json.loads(response)
async def _handle_generation(self, message: str, intent: dict):
"""Maneja solicitud de generación de proyecto"""
# Enviar mensaje inicial
await self._send_progress("🎵 Analizando tu solicitud...")
# 2. Generar proyecto
als_path = await self.project_generator.create_from_chat(
user_id=self.user_id,
requirements=intent['params']
)
# 3. Responder con éxito
return f"""
✅ ¡Proyecto generado con éxito!
🎹 Proyecto: {os.path.basename(als_path)}
📁 Ubicación: /projects/{self.user_id}/{als_path}
💡 Puedes abrir este archivo directamente en Ableton Live.
"""
```
## 🎼 Motor de Generación Musical
```python
# project_generator.py
class ProjectGenerator:
"""Genera proyectos ALS basado en chat"""
def __init__(self):
self.musical_ai = MusicalIntelligence()
self.sample_db = SampleDatabase()
self.als_generator = ALSGenerator()
async def create_from_chat(self, user_id: str, requirements: dict) -> str:
"""Crea proyecto desde chat input"""
# 1. Analizar musicalmente
await self._send_progress("🎼 Analizando estructura musical...")
analysis = await self.musical_ai.analyze_requirements(requirements)
# 2. Seleccionar samples
await self._send_progress("🥁 Seleccionando samples...")
selected_samples = await self._select_samples_for_project(analysis)
# 3. Generar layout
await self._send_progress("🎨 Diseñando layout...")
layout = self._generate_track_layout(analysis, selected_samples)
# 4. Crear archivo ALS
await self._send_progress("⚙️ Generando archivo ALS...")
project_config = {
'name': f"IA Project {datetime.now().strftime('%Y%m%d_%H%M%S')}",
'bpm': analysis['bpm'],
'key': analysis['key'],
'tracks': layout,
'metadata': {
'generated_by': 'MusiaIA',
'style': analysis['style'],
'mood': analysis['mood']
}
}
als_path = self.als_generator.create_project(project_config)
# 5. Guardar en historial
await self._save_to_history(user_id, requirements, als_path)
return als_path
async def _select_samples_for_project(self, analysis: dict) -> dict:
"""Selecciona samples automáticamente"""
selected = {}
for track_type in ['drums', 'bass', 'leads', 'pads', 'fx']:
if track_type in analysis.get('required_tracks', []):
samples = self.sample_db.search({
'type': track_type,
'style': analysis['style'],
'bpm_range': [analysis['bpm'] - 5, analysis['bpm'] + 5]
})
selected[track_type] = samples[:4] # Top 4 matches
return selected
```
## 📡 API REST Endpoints
```python
# api_endpoints.py
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import FileResponse
router = FastAPI()
@router.post("/chat/message")
async def send_message(request: ChatRequest):
"""Envía mensaje al chatbot"""
processor = ChatProcessor(request.user_id)
response = await processor.process_message(request.message)
return {"response": response}
@router.post("/projects/generate")
async def generate_project(request: GenerationRequest):
"""Genera nuevo proyecto ALS"""
generator = ProjectGenerator()
als_path = await generator.create_from_chat(
user_id=request.user_id,
requirements=request.requirements
)
return {
"status": "success",
"project_path": als_path,
"download_url": f"/projects/{request.user_id}/{os.path.basename(als_path)}"
}
@router.get("/projects/{user_id}/{project_name}")
async def download_project(user_id: str, project_name: str):
"""Descarga proyecto generado"""
project_path = f"/data/projects/{user_id}/{project_name}"
return FileResponse(project_path, filename=project_name)
@router.get("/projects/{user_id}")
async def list_projects(user_id: str):
"""Lista proyectos del usuario"""
projects = db.get_user_projects(user_id)
return {"projects": projects}
@router.get("/samples")
async def list_samples(filters: SampleFilters = None):
"""Lista samples disponibles"""
samples = sample_db.search(filters.dict() if filters else {})
return {"samples": samples}
@router.post("/samples/upload")
async def upload_sample(file: UploadFile = File(...)):
"""Sube nuevo sample"""
sample_id = sample_manager.upload(file)
return {"sample_id": sample_id, "status": "uploaded"}
@router.get("/chat/history/{user_id}")
async def get_chat_history(user_id: str, limit: int = 50):
"""Obtiene historial de chat"""
history = db.get_chat_history(user_id, limit=limit)
return {"history": history}
```
## 💾 Base de Datos (SQLAlchemy Models)
```python
# models.py
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, JSON
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True)
email = Column(String(100), unique=True)
api_provider = Column(String(20)) # glm46 or minimax
projects = relationship("Project", back_populates="user")
chat_history = relationship("ChatMessage", back_populates="user")
class Project(Base):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
name = Column(String(100))
als_path = Column(String(255))
style = Column(String(50))
bpm = Column(Integer)
key = Column(String(10))
config = Column(JSON) # Project configuration
user = relationship("User", back_populates="projects")
samples = relationship("ProjectSample", back_populates="project")
class ChatMessage(Base):
__tablename__ = 'chat_messages'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
message = Column(String(1000))
response = Column(String(1000))
timestamp = Column(DateTime, default=datetime.utcnow)
user = relationship("User", back_populates="chat_history")
class Sample(Base):
__tablename__ = 'samples'
id = Column(Integer, primary_key=True)
name = Column(String(100))
type = Column(String(50)) # kick, snare, bass, etc
file_path = Column(String(255))
bpm = Column(Integer)
key = Column(String(10))
tags = Column(JSON)
class ProjectSample(Base):
__tablename__ = 'project_samples'
id = Column(Integer, primary_key=True)
project_id = Column(Integer, ForeignKey('projects.id'))
sample_id = Column(Integer, ForeignKey('samples.id'))
track_name = Column(String(50))
project = relationship("Project", back_populates="samples")
sample = relationship("Sample")
```
## 🔐 Autenticación
```python
# auth.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
import jwt
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
user_id: int = payload.get("sub")
if user_id is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)
user = db.get_user(user_id)
return user
except jwt.PyJWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
@router.post("/auth/login")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = db.authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password"
)
access_token = create_access_token(data={"sub": user.id})
return {"access_token": access_token, "token_type": "bearer"}
```
## 📊 Request/Response Models
```python
# schemas.py
from pydantic import BaseModel
from typing import List, Optional, Dict, Any
class ChatRequest(BaseModel):
user_id: str
message: str
class GenerationRequest(BaseModel):
user_id: str
requirements: Dict[str, Any]
class ProjectResponse(BaseModel):
status: str
project_path: str
download_url: str
class ChatResponse(BaseModel):
response: str
timestamp: str
class SampleFilters(BaseModel):
type: Optional[str] = None
bpm_min: Optional[int] = None
bpm_max: Optional[int] = None
key: Optional[str] = None
style: Optional[str] = None
class ProjectSummary(BaseModel):
id: int
name: str
style: str
bpm: int
key: str
created_at: str
als_path: str
```
## 🚀 Inicio del Servidor
```python
# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
app = FastAPI(title="MusiaIA - AI Music Generator", version="1.0.0")
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Incluir routers
app.include_router(router, prefix="/api/v1")
# WebSocket
app.websocket_route("/ws/chat/{user_id}")
if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=True,
log_level="info"
)
```
## 🔄 Flujo de Ejemplo
```python
# Ejemplo de flujo completo
async def example_usage():
# 1. Usuario envía mensaje
user_message = "Genera un track de house a 124 BPM en La menor"
# 2. Chat API recibe mensaje
chat_request = ChatRequest(user_id="user123", message=user_message)
response = await send_message(chat_request)
# 3. IA analiza
analysis = await musical_ai.analyze_requirements(user_message)
# Returns: {'style': 'house', 'bpm': 124, 'key': 'Am', ...}
# 4. Genera proyecto
als_path = await project_generator.create_from_chat(
user_id="user123",
requirements=analysis
)
# 5. Retorna URL de descarga
download_url = f"/projects/user123/{os.path.basename(als_path)}"
return {
"response": "¡Proyecto generado!",
"download_url": download_url
}
```
## 📝 Logging y Monitoreo
```python
# logging_config.py
import logging
from pythonjsonlogger import jsonlogger
logHandler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter()
logHandler.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(logHandler)
logger.setLevel(logging.INFO)
# Uso
logger.info("User generated project", extra={
"user_id": user_id,
"project_type": "house",
"bpm": 124,
"generation_time": generation_time
})
```

177
docs/arquitectura.md Normal file
View File

@@ -0,0 +1,177 @@
# Arquitectura del Sistema - MusiaIA
## 📋 Descripción General
Sistema de generación de música por IA que crea proyectos compatibles con Ableton Live (.als) a partir de conversaciones con un chatbot.
## 🏗️ Componentes Principales
### 1. **Frontend - Dashboard Web**
- **Framework**: React/Next.js + TypeScript
- **Características**:
- Chat interfaz para interactuar con la IA
- Visualización de proyectos generados
- Gestión de samples y presets
- Preview de información de proyectos
- Sistema de descarga de archivos .als
### 2. **Backend - API Server**
- **Framework**: Python (FastAPI) o Node.js (Express)
- **Responsabilidades**:
- Procesamiento de requests del chat
- Integración con APIs de IA (GLM4.6, Minimax M2)
- Generación de archivos ALS
- Gestión de samples y proyectos
- Base de datos de usuarios y proyectos
### 3. **Generador ALS (Core)**
- **Lenguaje**: Python
- **Funcionalidad**:
- Parser XML para archivos ALS existentes
- Generador de XML programático
- Compresión gzip para crear archivos .als válidos
- Validación de estructura ALS
- Templates para diferentes estilos musicales
### 4. **Motor de IA Musical**
- **Componentes**:
- Analizador de requests del usuario
- Generador de estructuras musicales (bpm, clave, estilo)
- Selector de samples basado en criterios
- Configurador de tracks y efectos
- Orquestador de todos los elementos
### 5. **Gestión de Samples**
- **Sistema**: Base de datos + Almacenamiento de archivos
- **Características**:
- Upload y procesamiento de samples
- Tagging automático (kick, snare, bass, etc.)
- Análisis de BPM y tonalidad
- Búsqueda inteligente
- Presets de samples
### 6. **Base de Datos**
- **Tecnología**: PostgreSQL/MongoDB
- **Esquemas**:
- Usuarios y autenticación
- Proyectos generados
- Catálogo de samples
- Historial de chats
- Templates y presets
## 🔄 Flujo de Trabajo
```
1. Usuario → Chat Interface (Dashboard)
2. Chat Interface → Backend API
3. Backend → GLM4.6/Minimax M2 (análisis de request)
4. Backend → Motor IA Musical (generación estructura)
5. Motor IA → Selector de samples
6. Generador ALS → Crea XML → Comprime → Archivo .als
7. Backend → Dashboard → Usuario descarga archivo
```
## 📁 Estructura del Proyecto
```
/
├── als/ # Archivos ALS de ejemplo
├── source/ # Samples organizados por tipo
│ ├── kicks/
│ ├── snares/
│ ├── hats/
│ ├── bass/
│ ├── leads/
│ ├── pads/
│ ├── fx/
│ └── vox/
├── src/
│ ├── backend/
│ │ ├── api/ # Endpoints REST
│ │ ├── core/ # Motor de generación
│ │ ├── ai/ # Integración IA
│ │ ├── als/ # Parser/Generador ALS
│ │ ├── db/ # Models y schemas
│ │ └── utils/
│ └── dashboard/ # Frontend React
│ ├── components/
│ ├── pages/
│ ├── hooks/
│ └── services/
├── tests/
├── docs/
└── docker/ # Configuración de contenedores
```
## 🔧 Tecnologías Clave
### Backend
- **Python 3.11+**
- FastAPI (API framework)
- lxml (XML parsing/generation)
- pydantic (Data validation)
- SQLAlchemy (ORM)
- celery (async tasks)
### Frontend
- **React/Next.js 14+**
- **TypeScript**
- **TailwindCSS**
- **Socket.io** (real-time chat)
- **Axios** (API client)
### Infra
- **PostgreSQL** / MongoDB
- **Redis** (caching y queues)
- **Docker & Docker Compose**
- **Nginx** (reverse proxy)
## 🎯 Funcionalidades Clave
### Chatbot IA
- Conversación natural sobre música
- Interpretación de requests musicales
- Generación de prompts estructurados
- Historial de conversaciones
### Generación Musical
- Análisis de BPM y tonalidad
- Selección inteligente de samples
- Configuración de tracks automática
- Aplicación de efectos y procesamiento
### Gestión de Samples
- Auto-tagging basado en ML
- Búsqueda por características musicales
- Organización por categorías
- Sistema de favorites
### Compatibilidad ALS
- Estructura XML completa
- Compresión gzip adecuada
- Referencias a samples correctas
- Metadatos válidos
## 🔐 Seguridad
- Autenticación JWT
- Validación de inputs
- Sanitización de XML
- Rate limiting
- CORS configurado
## 📊 Métricas y Monitoreo
- Logs estructurados
- Métricas de uso
- Performance monitoring
- Error tracking (Sentry)
- Health checks
## 🚀 Despliegue
- Contenerización con Docker
- CI/CD con GitHub Actions
- Ambiente de staging y producción
- Backup automático de datos
- Auto-scaling para负载

442
docs/generador_als.md Normal file
View File

@@ -0,0 +1,442 @@
# 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
<?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`)
```python
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`)
```python
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`)
```python
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`)
```python
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`)
```python
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
```python
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
```python
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
```python
# 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