claude-code-arcane 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +57 -55
- package/agents/audio/audio-director.md +90 -0
- package/agents/audio/audio-programmer.md +97 -0
- package/agents/audio/audio-qa.md +95 -0
- package/agents/audio/composer.md +88 -0
- package/agents/audio/technical-sound-designer.md +90 -0
- package/agents/audio/voice-director.md +96 -0
- package/agents/career/career-strategist.md +88 -0
- package/agents/career/cv-reviewer.md +81 -0
- package/agents/career/hiring-manager.md +68 -0
- package/agents/career/mock-interviewer.md +76 -0
- package/agents/freelance/client-screener.md +84 -0
- package/agents/freelance/discovery-call.md +65 -0
- package/agents/freelance/pipeline-strategist.md +99 -0
- package/agents/freelance/proposal-reviewer.md +82 -0
- package/dist/cli.js +52 -13
- package/docs/RELEASE-SETUP.md +1 -1
- package/docs/SKILLS-CATALOG.md +47 -3
- package/docs/USER-GUIDE.md +77 -72
- package/docs/agent-hierarchy.md +21 -1
- package/docs/presentations/build_arcane_deck.py +2 -2
- package/docs/presentations/build_arcane_deck_12.py +2 -2
- package/package.json +2 -2
- package/profiles/audio.yaml +45 -0
- package/profiles/freelance.yaml +43 -0
- package/profiles/job-hunt.yaml +5 -2
- package/rules/drafter-reviewer.md +16 -3
- package/rules/freelance-guardrails.md +53 -0
- package/rules/gamedev/audio-code.md +45 -0
- package/skills/adaptive-music/SKILL.md +171 -0
- package/skills/adaptive-music/references/anti-patterns.md +53 -0
- package/skills/audio-audit/SKILL.md +164 -0
- package/skills/audio-audit/references/anti-patterns.md +65 -0
- package/skills/audio-audit/scripts/check_assets.py +208 -0
- package/skills/audio-bible/SKILL.md +119 -0
- package/skills/audio-bible/references/anti-patterns.md +39 -0
- package/skills/audio-mix/SKILL.md +151 -0
- package/skills/audio-mix/references/anti-patterns.md +65 -0
- package/skills/audio-spec/SKILL.md +107 -0
- package/skills/audio-spec/references/anti-patterns.md +45 -0
- package/skills/career-registry/SKILL.md +95 -0
- package/skills/career-registry/scripts/career_registry.py +488 -0
- package/skills/client-screen/SKILL.md +83 -0
- package/skills/client-screen/references/red-flags.md +95 -0
- package/skills/cover-letter/SKILL.md +4 -3
- package/skills/cv-tailor/SKILL.md +11 -2
- package/skills/freelance-deliver/SKILL.md +90 -0
- package/skills/freelance-hunt/SKILL.md +126 -0
- package/skills/freelance-hunt/references/templates/Connects.md +39 -0
- package/skills/freelance-hunt/references/templates/Contrato.md +60 -0
- package/skills/freelance-hunt/references/templates/Propuesta.md +66 -0
- package/skills/freelance-kickoff/SKILL.md +94 -0
- package/skills/freelance-pipeline/SKILL.md +109 -0
- package/skills/freelance-pipeline/scripts/import_history.py +425 -0
- package/skills/freelance-profile/SKILL.md +93 -0
- package/skills/freelance-proposal/SKILL.md +112 -0
- package/skills/freelance-scan/SKILL.md +196 -0
- package/skills/freelance-scan/references/dedup.md +35 -0
- package/skills/freelance-scan/references/platforms.md +167 -0
- package/skills/freelance-scan/scripts/freelance_search.py +1077 -0
- package/skills/interview-prep/SKILL.md +11 -3
- package/skills/job-aplicar/SKILL.md +5 -1
- package/skills/job-hunt/SKILL.md +17 -0
- package/skills/job-outcome/SKILL.md +15 -1
- package/skills/job-upskill/SKILL.md +10 -2
- package/skills/middleware-integration/SKILL.md +186 -0
- package/skills/middleware-integration/references/anti-patterns.md +69 -0
- package/skills/midi-compose/SKILL.md +136 -0
- package/skills/midi-compose/references/smf-format.md +129 -0
- package/skills/midi-compose/scripts/compose_midi.py +326 -0
- package/skills/midi-compose/templates/example-composition.json +69 -0
- package/skills/music-composition/SKILL.md +106 -0
- package/skills/music-composition/references/form.md +57 -0
- package/skills/music-composition/references/game-music.md +82 -0
- package/skills/music-composition/references/harmony.md +82 -0
- package/skills/music-composition/references/melody.md +48 -0
- package/skills/music-composition/references/orchestration.md +69 -0
- package/skills/music-composition/references/rhythm-groove.md +49 -0
- package/skills/procedural-audio/SKILL.md +157 -0
- package/skills/procedural-audio/references/anti-patterns.md +45 -0
- package/skills/sfx-design/SKILL.md +130 -0
- package/skills/sfx-design/references/anti-patterns.md +53 -0
- package/skills/spatial-audio/SKILL.md +139 -0
- package/skills/spatial-audio/references/anti-patterns.md +53 -0
- package/skills/ui-sound-design/SKILL.md +133 -0
- package/skills/ui-sound-design/references/anti-patterns.md +57 -0
- package/skills/voice-pipeline/SKILL.md +150 -0
- package/skills/voice-pipeline/references/anti-patterns.md +69 -0
|
@@ -4,7 +4,7 @@ description: "Prepare for a job interview: company research, likely question ban
|
|
|
4
4
|
argument-hint: "[application-note | company + role + round]"
|
|
5
5
|
category: "career"
|
|
6
6
|
user-invocable: true
|
|
7
|
-
allowed-tools: Read, Glob, Grep, Write, Edit, WebFetch
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Write, Edit, WebFetch, Task
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Interview Prep - Preparación de entrevista
|
|
@@ -57,13 +57,21 @@ Preparás al usuario para una entrevista concreta: research de la empresa, banco
|
|
|
57
57
|
1. Leer aplicación + empresa + perfil. WebFetch del sitio/notas de la empresa.
|
|
58
58
|
2. Crear nota en `06-Entrevistas/` con el template `Entrevista`.
|
|
59
59
|
3. Llenar research, banco de preguntas (marcando las top), STAR de las 3-5 historias clave, preguntas a hacer, red flags.
|
|
60
|
-
4. Ofrecer un **mock
|
|
60
|
+
4. Ofrecer un **mock** con el agente `mock-interviewer` (ver abajo).
|
|
61
61
|
5. Post-entrevista: ayudar a registrar feedback y definir próximo paso / follow-up (con `/cold-outreach`).
|
|
62
62
|
|
|
63
|
+
## El mock va en un agente, no en esta sesión
|
|
64
|
+
|
|
65
|
+
El mock lo corre el agente `mock-interviewer`, no vos. La razón es dura: vos acabás de escribir las respuestas STAR del usuario, así que ibas a preguntar exactamente lo que preparó, iba a salir perfecto, y el usuario iba a entrar a la entrevista real con confianza falsa.
|
|
66
|
+
|
|
67
|
+
Al lanzarlo, pasale **inline**: el JD, la ronda, y el CV que se mandó. **No** le pases el perfil maestro ni la nota de `06-Entrevistas/` con las respuestas preparadas - eso invalida el simulacro, y el agente tiene instrucción de rechazarlos.
|
|
68
|
+
|
|
69
|
+
Cuando devuelve el feedback, tu trabajo es registrarlo: agregá en la nota de `06-Entrevistas/` qué respuestas hay que rehacer y por qué. Ese es el valor que queda para la próxima ronda.
|
|
70
|
+
|
|
63
71
|
## Idioma
|
|
64
72
|
|
|
65
73
|
En el idioma de la entrevista. Preparar respuestas en inglés si el rol es internacional, aunque la prep se converse en español.
|
|
66
74
|
|
|
67
75
|
## Handoff
|
|
68
76
|
|
|
69
|
-
Pedí aprobación (approval) antes de escribir la nota en `06-Entrevistas/`. Cuando la prep está READY, ofrecé
|
|
77
|
+
Pedí aprobación (approval) antes de escribir la nota en `06-Entrevistas/`. Cuando la prep está READY, ofrecé el mock con `mock-interviewer` y registrá su feedback; post-entrevista, seguí con `/cold-outreach` para el follow-up.
|
|
@@ -54,7 +54,11 @@ Usar el skill **cover-letter** según el canal de aplicación (formulario ATS
|
|
|
54
54
|
|
|
55
55
|
## Paso 7 - Reviewer (regla drafter-reviewer, obligatorio)
|
|
56
56
|
|
|
57
|
-
Seguir `.claude/rules/drafter-reviewer.md`: lanzar
|
|
57
|
+
Seguir `.claude/rules/drafter-reviewer.md`: lanzar el agente **`cv-reviewer`** de contexto fresco con el JD + borradores inline y el perfil usado. Aplicar la Parte A (reemplazos exactos) e incorporar lo relevante de la Parte B. Una sola ronda. Never stuff keywords: los gaps se reconocen en el cover, no se rellenan en el CV. Normalizar guiones largos del texto sugerido.
|
|
58
|
+
|
|
59
|
+
**Segunda lente si el rol es senior o la postulación importa:** lanzar `hiring-manager` **en paralelo** con `cv-reviewer` (no en cadena). Contesta si el CV convence a quien contrata, que es una pregunta distinta a si pasa el filtro. Sus 3 preguntas van a `## Notas` de la nota: son material directo de `/interview-prep`.
|
|
60
|
+
|
|
61
|
+
Si el directorio de agentes no está instalado, el fallback con `general-purpose` está en la regla.
|
|
58
62
|
|
|
59
63
|
## Paso 8 - CONFIRMACIÓN 2: aprobar CV final
|
|
60
64
|
|
package/skills/job-hunt/SKILL.md
CHANGED
|
@@ -67,6 +67,7 @@ Según el estado del workspace, recomendar el skill siguiente (ver routing).
|
|
|
67
67
|
| Corrida de búsqueda con CLIs y dedup entre corridas | `/job-scrape` |
|
|
68
68
|
| Postulación completa (nota + CV + cover + review + PDF) | `/job-aplicar` |
|
|
69
69
|
| Registrar resultado/avance de una postulación | `/job-outcome` |
|
|
70
|
+
| ¿Ya me postulé acá? / exportar el registro a CSV | `/career-registry` |
|
|
70
71
|
| Gaps de skills agregados + plan de estudio | `/job-upskill` |
|
|
71
72
|
| Adaptar CV a una oferta | `/cv-tailor` |
|
|
72
73
|
| Exportar CV a PDF ATS | `/cv-ats-export` |
|
|
@@ -75,6 +76,22 @@ Según el estado del workspace, recomendar el skill siguiente (ver routing).
|
|
|
75
76
|
| Optimizar perfil de LinkedIn | `/linkedin-optimize` |
|
|
76
77
|
| Crear/actualizar portfolio web | `/portfolio-site` |
|
|
77
78
|
| Preparar una entrevista | `/interview-prep` |
|
|
79
|
+
| Diagnosticar por qué se frena la búsqueda | agente `career-strategist` (vía `/job-upskill`) |
|
|
80
|
+
|
|
81
|
+
## Los 4 agentes del perfil
|
|
82
|
+
|
|
83
|
+
El perfil instala `agents/career/`. Los agentes existen por una sola razón: **hay lecturas que solo sirven con contexto fresco**. Vos no podés revisar un CV que acabás de escribir, ni entrevistar a alguien cuyas respuestas preparadas leíste. Son read-only: devuelven análisis, los archivos los escribe la sesión principal.
|
|
84
|
+
|
|
85
|
+
| Agente | Contesta | Lo lanza |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `cv-reviewer` | ¿Pasa el filtro del ATS y del recruiter? | `/job-aplicar`, `/cv-tailor`, `/cover-letter` |
|
|
88
|
+
| `hiring-manager` | ¿Convence a quien contrata? (evidencia de impacto, no keywords) | `/job-aplicar`, `/cv-tailor` (opcional) |
|
|
89
|
+
| `mock-interviewer` | ¿Aguanta la entrevista real? | `/interview-prep` |
|
|
90
|
+
| `career-strategist` | ¿Por qué me frenan siempre en la misma etapa? | `/job-upskill` (10+ aplicaciones resueltas) |
|
|
91
|
+
|
|
92
|
+
`cv-reviewer` y `hiring-manager` se lanzan **en paralelo** cuando van los dos: son lentes independientes, encadenarlos solo suma latencia.
|
|
93
|
+
|
|
94
|
+
Si el usuario instaló el perfil sin el directorio de agentes, la regla `drafter-reviewer` documenta el fallback con `general-purpose`.
|
|
78
95
|
|
|
79
96
|
## Workflow estándar para una nueva oportunidad
|
|
80
97
|
|
|
@@ -35,14 +35,28 @@ Empresa/oferta indicada: `$ARGUMENTS`
|
|
|
35
35
|
- `rechazado` con feedback → anotar la lección en la nota; si se acumulan 3+ resoluciones, ofrecer `/job-upskill` y revisar los criterios de búsqueda contra los resultados reales.
|
|
36
36
|
- `sin_respuesta` a los 10-14 días → ofrecer follow-up con `/cold-outreach`.
|
|
37
37
|
- `contratado` → 🎉 y actualizar el resto de las aplicaciones abiertas (¿retirarse de procesos?).
|
|
38
|
+
- **Freelance** - `en_conversacion` → ofrecer ensayar con el agente `discovery-call`; `contrato_activo` → crear la nota en `08-Contratos/`; `sin_respuesta` o `rechazado` acumulados (10+ propuestas resueltas) → ofrecer `/freelance-pipeline`, que es donde se ve si el problema es la propuesta o el targeting.
|
|
38
39
|
|
|
39
40
|
6. **Confirmar** al usuario qué se registró (nota, campos tocados, fila del dashboard).
|
|
40
41
|
|
|
41
42
|
## Vocabulario de `estado` (frontmatter)
|
|
42
43
|
|
|
44
|
+
Depende del `tipo:` de la nota. Leelo primero: son dos pipelines distintos y confundirlos rompe el dashboard y los reportes.
|
|
45
|
+
|
|
46
|
+
**`tipo: aplicacion` (empleo, perfil `+job-hunt`):**
|
|
47
|
+
|
|
43
48
|
`interesado → aplicado → entrevista → oferta` y cierra en: `contratado | rechazado | sin_respuesta | declinada | descartado`
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
**`tipo: freelance` (propuestas, perfil `+freelance`):**
|
|
51
|
+
|
|
52
|
+
`interesado → screeneado → propuesta_enviada → en_conversacion → contrato_activo → entregado` y cierra en: `ganado_cerrado | sin_respuesta | rechazado | descartado | disputa`
|
|
53
|
+
|
|
54
|
+
En freelance hay dos registros extra que no existen en empleo, y sin ellos `/freelance-pipeline` no puede calcular nada:
|
|
55
|
+
|
|
56
|
+
- Al marcar `propuesta_enviada`, preguntar y registrar **cuántos Connects costó** (frontmatter `connects_gastados`) y agregar la fila al ledger de `07-Recursos/Connects - <año>.md`.
|
|
57
|
+
- Si la oferta se cerró sin que el cliente contratara a nadie, el resultado en el ledger es `oferta_nunca_contrato`, no `sin_respuesta`. Son cosas distintas: la primera es Connects perdidos por mal screening, la segunda es tu propuesta que no convenció.
|
|
58
|
+
|
|
59
|
+
Emojis del dashboard: ⬜ interesado/screeneado · ✅ aplicado/propuesta_enviada · 📅 entrevista/oferta/en_conversacion · 🔨 contrato_activo/entregado · ❌ rechazado/sin_respuesta/declinada · 🚫 descartado/cerrada · ⚠️ disputa
|
|
46
60
|
|
|
47
61
|
## Reglas
|
|
48
62
|
|
|
@@ -4,7 +4,7 @@ description: "Aggregate skill gaps across all scored job applications, weigh the
|
|
|
4
4
|
argument-hint: "[url | nota de aplicación]"
|
|
5
5
|
category: "career"
|
|
6
6
|
user-invocable: true
|
|
7
|
-
allowed-tools: Read, Glob, Grep, Write, Bash, WebFetch, WebSearch
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Write, Bash, WebFetch, WebSearch, Task
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# /job-upskill - Análisis de gaps y plan de estudio
|
|
@@ -13,6 +13,14 @@ Detecta qué skills te están costando puntos de match en las ofertas reales y a
|
|
|
13
13
|
|
|
14
14
|
Argumento: `$ARGUMENTS` (vacío = modo agregado; URL o nota = modo targeted)
|
|
15
15
|
|
|
16
|
+
## Antes del plan: ¿el gap es real o de presentación?
|
|
17
|
+
|
|
18
|
+
Este skill asume que la respuesta es estudiar. A veces no lo es: si la experiencia está en el perfil maestro pero no llega al CV, el plan de estudio manda al usuario a aprender meses lo que ya sabe.
|
|
19
|
+
|
|
20
|
+
En **modo agregado con 10+ aplicaciones resueltas**, lanzar primero el agente `career-strategist` (contexto fresco, read-only). Diagnostica el embudo completo y separa gap real de gap de presentación. Si el punto de fuga no es la etapa técnica, decílo antes del heatmap: el plan de estudio no es la palanca y conviene ir a `/cv-tailor` o `/interview-prep`.
|
|
21
|
+
|
|
22
|
+
Con menos de 10 resueltas, saltear: no hay volumen para un embudo y el propio agente lo va a rechazar. Seguí directo al modo agregado.
|
|
23
|
+
|
|
16
24
|
## Modo agregado (sin argumento)
|
|
17
25
|
|
|
18
26
|
1. **Recolectar evidencia.** Grep de `match_score:` en `03-Aplicaciones/*.md` (excluir `_index`; aceptar también el campo legacy `score:` de notas viejas). De cada nota leer: `match_score`, `estado`, `perfil`, y los gaps mencionados en las secciones "Match con mi perfil" / "Gaps reales" y los gaps reconocidos en `## Notas`.
|
|
@@ -55,4 +63,4 @@ Argumento: `$ARGUMENTS` (vacío = modo agregado; URL o nota = modo targeted)
|
|
|
55
63
|
|
|
56
64
|
## Handoff
|
|
57
65
|
|
|
58
|
-
Reporte COMPLETE en `07-Recursos/`. Retomar el pipeline con `/job-aplicar` en las ofertas abiertas, o `/job-scrape` para una corrida nueva apuntando a los gaps que ya se están cerrando.
|
|
66
|
+
Reporte COMPLETE en `07-Recursos/`. Retomar el pipeline con `/job-aplicar` en las ofertas abiertas, o `/job-scrape` para una corrida nueva apuntando a los gaps que ya se están cerrando. Si el `career-strategist` ubicó la fuga fuera de la etapa técnica, el siguiente paso es ese (`/cv-tailor`, `/interview-prep` o `/job-search`), no el plan de estudio.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: middleware-integration
|
|
3
|
+
description: "Integracion de middleware de audio (Wwise/FMOD): containers, eventos, Switches/States/RTPCs, atenuaciones, soundbanks, streaming vs memoria, y el contrato con el codigo de gameplay. Usar para: Wwise, FMOD, event authoring, RTPC, soundbanks, arquitectura de audio."
|
|
4
|
+
category: "audio"
|
|
5
|
+
argument-hint: "[structure | events | rtpc | banks | contract]"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Write, Edit
|
|
8
|
+
---
|
|
9
|
+
# middleware-integration — Wwise / FMOD
|
|
10
|
+
|
|
11
|
+
Diseña la estructura del proyecto de middleware y el contrato entre el audio y el código de gameplay.
|
|
12
|
+
|
|
13
|
+
Conceptos agnósticos donde se puede; nomenclatura de Wwise como referencia principal, con el equivalente FMOD anotado, porque los dos modelan lo mismo con nombres distintos.
|
|
14
|
+
|
|
15
|
+
## Cuándo usar
|
|
16
|
+
|
|
17
|
+
- Estructurar un proyecto de Wwise/FMOD desde cero
|
|
18
|
+
- Definir el contrato de eventos que va a postear el código
|
|
19
|
+
- Diseñar la organización de soundbanks
|
|
20
|
+
- El proyecto de middleware creció desordenado y hay que reorganizarlo
|
|
21
|
+
- Decidir qué va en memoria y qué va en streaming
|
|
22
|
+
|
|
23
|
+
## Input
|
|
24
|
+
|
|
25
|
+
1. Leer `/audio-spec` (event lists), `/audio-mix` (buses) y `/adaptive-music` (matriz de transiciones) si existen
|
|
26
|
+
2. Determinar el middleware y su versión
|
|
27
|
+
3. Preguntar al usuario: plataformas target, budget de memoria de audio, y si el proyecto ya existe o se arranca limpio
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Equivalencias Wwise ↔ FMOD
|
|
32
|
+
|
|
33
|
+
| Concepto | Wwise | FMOD Studio |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| Unidad que dispara el código | Event | Event |
|
|
36
|
+
| Contenedor de variación | Random / Sequence Container | Multi-instrument, Scatterer |
|
|
37
|
+
| Selección por estado discreto | Switch Container | Parameter (labeled) |
|
|
38
|
+
| Mezcla continua entre variantes | Blend Container | Parameter con automatización |
|
|
39
|
+
| Parámetro continuo | RTPC (Game Parameter) | Parameter (continuous) |
|
|
40
|
+
| Estado global | State | Global parameter / Snapshot |
|
|
41
|
+
| Agrupación de mezcla | Actor-Mixer / Bus | Group bus |
|
|
42
|
+
| Snapshot de mezcla | State con propiedades de bus | Snapshot |
|
|
43
|
+
| Curva de distancia | Attenuation ShareSet | 3D panner + curvas |
|
|
44
|
+
| Empaquetado | SoundBank | Bank |
|
|
45
|
+
| Música interactiva | Music Switch / Playlist Container | Event con transition regions |
|
|
46
|
+
|
|
47
|
+
## Estructura del proyecto
|
|
48
|
+
|
|
49
|
+
Organizar por **sistema de gameplay**, no por tipo de sonido. `Weapons/Shotgun/` es navegable; `Impacts/` con todo mezclado no.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Actor-Mixer Hierarchy
|
|
53
|
+
├── Player (locomoción, armas, voz del jugador)
|
|
54
|
+
├── Enemies (por arquetipo)
|
|
55
|
+
├── World (props, entorno, destructibles)
|
|
56
|
+
├── Ambience (por zona)
|
|
57
|
+
└── UI
|
|
58
|
+
|
|
59
|
+
Interactive Music
|
|
60
|
+
└── (por estado: Explore, Combat, Victory...)
|
|
61
|
+
|
|
62
|
+
Events
|
|
63
|
+
└── (espeja la jerarquía de Actor-Mixer)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Reglas que evitan el desorden:**
|
|
67
|
+
- Un evento por acción de gameplay, no por sonido. `Play_Weapon_Fire` con un Switch de tipo de arma le gana a un evento por arma
|
|
68
|
+
- Los ShareSets (atenuación, conversión, efectos) se definen una vez y se reusan. Un ShareSet por objeto es la causa número uno de proyectos inmanejables
|
|
69
|
+
- Work units separados por sistema: es lo que permite que dos personas trabajen sin conflictos de merge
|
|
70
|
+
|
|
71
|
+
## Containers
|
|
72
|
+
|
|
73
|
+
| Container | Cuándo | Cuidado |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| **Random** | Variación de un mismo sonido | Usar *shuffle* (round-robin), no random puro: random repite |
|
|
76
|
+
| **Sequence** | Sonidos que tienen orden (carga → disparo) | Definir qué pasa al interrumpir |
|
|
77
|
+
| **Switch** | Selección por estado discreto (superficie, tipo de arma) | Definir el default: si el Switch no está seteado, el evento no suena |
|
|
78
|
+
| **Blend** | Mezcla continua por parámetro (RPM de motor) | Cuidar el solapamiento de las curvas o hay huecos |
|
|
79
|
+
|
|
80
|
+
## Switches, States y RTPCs
|
|
81
|
+
|
|
82
|
+
Tres cosas distintas, y confundirlas es un error de arquitectura frecuente:
|
|
83
|
+
|
|
84
|
+
| | Alcance | Tipo | Ejemplo |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| **Switch** | Por objeto de juego | Discreto | Superficie bajo *este* personaje |
|
|
87
|
+
| **State** | Global | Discreto | El juego está en pausa |
|
|
88
|
+
| **RTPC** | Global o por objeto | Continuo | Salud del jugador, intensidad de combate |
|
|
89
|
+
|
|
90
|
+
**Regla:** si es una propiedad de un objeto específico, es Switch. Si es una condición del juego entero, es State. Si es un número que varía suave, es RTPC.
|
|
91
|
+
|
|
92
|
+
Usar un State para algo que es por objeto es el bug que produce "todos los personajes caminan sobre la superficie del jugador".
|
|
93
|
+
|
|
94
|
+
**Contrato de RTPCs:** todo RTPC necesita rango declarado, curva, y valor por default. El código **normaliza y clampea antes de enviarlo** — el middleware no debería recibir nunca un valor fuera de rango.
|
|
95
|
+
|
|
96
|
+
## Soundbanks
|
|
97
|
+
|
|
98
|
+
Estrategia por defecto que funciona en la mayoría de los proyectos:
|
|
99
|
+
|
|
100
|
+
| Bank | Contenido | Cuándo carga |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| `Init` | Estructura del proyecto, buses | Al arrancar, siempre |
|
|
103
|
+
| `Global` | UI, voz del jugador, música persistente | Al arrancar, siempre |
|
|
104
|
+
| `Level_XX` | Ambiente y props del nivel | Al cargar el nivel |
|
|
105
|
+
| `Character_XX` | Por personaje/enemigo | Con el spawn del personaje |
|
|
106
|
+
|
|
107
|
+
**Streaming vs memoria:**
|
|
108
|
+
|
|
109
|
+
| Va en memoria | Va en streaming |
|
|
110
|
+
|---|---|
|
|
111
|
+
| Todo lo corto y frecuente (SFX, UI, footsteps) | Música |
|
|
112
|
+
| Cualquier cosa que necesite latencia cero | Diálogo largo |
|
|
113
|
+
| | Ambientes largos |
|
|
114
|
+
|
|
115
|
+
El streaming ahorra memoria y cuesta latencia y accesos a disco. Un SFX en streaming llega tarde; la música en memoria se come el budget.
|
|
116
|
+
|
|
117
|
+
**Prefetch** para lo streameado que necesita arrancar rápido: los primeros ms van en memoria y el resto se stremea.
|
|
118
|
+
|
|
119
|
+
## Contrato con el código
|
|
120
|
+
|
|
121
|
+
Lo que el código tiene que poder hacer, y nada más:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
PostEvent(eventName, gameObject) → dispara
|
|
125
|
+
StopEvent(playingId, fadeMs) → detiene lo que loopea
|
|
126
|
+
SetSwitch(group, value, gameObject) → propiedad de objeto
|
|
127
|
+
SetState(group, value) → condición global
|
|
128
|
+
SetRTPC(param, value01, gameObject?) → parámetro, normalizado
|
|
129
|
+
RegisterGameObject / Unregister → ciclo de vida
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Reglas del contrato** (las mismas que enforcea `rules/gamedev/audio-code.md`):
|
|
133
|
+
|
|
134
|
+
- Gameplay **nunca** referencia assets, solo nombres de evento
|
|
135
|
+
- Todo loop tiene un stop explícito, y se detiene al destruirse el objeto dueño
|
|
136
|
+
- Los RTPCs llegan normalizados y clampeados
|
|
137
|
+
- Un evento que falla (bank no cargado) **no rompe el frame**: degrada en silencio
|
|
138
|
+
- Registrar y des-registrar game objects es obligatorio — objetos filtrados son un leak clásico
|
|
139
|
+
|
|
140
|
+
## Profiling
|
|
141
|
+
|
|
142
|
+
El middleware trae el profiler, y es la única fuente de verdad sobre lo que pasa en runtime:
|
|
143
|
+
|
|
144
|
+
- **Voces activas** vs el límite configurado, y qué se está descartando
|
|
145
|
+
- **CPU de audio** contra el 5-10% del frame
|
|
146
|
+
- **Memoria** por bank y total
|
|
147
|
+
- **Valores de RTPC en vivo** — sirve para descubrir que el código manda un rango equivocado
|
|
148
|
+
- **Árbol de contribución de voz** — por qué un sonido se oye más o menos de lo esperado
|
|
149
|
+
- Capturas exportables para análisis offline
|
|
150
|
+
|
|
151
|
+
Para profilear objetos de juego hay que tener la captura activa en la vista de Game Object antes de disparar los eventos, o no se registra nada.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Proceso
|
|
156
|
+
|
|
157
|
+
1. **Definir la jerarquía** por sistema de gameplay, con work units separados
|
|
158
|
+
2. **Crear los ShareSets** de atenuación y conversión antes de los objetos, para que se reusen
|
|
159
|
+
3. **Autorear los eventos** desde la event list de `/audio-spec`, uno por acción
|
|
160
|
+
4. **Definir Switch/State/RTPC** con su alcance correcto, rango y default
|
|
161
|
+
5. **Diseñar los buses** desde `/audio-mix`
|
|
162
|
+
6. **Definir los banks** y qué va en streaming
|
|
163
|
+
7. **Documentar el contrato** para el equipo de código
|
|
164
|
+
8. **Escribir** — preguntar "¿Escribo la arquitectura a `<path>`?" antes de usar Write
|
|
165
|
+
|
|
166
|
+
### Nota sobre automatización
|
|
167
|
+
|
|
168
|
+
Wwise expone WAAPI y FMOD tiene su API de scripting, así que la autoría en batch es automatizable. Hay servidores MCP de terceros que exponen WAAPI a un agente, pero al momento de escribir esto el más completo se declara **EXPERIMENTAL y explícitamente no recomendado para proyectos de producción**. Si querés explorarlo, hacelo en un proyecto de prueba, nunca sobre el proyecto real.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Verdict
|
|
173
|
+
|
|
174
|
+
- **READY** — jerarquía por sistema, ShareSets reusados, cada parámetro con el alcance correcto, banks definidos, contrato documentado
|
|
175
|
+
- **CONCERNS** — la estructura existe pero hay ShareSets duplicados, parámetros con alcance equivocado (State donde va Switch), o banks sin estrategia de carga
|
|
176
|
+
- **BLOCKED** — falta la event list de `/audio-spec` o la estructura de buses de `/audio-mix`
|
|
177
|
+
|
|
178
|
+
## Anti-patterns
|
|
179
|
+
|
|
180
|
+
> → Read references/anti-patterns.md
|
|
181
|
+
|
|
182
|
+
## Next steps
|
|
183
|
+
|
|
184
|
+
- `/audio-audit` para profilear voces, CPU y memoria en runtime
|
|
185
|
+
- `/adaptive-music` si falta la matriz de transiciones de música
|
|
186
|
+
- `/audio-spec` si la event list no existe todavía
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Middleware Integration — Anti-patterns
|
|
2
|
+
|
|
3
|
+
## Un evento por sonido
|
|
4
|
+
|
|
5
|
+
500 eventos donde alcanzaban 40 con Switches. El código termina conociendo el nombre de cada asset, que es exactamente lo que el middleware venía a evitar. Un evento por *acción de gameplay*.
|
|
6
|
+
|
|
7
|
+
## ShareSet por objeto
|
|
8
|
+
|
|
9
|
+
Cada sonido con su propia atenuación y su propia conversión. Cambiar el rango de audición del juego pasa de ser un cambio a ser 300. Definir los ShareSets primero y reusarlos.
|
|
10
|
+
|
|
11
|
+
## State donde va Switch
|
|
12
|
+
|
|
13
|
+
Usar un State global para la superficie bajo el personaje produce el bug clásico: todos los personajes caminan sobre la superficie del jugador. Propiedad de objeto = Switch.
|
|
14
|
+
|
|
15
|
+
## Switch sin default
|
|
16
|
+
|
|
17
|
+
Si el Switch no está seteado cuando llega el evento, el sonido no suena, y el bug es invisible hasta que alguien nota el silencio. Todo Switch group con default.
|
|
18
|
+
|
|
19
|
+
## Random puro en vez de shuffle
|
|
20
|
+
|
|
21
|
+
Random repite: con 4 variantes hay 25% de escuchar la misma dos veces seguidas. Shuffle/round-robin excluyendo la última.
|
|
22
|
+
|
|
23
|
+
## RTPC sin clamp del lado del código
|
|
24
|
+
|
|
25
|
+
Un valor de salud que llega en 150 cuando el RTPC espera 0-100 deja el parámetro pegado al extremo. Normalizar y clampear en el boundary de gameplay.
|
|
26
|
+
|
|
27
|
+
## Loops sin stop explícito
|
|
28
|
+
|
|
29
|
+
La fuente número uno de audio colgado. Todo sonido que loopea necesita un stop, y tiene que detenerse cuando el objeto dueño se destruye.
|
|
30
|
+
|
|
31
|
+
## Game objects sin des-registrar
|
|
32
|
+
|
|
33
|
+
Cada enemigo muerto deja su game object registrado. A las dos horas de juego hay miles. Leak clásico, y arruina el profiling.
|
|
34
|
+
|
|
35
|
+
## Todo en un bank
|
|
36
|
+
|
|
37
|
+
Un bank monolítico se carga entero aunque el nivel use el 10%. Y cualquier cambio obliga a redistribuirlo completo. Init + Global + por nivel + por personaje.
|
|
38
|
+
|
|
39
|
+
## SFX en streaming
|
|
40
|
+
|
|
41
|
+
Un sonido corto y frecuente en streaming llega tarde y genera accesos a disco. Streaming es para música, diálogo largo y ambientes.
|
|
42
|
+
|
|
43
|
+
## Música en memoria
|
|
44
|
+
|
|
45
|
+
Se come el budget de audio entero. Streaming con prefetch.
|
|
46
|
+
|
|
47
|
+
## Fallo de evento que rompe el frame
|
|
48
|
+
|
|
49
|
+
Un bank no cargado tira una excepción que llega a gameplay. El audio degrada en silencio, siempre.
|
|
50
|
+
|
|
51
|
+
## Jerarquía por tipo de sonido
|
|
52
|
+
|
|
53
|
+
`Impacts/` con todos los impactos del juego mezclados no se navega ni se mezcla. Organizar por sistema de gameplay.
|
|
54
|
+
|
|
55
|
+
## Un solo work unit
|
|
56
|
+
|
|
57
|
+
Dos personas trabajando producen conflictos de merge irresolubles en un archivo binario o XML gigante. Work units por sistema.
|
|
58
|
+
|
|
59
|
+
## Confiar en el editor en vez del profiler
|
|
60
|
+
|
|
61
|
+
Disparar eventos a mano desde el editor no dice nada sobre voces activas, CPU o memoria en gameplay real. El profiler es la única fuente de verdad.
|
|
62
|
+
|
|
63
|
+
## Profilear game objects sin activar la captura
|
|
64
|
+
|
|
65
|
+
Sin la captura activa en la vista de Game Object antes de disparar los eventos, no se registra nada y parece que el sistema no funciona.
|
|
66
|
+
|
|
67
|
+
## Automatizar sobre el proyecto de producción
|
|
68
|
+
|
|
69
|
+
Las herramientas de autoría en batch vía WAAPI son potentes y las de terceros están mayormente en estado experimental. Probar en un proyecto descartable; el proyecto real no es el lugar.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: midi-compose
|
|
3
|
+
description: "Genera archivos MIDI (.mid) desde un spec JSON con scripts Python stdlib-only. Sin dependencias: escribe el Standard MIDI File binario directo. Usar para: exportar una composicion a MIDI, bocetar stems, generar loops o capas para middleware."
|
|
4
|
+
category: "audio"
|
|
5
|
+
argument-hint: "[spec-path | new | validate]"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Write, Edit, Bash
|
|
8
|
+
---
|
|
9
|
+
# midi-compose — Composición a MIDI
|
|
10
|
+
|
|
11
|
+
Baja decisiones musicales a un `.mid` real. Las decisiones las toma `/music-composition`; esta skill las escribe.
|
|
12
|
+
|
|
13
|
+
**Sin dependencias.** El Standard MIDI File es un formato binario documentado y `scripts/compose_midi.py` lo emite con la stdlib de Python. No hay que instalar `midiutil`, `music21` ni nada. Corre en la máquina del usuario tal como se copia.
|
|
14
|
+
|
|
15
|
+
## Alcance
|
|
16
|
+
|
|
17
|
+
**Sí:** escribir `.mid` multipista con tempo, compás, tonalidad, programas General MIDI, velocities y percusión.
|
|
18
|
+
|
|
19
|
+
**No:** renderizar a audio (requiere FluidSynth + soundfonts, dependencias de sistema fuera de scope), tomar decisiones musicales (→ `/music-composition`), notación.
|
|
20
|
+
|
|
21
|
+
## Cuándo usar
|
|
22
|
+
|
|
23
|
+
- Ya hay material decidido (progresión, melodía, groove) y hace falta el archivo
|
|
24
|
+
- Bocetar stems para un sistema de capas de `/adaptive-music`
|
|
25
|
+
- Generar variantes de un tema por transformación (aumentación, cambio de modo)
|
|
26
|
+
- Entregar MIDI a un compositor o importarlo a un DAW/middleware
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Modos
|
|
31
|
+
|
|
32
|
+
| Modo | Qué hace |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `spec-path` | Genera el `.mid` desde un spec JSON existente |
|
|
35
|
+
| `new` | Arma el spec desde cero a partir de material musical, y después genera |
|
|
36
|
+
| `validate` | Solo valida el spec y reporta, sin escribir nada |
|
|
37
|
+
|
|
38
|
+
## Workflow
|
|
39
|
+
|
|
40
|
+
### 1. Armar el spec
|
|
41
|
+
|
|
42
|
+
Un objeto JSON. Todos los tiempos están en **beats** (negras), no en ticks — el script convierte.
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"name": "Explore Calm",
|
|
47
|
+
"tempo": 84,
|
|
48
|
+
"ppq": 480,
|
|
49
|
+
"time_signature": [4, 4],
|
|
50
|
+
"key": "D",
|
|
51
|
+
"mode": "minor",
|
|
52
|
+
"tracks": [
|
|
53
|
+
{
|
|
54
|
+
"name": "Bed (strings)",
|
|
55
|
+
"channel": 0,
|
|
56
|
+
"program": 48,
|
|
57
|
+
"notes": [
|
|
58
|
+
{ "pitch": "D3", "start": 0, "duration": 4, "velocity": 62 }
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Hay un ejemplo completo de tres pistas en `templates/example-composition.json`.
|
|
66
|
+
|
|
67
|
+
**Campos del spec**
|
|
68
|
+
|
|
69
|
+
| Campo | Default | Notas |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `name` | `"untitled"` | Va al meta de nombre de secuencia |
|
|
72
|
+
| `tempo` | `120` | BPM, 1-600 |
|
|
73
|
+
| `ppq` | `480` | Ticks por negra, 24-32767 |
|
|
74
|
+
| `time_signature` | `[4, 4]` | Denominador potencia de 2 |
|
|
75
|
+
| `key` / `mode` | `"C"` / `"major"` | Solo para el meta de tonalidad; no transpone |
|
|
76
|
+
| `tracks[].channel` | índice del track | 0-15, único por track |
|
|
77
|
+
| `tracks[].program` | `0` | Programa General MIDI 0-127 |
|
|
78
|
+
| `tracks[].drums` | `false` | Declaralo en el track del canal 9 |
|
|
79
|
+
| `notes[].pitch` | — | `60`, `"C4"`, `"F#3"`, `"Bb5"`. C4 = 60 |
|
|
80
|
+
| `notes[].start` | `0` | En beats desde el inicio |
|
|
81
|
+
| `notes[].duration` | `1` | En beats, > 0 |
|
|
82
|
+
| `notes[].velocity` | `90` | 1-127 |
|
|
83
|
+
|
|
84
|
+
### 2. Validar antes de escribir
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python skills/midi-compose/scripts/compose_midi.py spec.json --validate
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Salida `[PASS]` con el resumen, más `[WARN]` por cada problema no fatal. Un `[FAIL]` nombra el track y la nota exactos.
|
|
91
|
+
|
|
92
|
+
### 3. Generar
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python skills/midi-compose/scripts/compose_midi.py spec.json -o salida.mid
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Antes de escribir el `.mid` o el spec a disco, preguntar al usuario "¿Escribo esto a `<path>`?" — sobre todo si el archivo ya existe.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Límites de MIDI que importan
|
|
103
|
+
|
|
104
|
+
- **16 canales.** El canal 9 (10 en base 1) está reservado a percusión por General MIDI, así que quedan **15 pistas melódicas**. Si necesitás más, son varios archivos.
|
|
105
|
+
- **Un programa por canal a la vez.** Cambiar de instrumento a mitad de pista pide otro canal.
|
|
106
|
+
- **Velocity no es volumen.** Es intensidad de ataque; el sampler decide qué hacer con ella.
|
|
107
|
+
- **MIDI no lleva timbre.** El programa GM es una sugerencia; cómo suena depende del soundfont o del instrumento del DAW.
|
|
108
|
+
- **`pitch` en el canal de percusión es el instrumento**, no una altura: 36 = bombo, 38 = redoblante, 42 = hi-hat cerrado, 46 = hi-hat abierto.
|
|
109
|
+
|
|
110
|
+
## Feel y microtiming
|
|
111
|
+
|
|
112
|
+
El spec acepta `start` fraccional, así que el feel se escribe directo: para atrasar un backbeat 15 ms a 84 BPM, `15 / (60000 / 84 / 1) ≈ 0.021` beats. Un desvío de 10-30 ms es feel; arriba de ~50 ms se percibe como error. Ver `references/smf-format.md` para la relación tick/ms.
|
|
113
|
+
|
|
114
|
+
## Formato
|
|
115
|
+
|
|
116
|
+
> → Read references/smf-format.md para la estructura binaria del SMF, el encoding VLQ y la tabla de programas GM más usados
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Verdict
|
|
121
|
+
|
|
122
|
+
- **COMPLETE** — el `.mid` se escribió y la verificación de estructura pasa
|
|
123
|
+
- **CONCERNS** — se escribió pero hay `[WARN]`: contenido melódico en el canal de percusión, o pistas cerca del límite de 15
|
|
124
|
+
- **FAIL** — el spec no valida; el mensaje nombra el track y la nota
|
|
125
|
+
|
|
126
|
+
Verificación rápida de que el archivo es un SMF:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
python -c "print(open('salida.mid','rb').read(4) == b'MThd')" # True
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Next steps
|
|
133
|
+
|
|
134
|
+
- `/music-composition` si el material musical todavía no está decidido
|
|
135
|
+
- `/adaptive-music` para armar el sistema de capas que consume estos stems
|
|
136
|
+
- `/middleware-integration` para importar los segmentos a Wwise/FMOD
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Standard MIDI File — formato binario
|
|
2
|
+
|
|
3
|
+
Referencia de lo que emite `scripts/compose_midi.py`. Útil para debuggear un `.mid` a mano o extender el script.
|
|
4
|
+
|
|
5
|
+
## Estructura general
|
|
6
|
+
|
|
7
|
+
Un SMF es una secuencia de chunks. Cada chunk: 4 bytes de tipo + 4 bytes de longitud (big-endian) + cuerpo.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MThd <len=6> <format:2> <ntrks:2> <division:2>
|
|
11
|
+
MTrk <len> <eventos...>
|
|
12
|
+
MTrk <len> <eventos...>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Formatos:**
|
|
16
|
+
|
|
17
|
+
| Formato | Qué es |
|
|
18
|
+
|---|---|
|
|
19
|
+
| 0 | Un solo track con todo mezclado |
|
|
20
|
+
| 1 | Múltiples tracks simultáneos; el track 0 es el conductor (tempo/compás) |
|
|
21
|
+
| 2 | Tracks independientes, secuenciales. Casi no se usa |
|
|
22
|
+
|
|
23
|
+
El script emite **formato 1**: track 0 con tempo, compás y tonalidad sin notas, y un `MTrk` por pista musical. Es lo que esperan los DAWs.
|
|
24
|
+
|
|
25
|
+
**División** — con el bit alto en 0, es *ticks por negra* (PPQ). 480 es el valor estándar de la industria y divide bien tresillos (160) y semicorcheas (120).
|
|
26
|
+
|
|
27
|
+
## Delta-times y VLQ
|
|
28
|
+
|
|
29
|
+
Cada evento arranca con un delta-time: cuántos ticks pasaron desde el evento anterior *en ese track*. Se codifica como Variable-Length Quantity: 7 bits de datos por byte, MSB = 1 en todos los bytes menos el último.
|
|
30
|
+
|
|
31
|
+
| Valor | Bytes |
|
|
32
|
+
|---|---|
|
|
33
|
+
| 0 | `00` |
|
|
34
|
+
| 127 | `7F` |
|
|
35
|
+
| 128 | `81 00` |
|
|
36
|
+
| 480 | `83 60` |
|
|
37
|
+
| 8192 | `C0 00` |
|
|
38
|
+
|
|
39
|
+
Un delta mal codificado desplaza todo lo que sigue: es la causa número uno de un `.mid` que "suena raro a partir de la mitad".
|
|
40
|
+
|
|
41
|
+
## Eventos de canal
|
|
42
|
+
|
|
43
|
+
`n` es el canal, 0-15.
|
|
44
|
+
|
|
45
|
+
| Evento | Bytes | Notas |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| Note Off | `8n kk vv` | `kk` nota, `vv` velocity de release |
|
|
48
|
+
| Note On | `9n kk vv` | `vv = 0` equivale a Note Off |
|
|
49
|
+
| Control Change | `Bn cc vv` | cc 7 = volumen, 10 = pan, 11 = expresión |
|
|
50
|
+
| Program Change | `Cn pp` | Instrumento GM |
|
|
51
|
+
| Pitch Bend | `En ll mm` | 14 bits, centro en `00 40` |
|
|
52
|
+
|
|
53
|
+
**Orden en el mismo tick:** el Note Off tiene que ir antes del Note On cuando se reataca la misma nota, o el sintetizador corta la nota nueva. El script ordena por `(tick, 0=off, 1=on)` justamente por esto.
|
|
54
|
+
|
|
55
|
+
## Meta eventos
|
|
56
|
+
|
|
57
|
+
Todos arrancan con `FF`, después tipo, después longitud en VLQ.
|
|
58
|
+
|
|
59
|
+
| Meta | Bytes | Contenido |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| Nombre de secuencia/track | `FF 03 <len> <texto>` | |
|
|
62
|
+
| Tempo | `FF 51 03 tttttt` | Microsegundos por negra = `60000000 / BPM` |
|
|
63
|
+
| Compás | `FF 58 04 nn dd cc bb` | `dd` = log2(denominador); `cc` = 24; `bb` = 8 |
|
|
64
|
+
| Tonalidad | `FF 59 02 sf mi` | `sf` sostenidos (+) o bemoles (−), signed; `mi` 0 mayor / 1 menor |
|
|
65
|
+
| End of Track | `FF 2F 00` | **Obligatorio** al final de cada `MTrk` |
|
|
66
|
+
|
|
67
|
+
Sin `End of Track` muchos lectores rechazan el archivo entero.
|
|
68
|
+
|
|
69
|
+
## Conversión de tiempo
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
ticks = beats × PPQ
|
|
73
|
+
ms_por_tick = 60000 / (BPM × PPQ)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
A 120 BPM con PPQ 480: 1 tick = 1.041 ms. A 84 BPM: 1 tick = 1.488 ms.
|
|
77
|
+
|
|
78
|
+
Para escribir un desvío de feel en beats: `beats = ms × BPM / 60000`.
|
|
79
|
+
|
|
80
|
+
## Programas General MIDI usados seguido
|
|
81
|
+
|
|
82
|
+
| # | Instrumento | # | Instrumento |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| 0 | Acoustic Grand Piano | 48 | String Ensemble 1 |
|
|
85
|
+
| 4 | Electric Piano 1 | 49 | String Ensemble 2 |
|
|
86
|
+
| 11 | Vibraphone | 52 | Choir Aahs |
|
|
87
|
+
| 19 | Church Organ | 56 | Trumpet |
|
|
88
|
+
| 24 | Nylon Guitar | 57 | Trombone |
|
|
89
|
+
| 25 | Steel Guitar | 60 | French Horn |
|
|
90
|
+
| 32 | Acoustic Bass | 68 | Oboe |
|
|
91
|
+
| 33 | Finger Bass | 71 | Clarinet |
|
|
92
|
+
| 40 | Violin | 73 | Flute |
|
|
93
|
+
| 42 | Cello | 89 | Pad 2 (warm) |
|
|
94
|
+
| 46 | Orchestral Harp | 94 | Pad 7 (halo) |
|
|
95
|
+
|
|
96
|
+
## Percusión (canal 9)
|
|
97
|
+
|
|
98
|
+
El `pitch` es el instrumento, no la altura.
|
|
99
|
+
|
|
100
|
+
| # | Instrumento | # | Instrumento |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| 35 | Acoustic Bass Drum | 44 | Pedal Hi-Hat |
|
|
103
|
+
| 36 | Bass Drum 1 | 46 | Open Hi-Hat |
|
|
104
|
+
| 38 | Acoustic Snare | 49 | Crash Cymbal 1 |
|
|
105
|
+
| 39 | Hand Clap | 51 | Ride Cymbal 1 |
|
|
106
|
+
| 40 | Electric Snare | 54 | Tambourine |
|
|
107
|
+
| 41 | Low Floor Tom | 56 | Cowbell |
|
|
108
|
+
| 42 | Closed Hi-Hat | 76 | Hi Wood Block |
|
|
109
|
+
|
|
110
|
+
## Verificar un .mid a mano
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
python - <<'EOF'
|
|
114
|
+
import struct
|
|
115
|
+
data = open("salida.mid", "rb").read()
|
|
116
|
+
assert data[:4] == b"MThd"
|
|
117
|
+
length, fmt, ntrks, div = struct.unpack(">IHHH", data[4:14])
|
|
118
|
+
print("format", fmt, "| tracks", ntrks, "| ppq", div)
|
|
119
|
+
pos, seen = 14, 0
|
|
120
|
+
while pos < len(data):
|
|
121
|
+
name, size = data[pos:pos+4], struct.unpack(">I", data[pos+4:pos+8])[0]
|
|
122
|
+
body = data[pos+8:pos+8+size]
|
|
123
|
+
assert name == b"MTrk" and body[-3:] == b"\xff\x2f\x00", f"track {seen} malformado"
|
|
124
|
+
pos += 8 + size
|
|
125
|
+
seen += 1
|
|
126
|
+
assert seen == ntrks and pos == len(data)
|
|
127
|
+
print("SMF valido")
|
|
128
|
+
EOF
|
|
129
|
+
```
|