kuruitsugizaki
Permite que un modelo escriba proyectos de FL Studio (notas, cualquier plugin y preset instalado, mezclador, sidechain, automatización), los abra en un FL en ejecución y los renderice.
Documentación
kuruitsugizaki
Un servidor MCP que permite a un modelo leer y escribir proyectos de FL Studio y controlar una instancia en ejecución de FL Studio, para que pueda componer música. FL Studio es la primera integración; el sistema está pensado para llegar a más software en el futuro.
El modelo no escucha. Un render vuelve como números e imágenes (espectrogramas, sonoridad, ancho estéreo), y la persona escucha y decide.
Factory Seance, dos minutos de witch house glitchy y breakcore, fue escrita de principio a fin por Claude Opus 5.5 a través de este sistema, en unos 35 minutos: cada nota, golpe de batería, elección de sonido, ruta de mezclador y clip de automatización, usando solo los sonidos de fábrica de FL. Nadie la editó; la persona que la pidió escuchó después y la dejó tal cual. Escucha: examples/seance.mp3, el clip con sonido: examples/demo.mp4, cómo se hizo: examples/seance.md.
https://github.com/user-attachments/assets/8ee80e6e-cf6b-459d-bbe1-a83a50da325c
Solo Windows. Para instalarlo, sigue INSTALL.md. Todo lo que va más allá de la configuración y las herramientas (cómo escribir una pieza, el formato de archivo, trampas, decisiones) está en docs/, empezando por docs/INDEX.md.
Requisitos
- Windows, FL Studio 2025. Construido y medido en FL 25.1.6, API de scripting 38.
- loopMIDI con al menos un puerto. FL carga el script de controlador solo cuando su puerto de entrada MIDI existe.
- Python 3.12 o más reciente y
uv. ffmpegyffprobeen PATH, para los scripts de análisis.- Serum 2 para usar el canal Serum 2 de la plantilla. FLEX viene con FL.
Configuración
La forma rápida: cierra FL y haz doble clic en setup.bat. Instala lo que falta (uv, loopMIDI, opcionalmente ffmpeg), construye el entorno, instala el script de controlador de FL, crea el puerto MIDI y el ajuste MIDI de FL, añade el servidor a la aplicación de escritorio de Claude, y termina con una comprobación de que FL responde. INSTALL.md lista cada paso y dónde termina.
A mano, lo mismo en cuatro movimientos (INSTALL.md, A mano, tiene el detalle):
uv sync
Registra el servidor con el cliente MCP, con la ruta a esta carpeta:
"kuruitsugizaki": { "command": "<this folder>\\.venv\\Scripts\\kuruitsugizaki.exe" }
Luego, en un chat con el modelo, con FL cerrado:
- Llama a
config. En una instalación estándar, todas las rutas se encuentran solas (FL enProgram Files\Image-Line, el resto en tu carpeta de Documentos) y se muestra comodetected; para cualquier cosa que no encuentre, te pregunta dónde está y lo establece; no adivina. Las claves están enkuruitsugizaki.example.json; lo que establezcas se guarda enkuruitsugizaki.jsonaquí, que git ignora. - Llama a
fl_install, que escribe el script de controlador en la carpetaSettings\Hardwarede FL con la ruta del puente de esta carpeta incluida, añade un puerto loopMIDI llamadokuruitsugizakiy asigna el script en los ajustes MIDI de FL. Ambos viven en el registro, y FL sobrescribe el suyo al cerrarse, por eso FL tiene que estar cerrado. - Solo cuando
fl_installresponda que no pudo hacer el ajuste MIDI: en FL, Opciones, ajustes MIDI, selecciona un puerto de entrada loopMIDI, tipo de controlador kuruitsugizaki, y reinicia FL una vez.
La salida del script en FL muestra kuruitsugizaki {'api': 38, ...} al cargar. .venv\Scripts\python scripts\doctor.py imprime una línea por requisito, en cualquier momento.
El servidor inicia loopMIDI y FL mismo cuando una herramienta en vivo los encuentra cerrados.
Estructura
| archivo | qué es |
|---|---|
scripts/server.py | el servidor MCP: herramientas, y el enlace en vivo con FL |
scripts/config.py | las rutas de la máquina, leídas de kuruitsugizaki.json |
setup.bat, setup.ps1 | la instalación en un paso (INSTALL.md, La forma rápida) |
Dockerfile | para las comprobaciones automáticas de los directorios MCP: ejecuta el servidor en Linux, donde las herramientas de archivo funcionan y el enlace en vivo con FL responde que necesita Windows |
scripts/doctor.py | doctor.py [--install] [--claude [config]] [--live]: una línea por requisito de la configuración de esta máquina, ok o MISSING con qué hacer; --install ejecuta fl_install, --claude añade el servidor a la configuración de la aplicación de escritorio de Claude, --live inicia FL y le pide su versión de API |
scripts/midisetup.py | el puerto loopMIDI y el ajuste de entrada MIDI de FL, ambos en el registro: los lee, y los crea cuando ninguna entrada tiene el script |
fl/device_kuruitsugizaki.py | script de controlador de FL; fl_install lo coloca en su sitio |
scripts/flp.py | códec de eventos .flp: analizar, serializar (byte exacto), notas, diff |
scripts/project.py | .flp -> JSON completo del proyecto (round-trip byte idéntico) |
scripts/view.py | vistas compactas y de resumen, compacto -> completo |
scripts/writer.py | JSON del proyecto -> .flp, basado en plantilla |
scripts/automation.py | puntos de automatización (evento 234) y objetivos (evento 227) |
scripts/levels.py | registros de fader de mezclador y volumen principal en el evento 225 |
scripts/mixerfx.py | bloques de efectos de mezclador: lee uno de cualquier proyecto guardado por FL, lo coloca en la ranura de una pista |
scripts/presets.py | pone un preset en el estado del canal de un plugin: .vital (Vital), .h2p (Diva), .fxp (Sylenth1), .SerumPreset (Serum 2), <pack>/<preset> (FLEX) |
scripts/serum2.py, sylenth1.py, diva.py, flex.py | el formato de preset de cada sintetizador, y <script> catalog [out.tsv]: una fila TSV por preset en esa biblioteca |
scripts/catalog.py | catalog.py <preset folder> <out.tsv>: presets de Vital por estilo, osciladores, unison, efectos activados, nombres de macros |
scripts/render.py | render.py <flp> [--close] [--reopen]: el render por línea de comandos de FL a wav y mp3 junto al proyecto, luego espectro y estadísticas estéreo. FL debe estar cerrado primero; --close se lo pide (nunca forzado) |
scripts/spectro.py | render -> PNG: espectrograma de piano roll en semitonos, croma, espectrograma de 20 Hz-20 kHz, RMS/pico, correlación L/R; --bpm añade líneas de compás. PNGs en tmp/spectro/ |
scripts/loudness.py | LUFS integrado y momentáneo, LRA, pico verdadero, RMS; --zwicker añade sonoridad ISO 532-1 en sones |
scripts/scope.py | mapas de calor de densidad estéreo por momento y banda, --timeline para ancho a lo largo del tiempo |
scripts/waveshape.py | A/B de varios renders a varios zooms alrededor del segundo más fuerte de cada archivo |
scripts/onsets.py | ataques de un WAV, para cortar loops |
scripts/chop.py | corta rangos de tiempo de un archivo en sus propios WAVs con fades cortos, opcionalmente invertidos |
scripts/vox.py | voz -> sílabas con un tono mediano cada una |
scripts/samples.py | samples.py <folder>: por archivo duración, BPM y tonalidad desde el nombre, niveles, ancho, ataques, centroide -> tmp/samples/<folder>.tsv |
scripts/melstats.py | vista general de notas por instrumento de un proyecto |
scripts/fxdump_fl.py | enviado a través de fl_exec: cada efecto en cada insert del proyecto abierto con sus parámetros |
scripts/nudgemap.py, profiles/serum2/ | mapeo de parámetros de host de Serum 2 (aparcado) |
profiles/sylenth1/params.json | los 244 nombres de parámetros de Sylenth1 en orden de blob |
projects/init/init.flp | la plantilla desde la que se construyen los proyectos nuevos |
projects/init/fx.flp | una biblioteca de efectos propios de FL para copiar |
examples/ | siete documentos pequeños, uno por parte del formato (instrumentos, efectos, enrutamiento y sidechain, global, automatización de mezclador e instrumento), y una pieza completa de dos minutos con su script, notas y render; examples/build.py los construye todos. Empieza en examples/README.md |
docs/params/ | mapas de parámetros, un TSV por plugin: 27 de los propios de FL y 22 VST3s, listados en docs/params/INDEX.md; la herramienta params los lee |
Herramientas
| herramienta | hace |
|---|---|
config(key=None, value=None) | sin argumentos: cada clave de ruta, su valor o la ruta detectada para ella, y si existe; con key y value: la establece |
fl_install() | instala el script de controlador en la carpeta Hardware de FL con la ruta del puente escrita y, con FL cerrado y salvo que una entrada en FL ya tenga el script, añade un puerto loopMIDI llamado kuruitsugizaki y asigna el script en los ajustes MIDI de FL (midisetup.py); la entrada midi de la respuesta dice qué se hizo o qué queda por hacer a mano |
project_read(path, view="compact", patterns=None, arrangements=None, unused=False) | .flp -> JSON. compact: filas con punteros, la forma que toma project_write. summary: estadísticas por patrón y una línea de tiempo por pista. full: forma exacta de round-trip |
project_write(doc, path, open=False, play=None) | JSON (compacto o completo) -> .flp; open lo carga en FL (lanzando FL si es necesario), play = song o pattern |
fl_render(path, reopen=True) | guarda el proyecto abierto de FL, luego ejecuta render.py <path> --close [--reopen] en segundo plano; devuelve la ruta del log en tmp/render_logs/ |
fl_transport(action, mode=None, pattern=None) | reproducir / detener, modo canción / patrón |
plugins(query=None) | cada plugin que FL tiene instalado, y cada instancia en el proyecto de biblioteca opcional (ver Plugins por nombre): nombre, instrumento o efecto, fabricante, formato, fuente, cuántos presets cargan por nombre, si tiene un mapa de parámetros |
presets(plugin, query=None, limit=200) | los ids de preset de un plugin de biblioteca, filtrados por palabras; el id va en project_write como preset |
params(plugin, query=None) | los parámetros de un plugin de biblioteca desde docs/params/: índice (para objetivos de automatización), nombre, valor guardado y display, y el display de FL en 0, .25, .5, .75 y 1 donde se barrió |
fl_exec(code, out=None, timeout=10), fl_call(fn, args), fl_probe() | acceso directo a la API de Python de FL dentro del FL en ejecución; fl_exec devuelve lo que el código asigna a result, o lo escribe como JSON en out (también cuando result es {'__out__': path, 'data': value}) |
test() | devuelve ok |
project_read, project_write, plugins, presets y params recargan los módulos del códec en cada llamada; un cambio en server.py, flex.py o diva.py necesita un reinicio de MCP, y una herramienta nueva llega a un cliente solo en una sesión iniciada después.
La plantilla
projects/init/init.flp, guardada por FL: un Sampler, Serum 2, FLEX, un canal de audio y un clip de automatización; master más inserts 1-17, sin efectos. Enruta a inserts 1-16: el insert 17 no toma fader en este mezclador (docs/NOTES.md). Un instrumento plugin se copia del canal de plantilla de ese plugin, así que con esta plantilla plugin toma serum2 o flex. Los cargadores de preset también manejan Vital, Diva y Sylenth1: para usarlos, guarda una plantilla propia en FL con esos plugins y establece template a ella, o toma un canal de cualquier proyecto con state: {from, channel}. Los plugins de plantilla se emparejan por nombre: Serum2, FLEX, Vital, Sylenth1, Diva(x64).
Efectos
El escritor no puede crear un efecto; copia uno, con sus ajustes, de un proyecto guardado por FL. projects/init/fx.flp contiene los propios de FL:
| de insert/ranura | efecto |
|---|---|
| 1/0 | Fruity Parametric EQ 2, guardado con una banda configurada (un shelf alto a -18 dB); mueve o pon a cero sus bandas antes de confiar en él |
| 2/0 | Fruity Limiter |
| 3/0 | Fruity Limiter en modo compresor, configurado para ducking de sidechain |
| 4/0 | Fruity Reeverb 2 |
| 5/0 | Fruity Multiband Compressor |
| 6/0 | Soundgoodizer |
| 7/0 | Gross Beat |
Soundgoodizer y Gross Beat cargan solo en una edición de FL que los incluya. Cualquier otro efecto instalado entra por nombre (Plugins por nombre).
Plugins por nombre
Cada plugin que FL tiene instalado puede entrar en un proyecto por nombre, sin nada que preparar: el gestor de plugins de FL mantiene una entrada por plugin en su base de datos de plugins (Documents\Image-Line\FL Studio\Presets\Plugin database, clave de configuración plugin_db, encontrada sola), y el escritor construye el plugin desde ella en los bloques de la plantilla, fresco, como al arrastrarlo desde el navegador de FL. plugins los lista; project_write toma un instrumento como {"kind": "plugin", "plugin": "<name>", "preset": "<id>"} y un efecto como mixer.effects: [{"track": t, "slot": s, "plugin": "<name>", "preset": "<id>"}]. Un plugin instalado en varios formatos es un nombre; añade "format": "vst3" (o native, clap, vst) para elegir uno. Un plugin que FL no ha escaneado no se lista: ejecuta primero el gestor de plugins de FL.
Opcionalmente, un proyecto FL guardado puede contener instancias afinadas que quieras reutilizar tal como están, cada instrumento en su propio canal, cada efecto en una ranura de mezclador; establece su ruta como clave de configuración library. Una instancia de biblioteca gana sobre una nueva del mismo nombre.
Presets, listados por presets:
- Archivos
.fst, de las carpetas de presets de fábrica y de usuario de FL (claves de configuraciónfl_presets,fl_user_presets, encontradas por sí mismas). FL escribe uno para cualquier plugin desde su menú de plugins, incluidos los VST (los de un VST envuelto van aFruity Wrapper - <name>), así que guardar un sonido allí lo hace utilizable por nombre. - Archivos de preset de proveedor para plugins JUCE (un preset XML) y los de Cableguys (
#zip#), de las carpetas que la clave de configuraciónvst_presetsnombra por plugin; un plugin JUCE no necesita una instancia guardada para esto:{"<plugin>": {"ext": ".vpreset", "dirs": {"": "<factory folder>", "user": "<user folder>"}}}.
Mapas de parámetros: abre una copia de la biblioteca en FL (el barrido deja cada perilla en 1) y envía el texto de scripts/params_fl.py a través de fl_exec una vez. Luego _kz_dump('tmp/params/pass1_all.json'); para cada v en 0, .25, .5, .75, 1, cuatro llamadas separadas de fl_exec, _kz_sweep(v, 'set'), _kz_sweep(v, 'nudge'), _kz_sweep(v, 'back'), _kz_read('tmp/params/sweep_<v>.json') (una lectura mostrada en la llamada que la estableció está desactualizada); luego python scripts/parammap.py escribe docs/params/. Cada función toma listas de only y skip de nombres de plugins tal como FL los reporta (docs/params/INDEX.md, columna as FL names it).
JSON de proyecto, forma compacta
{
"format": "compact",
"base": "projects/init/init.flp",
"project": {"bpm": 175, "ppq": 96},
"instruments": [{"id": "lead", "kind": "plugin", "plugin": "serum2", "preset": "C:/presets/lead.SerumPreset", "mixer": 1},
{"id": "keys", "kind": "plugin", "plugin": "flex", "preset": "Hard 808s/808 Clean Fuzz", "mixer": 2},
{"id": "pad", "kind": "plugin", "state": {"from": "projects/my_song.flp", "channel": 3}, "mixer": 3}],
"samples": [{"id": "snare", "kind": "audio", "path": "C:/samples/snare.wav", "mixer": 8}],
"automation": [{"id": "fade", "kind": "automation", "target": {"mixer": 0, "param": "vol"}, "points": [[0, 0.8], [32, 0.0]]}],
"mixer": {"volume": {"1": 0.729, "7": 0.434}, "main_volume": 0.6625},
"note_cols": ["bar", "beat", "tick", "len", "key", "vel", "inst"],
"patterns": [{"id": "lead_a", "notes": [[1, 1, 4, 66, "A#4", 96, 0], [1, 2, 3, 18, "G#4", 80, 0]]}],
"clip_cols": ["track", "bar", "beat", "tick", "len", "src", "trim?"],
"arrangements": [{"name": "Arrangement", "clips": [[5, 17, 1, 0, 1536, "lead_a"], [4, 17, 3, 0, 31, "s0"], [20, 1, 1, 0, 27648, "a0"]]}]
}
- Tiempo:
[bar, beat, tick], compás y tiempo basados en 1, 96 ticks por tiempo, 4/4. Longitudes en ticks. Enteros exactos; no se impone una cuadrícula. - Notas: nombres de notas, MIDI 60 = C4 (la propia interfaz de FL llama MIDI 60 C5). Se aceptan bemoles.
- Punteros: el
instde una nota es un índice eninstruments. Elsrcde un clip es un id de patrón,s<N>(muestra N) oa<N>(automatización N), así que ningún instrumento o id de patrón puede parecerse as1oa1. - Patrones contienen notas para cualquier número de instrumentos. El
lende un clip establece cuánto de un patrón se reproduce. - Instrumentos:
state: {from: file, channel: n}copia ese canal completo, preset y enrutamiento incluidos, de cualquier proyecto guardado por FL; o una plantillaplugin(ver La plantilla).presetcarga un sonido en él: una ruta.SerumPreset,.vital,.h2po.fxp, o<pack>/<preset>para FLEX.mixer: nlo enruta. Los.fxpde Serum 1 no son compatibles. - Canales de sampler:
"kind": "sampler"con unpathcarga ese archivo en un sampler, así que las notas lo afinan (C4 = tono original). Un sampler reproduce su muestra hasta el final sin importar la longitud de la nota; corta los chops a sus propios archivos primero (chop.py). - Modo canción: los documentos compactos se guardan en modo canción para que FL y su renderizado por línea de comandos reproduzcan el arreglo;
project.song_mode: falseguarda el modo patrón. - Muestras:
path, ostatepara copiar una. Un clip de audio sintrimreproduce toda la muestra;trimes[start_ms, end_ms]dentro de la muestra. Una ruta puede comenzar con%FLStudioFactoryData%, el propio token de FL para su carpeta de instalación, así que%FLStudioFactoryData%/Data/Patches/Packs/Drums/Kicks/808 Kick.wavencuentra el kick de fábrica de FL en cualquier máquina. - Información del proyecto:
projecttomatitle,author,genreycomment, escritos en la información del proyecto de FL (FL muestra el título en su ventana), yproject_readlos devuelve. - Automatización:
pointsson[beat, value, tension?]en tiempos desde el inicio del clip. Objetivos:{"global": "tempo" | "main_pitch"},{"mixer": t, "param": "vol" | "pan" | "sep"}(la inserción 0 es el maestro: su fader es el nivel general),{"mixer": t, "slot": s, "param": n}(parámetro de efecto n),{"mixer": t, "slot": s, "rec": "0x1f00"}(el mute de la ranura) o"0x1f01"(su mezcla),{"channel": id, "param": n}(parámetro de instrumento n) o{"channel": id, "param": "vol" | "pan" | "pitch" | "fcut" | "fres" | "mute"}. Valores: tempo = (bpm - 60) / 120; tono principal 0.5 afinado, +-12 semitonos; volumen del mezclador 0.8 = 0 dB; pan 0 izquierda, 0.5 centro, 1 derecha; sep 1 mono, 0 más ancho; tono de canal 0.5 afinado, +-48 semitonos; volumen de canal 0.78 es el predeterminado de FL.{"global": "main_vol"}se escribe pero FL 2025 lo ignora.examples/README.mdlista las escalas medidas hasta ahora (fcut, fres y mute se escriben pero sus escalas no se midieron). - Efectos del mezclador:
mixer.effects: [{"track": 0, "slot": 9, "from": {"file": "projects/init/fx.flp", "track": 2, "slot": 0}}]copia ese efecto con su configuración en la ranura dada, reemplazando lo que había allí. - Rutas del mezclador:
mixer.routes: [{"from": 11, "to": 10, "level": 0.0}]enruta la inserción 11 a la inserción 10;level0 es un envío solo de sidechain, que es lo que un limitador o compresor con sidechain escucha."on": falseelimina una ruta. - Volumen del mezclador:
mixer.volumemapea inserción -> fader, 0.8 = 0 dB;main_volumees el volumen principal global de FL. - base: el archivo cuyos globales, secciones no mapeadas, plantillas de pista de arreglo y mezclador se conservan. Construir sobre uno de tus propios proyectos conserva sus cadenas de mezclador. Predeterminado: la clave
template.
La forma compacta omite bytes que no están mapeados. Para editar un proyecto existente sin perder nada, usa view="full".
Aún no escrito
Panes y envíos del mezclador como configuraciones estáticas; un plugin que el administrador de plugins de FL no ha escaneado; nombres de patrones; firma de tiempo; archivos de preset de proveedor para VST cuyo estado no es ni XML JUCE ni #zip# (un .fst guardado desde FL es su ruta). Ver docs/FLP.md, Abrir.
Licencia
MIT, ver LICENSE. Cubre este código y estos documentos, no FL Studio, los plugins o sus presets.