Skip to main content
Glama
README.md
# sinfo-mcp

Servidor MCP que permite a un agente de IA **componer música de verdad**: planear la forma, escribir las voces, verificar el resultado y exportarlo.

A diferencia de los servidores MCP de MIDI existentes, que operan sobre fragmentos sueltos y a ciegas, aquí la partitura **vive en el servidor** entre llamadas y el agente puede **releer y verificar** lo que escribió. Eso es lo que hace posible pasar de ocho compases a una sinfonía.

## Instalación

```bash
npm install
npm run build
```

Registro en Claude Code:

```bash
claude mcp add sinfo -- node /ruta/a/sinfo-mcp/packages/mcp/dist/index.js
```

Los archivos exportados van a `./sinfo-out/<scoreId>/`. Se cambia con la variable `SINFO_OUT_DIR`.

### La skill

El servidor expone treinta herramientas, pero el flujo correcto —planear la forma antes de escribir, verificar después de cada sección, apuntar las semillas para poder iterar— no se deduce de los esquemas. La skill se lo enseña al agente:

```bash
npx skills add JOSETRA44/sinfo-mcp@sinfo-mcp
```

No se instala sola al hacer `npm install`, a propósito: que instalar una dependencia cambie el comportamiento de tu agente debe ser una decisión tuya. El `postinstall` solo te recuerda el comando.

### Audio

Para que el WAV suene de verdad hace falta un SoundFont General MIDI. Sin él se usa un banco de un solo sonido:

```bash
SINFO_SOUNDFONT=/ruta/a/GeneralUser.sf2
```

## Flujo típico

```
score_create          abre la obra y devuelve un scoreId
ensemble_add          monta un conjunto completo de una llamada
plan_form             reparte el movimiento en secciones con su plan tonal
motif_create          guarda una célula temática
motif_develop         inversión, retrogradación, aumentación, secuencia…
melody_generate       melodía sobre una progresión, con contorno y semilla
counterpoint_add      una voz contra otra, por búsqueda con retroceso
orchestrate           reparte el material entre toda la orquesta
harmony_progression   convierte I-vi-ii-V7-I en acordes reales
part_write            escribe la música en notación de texto
analyze_harmony       qué función cumple lo escrito, y dónde hay cadencias
check_voice_leading   quintas y octavas paralelas, cruces, saltos
check_ranges          nadie toca notas imposibles
export                .mid  .musicxml  .svg  .wav  .json
```

La partitura no se reenvía nunca: se referencia por `scoreId`. `score_describe` da el resumen y `part_read` devuelve fragmentos acotados por compases.

## Notación

### SinfoScript — instrumentos afinados

```
mf c4/q e4/q g4/h | a4/e. g4/s f4/q+stacc r/q
```

| | |
|---|---|
| `c4/q` | altura + `/` + figura; `c4` es el do central |
| `w h q e s t x` | redonda, blanca, negra, corchea, semicorchea, fusa, semifusa |
| `q.` `q..` | puntillo y doble puntillo |
| `e3` `s5` | tresillo de corchea, quintillo de semicorchea |
| `C#4` `Bb3` `Ebb2` | alteraciones (`#` sostenido, `b` bemol, repetibles) |
| `r/h` | silencio |
| `[c4,e4,g4]/h` | acorde |
| `mf` | dinámica suelta; rige hasta la siguiente |
| `c4/q+stacc+accent` | articulaciones |
| `c4/q~ c4/h` | ligadura de unión |
| `\|` | barra de compás — **se valida** que los tiempos cuadren |
| `#` tras espacio | comentario |

### Rejilla — percusión y ritmos programados

```
kick   x...x...x...x...
snare  ....X.......X...
hihat  x.x.x.x.x.x.x.x.
```

`x` golpe, `X` acentuado, `o` suave, `.` silencio. Las filas cortas se repiten en bucle contra las largas.

## Arquitectura

Hexagonal, con la dirección de dependencias **verificada mecánicamente** en cada build (`npm run arch`).

```
mcp  →  render  →  engine  →  core
 └────────┴──────────┘
```

| Paquete | Responsabilidad | Dependencias |
|---|---|---|
| `@sinfo/core` | Dominio: altura, duración, evento, voz, parte, movimiento, partitura, notación, división en compases | **ninguna** |
| `@sinfo/theory` | Escalas, acordes, números romanos, cadencias, conducción de voces | core |
| `@sinfo/generate` | Motivos, melodía por restricciones, contrapunto, orquestación, PRNG determinista | core, theory |
| `@sinfo/engine` | Casos de uso, sesiones y puertos de salida | core, theory, generate |
| `@sinfo/render` | Adaptadores de formato: MIDI, MusicXML, SVG y audio WAV | core, engine |
| `sinfo-mcp` | Herramientas MCP y raíz de composición | todos |

`@sinfo/theory` tampoco tiene dependencias externas. El plan preveía apoyarse en `tonal`, pero esa librería trabaja con cadenas de texto (`"C#4"`, `"Cmaj7"`) y aquí todo son objetos que conservan la ortografía: cada conversión de ida y vuelta es un sitio donde Do♯ puede volver como Re♭. Un acorde es una tónica más un patrón de intervalos — sale más corto construirlo que traducirlo.

Tres decisiones que sostienen el resto:

**Tiempo racional exacto.** `Duration` es una fracción, no un flotante. Tres tresillos de corchea suman exactamente una negra; en coma flotante no. En 200 compases de tresillos la última nota cae en su tick exacto, sin un solo pulso de desfase acumulado.

**Alturas con ortografía.** `Pitch` guarda letra, alteración y octava, no un número MIDI. Do♯ y Re♭ suenan igual pero no son la misma nota: transponer Fa♯ mayor por quinta justa da Do♯ mayor, no Re♭. El número MIDI es una proyección con pérdida.

**La partitura vive en el servidor.** Un agente no puede mandar una sinfonía como argumento. `score_create` devuelve un identificador y las demás herramientas mutan esa partitura, devolviendo resúmenes compactos.

**Las voces no guardan compases.** El dominio almacena cada voz como un flujo continuo de eventos; las barras se *derivan* al exportar. Guardarlas dentro obligaría a partir notas y crear ligaduras en cada inserción, y a rehacerlo todo si cambia un compás a mitad de obra. `splitIntoMeasures` hace ese reparto una sola vez, cortando en las barras y encadenando las ligaduras, y lo comparten todos los exportadores de notación.

## Verificación

```bash
npm run verify     # typecheck + tests + reglas de arquitectura
```

Las reglas de `.dependency-cruiser.cjs` **fallan el build** si alguien invierte una dependencia o si `@sinfo/core` gana una dependencia externa. Están probadas inyectando violaciones deliberadas: una regla que nunca dispara no protege de nada.

## Armonía

Números romanos completos: `I ii V7 vii°7 viiø7 III+ bVII #iv`, inversiones por cifrado de bajo (`I6 I64 V65 V43 V42`) y dominantes secundarias (`V/V`, `V7/IV`).

El modo menor recibe el trato que exige. Se construye con la escala armónica, así que `V` sale mayor y `vii°` disminuido — sin eso no hay cadencia auténtica. Pero el diatonismo se mide contra la colección completa del modo (natural **más** la sensible), porque el modo menor no tiene siete notas sino ocho: medirlo con una sola escala marcaba el relativo mayor como acorde prestado. Y el séptimo grado distingue `VII` (subtónica, Sol mayor en la menor) de `vii°` (sensible, Sol♯ disminuido), que son acordes distintos con funciones distintas.

`check_voice_leading` separa **errores** de **avisos**: las quintas y octavas paralelas funden dos voces en una y son error; los cruces, solapamientos, quintas directas, espaciados anchos y saltos grandes son avisos. Cada uno dice en qué compás está, entre qué voces y por qué importa.

## Generación

Toda la aleatoriedad pasa por un PRNG **determinista**: la misma semilla con los mismos parámetros da exactamente la misma música, en cualquier máquina. Es lo que permite al agente iterar — «esa melodía me gustaba, dame otra vez esa y cámbiame solo el contorno».

Cada tipo de decisión consume un **sub-flujo propio** (`Random.fork('ritmo')`). Sin eso, tocar el algoritmo de las alturas desplazaría todos los números siguientes y el ritmo cambiaría también, aunque no se hubiera tocado.

La melodía se construye con **restricciones puntuables**, no con una función llena de condicionales: rango, nota del acorde en tiempo fuerte, grado conjunto, resolución de saltos, contorno, cierre estable. Las puntuaciones se multiplican, así que un solo cero veta al candidato — el rango del instrumento no se negocia con el gusto por el grado conjunto. Añadir un criterio es escribir una función y sumarla a una lista.

El contrapunto usa **búsqueda con retroceso**, no elección nota a nota. Las reglas se condicionan entre sí: una elección correcta en el compás 5 puede dejar el 6 sin ninguna salida legal, y un algoritmo voraz se atasca ahí. Si no existe solución estricta cede reglas de estilo por orden — nunca las disonancias — y dice cuáles cedió.

Lo generado se somete al **mismo analizador** que critica lo escrito a mano: el test comprueba que el contrapunto pasa `check_voice_leading` sin una sola paralela.

## Escala sinfónica

El catálogo cubre **47 instrumentos** con rango, tesitura, transposición, tamaño de sección y peso dinámico. Vive como datos en `catalog.ts`: añadir un instrumento es añadir una entrada, y una altura mal escrita revienta el build en vez de fallar cuando alguien orqueste para él. Once **plantillas de conjunto** montan desde un cuarteto de cuerda a una orquesta sinfónica de 30 partes en una sola llamada.

`plan_form` reparte el movimiento en secciones según ocho formas (sonata, rondó, tema y variaciones, minueto y trío, verso-estribillo…) con sus proporciones habituales y su plan tonal: el segundo tema de una sonata va a la dominante si la obra está en mayor y al relativo mayor si está en menor.

`orchestrate` decide quién lleva la melodía, la armonía y el bajo, ajusta cada línea al registro cómodo **por octavas** (nunca por otro intervalo, que cambiaría la tonalidad) y aplica la transposición de cada instrumento. Comprueba el balance con el peso real de cada sección: una flauta contra tres trombones queda enterrada aunque las dos líneas estén bien escritas.

`check_voice_leading` **detecta doblajes**. En un tutti, once instrumentos llevan la misma melodía en octavas: técnicamente son octavas paralelas y musicalmente son la práctica orquestal normal. Dos voces que mantienen unísono u octava en todo el pasaje se tratan como una sola línea. Sin eso, un movimiento sinfónico daba 1313 «errores» que enterraban los pocos reales; con eso, 162.

## Ver y escuchar

`export format:"svg"` graba la partitura con **Verovio** (C++ compilado a WebAssembly: calidad profesional, cero dependencias nativas). Es la única salida que el propio agente puede revisar — un modelo multimodal mira la imagen y comprueba que la notación quedó legible, algo que no se ve en los datos.

`export format:"wav"` sintetiza el audio con **spessasynth_core**, alimentando el sintetizador con eventos directos desde la partitura: sin rodeo por bytes MIDI y con la posición de cada nota calculada en muestras exactas desde las fracciones del dominio. Respeta los cambios de tempo.

Seamos claros sobre a quién sirve cada cosa: el agente **no puede oír** el WAV — el audio es para la persona. Lo que sí puede verificar por su cuenta es la partitura grabada.

Sin SoundFont configurado se usa un banco mínimo de un solo sonido: sirve para comprobar que la cadena funciona, no para escuchar la obra. Para eso, instala un SoundFont General MIDI y apúntalo con `SINFO_SOUNDFONT`. Si el resultado sale mudo, el propio `export` lo avisa en vez de entregar un archivo silencioso.

## Groove

`export groove:"swing" humanize:0.3` cambia **cómo suena** el MIDI y el audio, no lo que aparece en la partitura. Es la distinción correcta: un pasaje con swing se escribe con corcheas rectas y el intérprete lo balancea; escribirlo en tresillos sería notación incorrecta e ilegible. Por eso el groove es un parámetro de `export` y no toca `@sinfo/core`.

Siete grooves —recto, swing, shuffle, atrasado, adelantado, funk, vals— con su balanceo, su empuje y su patrón de acentos. La humanización descuadra el tiempo y varía la intensidad; cada parte usa su propio sub-flujo aleatorio, porque si todas se descuadraran igual sonaría a grabación desplazada, no a músicos tocando juntos.

Las proporciones de balanceo se guardan como **fracciones exactas**, no decimales: con 0.667 el swing de tresillo caía en 667/4000 de redonda en vez de 1/6, metiendo denominadores absurdos en un dominio construido entero sobre aritmética racional.

## Estado

Funciona hoy: estructura de obra y movimientos, plan formal, conjuntos predefinidos, escritura en ambas notaciones, validación de compases, armonía funcional y análisis, conducción de voces con detección de doblajes, material temático y contrapunto reproducibles, orquestación con balance, comprobación de rangos con transposición, y exportación a **MIDI**, **MusicXML**, **SVG** y **WAV**.

Un primer movimiento sinfónico completo —30 partes, forma sonata de 160 compases, tema generado, orquestación tutti, verificación y doble exportación— se compone en **menos de medio segundo**.

El MusicXML sale listo para MuseScore, Sibelius, Finale y Dorico: parte notas en las barras con ligaduras, escribe grupos irregulares con su corchete, declara la transposición de los instrumentos transpositores, usa `unpitched` en percusión y alinea todas las partes al mismo número de compases. Verificado abriéndolo en MuseScore.

Previsto: groove y humanización rítmica, y exportación a LilyPond y ABC.

## Licencia

MIT