claude-code-arcane 2.1.0 → 2.2.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +5 -4
  3. package/agents/audio/audio-director.md +90 -0
  4. package/agents/audio/audio-programmer.md +97 -0
  5. package/agents/audio/audio-qa.md +95 -0
  6. package/agents/audio/composer.md +88 -0
  7. package/agents/audio/technical-sound-designer.md +90 -0
  8. package/agents/audio/voice-director.md +96 -0
  9. package/docs/SKILLS-CATALOG.md +22 -2
  10. package/docs/USER-GUIDE.md +2 -2
  11. package/package.json +1 -1
  12. package/profiles/audio.yaml +45 -0
  13. package/rules/gamedev/audio-code.md +45 -0
  14. package/skills/adaptive-music/SKILL.md +171 -0
  15. package/skills/adaptive-music/references/anti-patterns.md +53 -0
  16. package/skills/audio-audit/SKILL.md +164 -0
  17. package/skills/audio-audit/references/anti-patterns.md +65 -0
  18. package/skills/audio-audit/scripts/check_assets.py +208 -0
  19. package/skills/audio-bible/SKILL.md +119 -0
  20. package/skills/audio-bible/references/anti-patterns.md +39 -0
  21. package/skills/audio-mix/SKILL.md +151 -0
  22. package/skills/audio-mix/references/anti-patterns.md +65 -0
  23. package/skills/audio-spec/SKILL.md +107 -0
  24. package/skills/audio-spec/references/anti-patterns.md +45 -0
  25. package/skills/middleware-integration/SKILL.md +186 -0
  26. package/skills/middleware-integration/references/anti-patterns.md +69 -0
  27. package/skills/midi-compose/SKILL.md +136 -0
  28. package/skills/midi-compose/references/smf-format.md +129 -0
  29. package/skills/midi-compose/scripts/compose_midi.py +326 -0
  30. package/skills/midi-compose/templates/example-composition.json +69 -0
  31. package/skills/music-composition/SKILL.md +106 -0
  32. package/skills/music-composition/references/form.md +57 -0
  33. package/skills/music-composition/references/game-music.md +82 -0
  34. package/skills/music-composition/references/harmony.md +82 -0
  35. package/skills/music-composition/references/melody.md +48 -0
  36. package/skills/music-composition/references/orchestration.md +69 -0
  37. package/skills/music-composition/references/rhythm-groove.md +49 -0
  38. package/skills/procedural-audio/SKILL.md +157 -0
  39. package/skills/procedural-audio/references/anti-patterns.md +45 -0
  40. package/skills/sfx-design/SKILL.md +130 -0
  41. package/skills/sfx-design/references/anti-patterns.md +53 -0
  42. package/skills/spatial-audio/SKILL.md +139 -0
  43. package/skills/spatial-audio/references/anti-patterns.md +53 -0
  44. package/skills/ui-sound-design/SKILL.md +133 -0
  45. package/skills/ui-sound-design/references/anti-patterns.md +57 -0
  46. package/skills/voice-pipeline/SKILL.md +150 -0
  47. package/skills/voice-pipeline/references/anti-patterns.md +69 -0
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: audio-spec
3
+ description: "SFX spec sheets y audio event lists: trigger, prioridad, concurrencia, cooldown, variaciones, propiedades espaciales. Usar para: especificar sonidos, event list por sistema, brief para sound designer."
4
+ category: "audio"
5
+ argument-hint: "[system-name | all | events]"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Write, Edit
8
+ ---
9
+ # audio-spec — SFX Specs & Event Lists
10
+
11
+ Convierte dirección sonora en especificaciones que un sound designer externo puede producir sin más briefing, y en event lists que un programador puede implementar sin adivinar.
12
+
13
+ Dos entregables distintos y complementarios:
14
+ - **Spec sheet** — describe *el sonido*: cómo suena, cuánto dura, cuántas variantes
15
+ - **Event list** — describe *el contrato*: qué lo dispara, con qué prioridad, cuántas instancias, qué parámetros recibe
16
+
17
+ ## Cuándo usar
18
+
19
+ - Después de `/audio-bible` (la dirección tiene que existir primero)
20
+ - Cuando vas a tercerizar producción de SFX y necesitás un brief cerrado
21
+ - Cuando el código necesita saber qué eventos postear antes de que existan los assets
22
+ - Antes de `/middleware-integration` — la event list es su input
23
+
24
+ ## Modos
25
+
26
+ | Modo | Qué hace |
27
+ |---|---|
28
+ | `system-name` | Especifica un sistema concreto: spec sheets + event list de, por ejemplo, `combat` |
29
+ | `all` | Barre todos los sistemas del proyecto, uno por vez |
30
+ | `events` | Solo la event list, sin spec sheets. Para cuando el código necesita el contrato antes de que existan los assets |
31
+
32
+ ## Input
33
+
34
+ 1. Leer audio bible (paleta, frequency allocation, mix hierarchy, standards). Si no existe, avisar y ofrecer `/audio-bible` primero
35
+ 2. Identificar el sistema a especificar (combate, locomoción, UI, ambiente, props...) o `all`
36
+ 3. Leer el código/GDD del sistema para extraer los momentos que piden sonido
37
+ 4. Preguntar al usuario: nivel de detalle (spec completo vs event list sola) y si hay assets existentes que reusar
38
+
39
+ ---
40
+
41
+ ## Spec sheet — campos obligatorios
42
+
43
+ Cada sonido lleva estos campos. Un campo vacío es una decisión que alguien va a tomar por vos:
44
+
45
+ | Campo | Qué define | Ejemplo |
46
+ |---|---|---|
47
+ | **ID** | Nombre canónico, matchea naming del bible | `sfx_weapon_shotgun_fire` |
48
+ | **Descripción** | Qué es, en una línea funcional | Disparo de escopeta de doble caño, seco, sin cola de reverb |
49
+ | **Referencias** | 1-3 sonidos concretos con qué tomar de cada uno | — |
50
+ | **Carácter frecuencial** | Dónde vive, contra la allocation del bible | Sub-punch 50-70 Hz + crack 3-5 kHz, vacío en mids |
51
+ | **Duración** | Rango, no valor único | 180-260 ms |
52
+ | **Envolvente** | Attack / body / tail explícitos | Attack <5 ms, body 60 ms, tail 120 ms |
53
+ | **Rango de volumen** | Nivel relativo a la categoría | −6 dB a −3 dB del bus SFX |
54
+ | **Propiedades espaciales** | 2D/3D, curva de atenuación, min/max distance | 3D, 2-40 m, log |
55
+ | **Variaciones** | Cuántas variantes y qué randomización | 5 variantes, pitch ±3%, volumen ±1.5 dB |
56
+ | **Prioridad** | Qué se cae primero bajo presión de voces | Alta (feedback de acción del jugador) |
57
+
58
+ ## Event list — campos obligatorios
59
+
60
+ | Campo | Qué define |
61
+ |---|---|
62
+ | **Evento** | Nombre que postea el código |
63
+ | **Trigger** | Condición exacta de gameplay que lo dispara |
64
+ | **Sonidos** | Qué IDs de spec resuelve (uno o un container) |
65
+ | **Concurrencia** | Máximo de instancias simultáneas + qué hacer al exceder (drop oldest / drop newest / reject) |
66
+ | **Cooldown** | Mínimo entre disparos, para eventos que pueden spamearse |
67
+ | **Parámetros** | RTPCs que recibe, con rango normalizado |
68
+ | **Stop condition** | Cómo termina: one-shot, loop con stop explícito, o fade |
69
+
70
+ **Concurrencia y cooldown no son opcionales.** Son la diferencia entre un sistema que sobrevive 40 enemigos en pantalla y uno que satura el voice budget. Todo evento que puede dispararse más de una vez por frame necesita ambos.
71
+
72
+ ---
73
+
74
+ ## Proceso
75
+
76
+ 1. **Barrer el sistema** — listar cada momento que pide sonido, incluyendo los negativos (dónde el silencio es la decisión)
77
+ 2. **Agrupar** — sonidos que comparten carácter van juntos; identificar qué puede ser variación de un mismo asset en vez de asset nuevo
78
+ 3. **Draftar** specs y event list del grupo
79
+ 4. **Presentar** al usuario para review
80
+ 5. **Escribir** — preguntar "¿Escribo el spec de `<sistema>` a `<path>`?" antes de usar Write
81
+
82
+ No batchear sistemas: cerrar uno antes de abrir el siguiente.
83
+
84
+ ### Reducción de assets
85
+
86
+ Antes de especificar N sonidos nuevos, preguntar si el caso se resuelve con variación o con síntesis. El caso canónico: 20 samples × 6 superficies × 3 intensidades = 360 archivos (~180 MB) vs footsteps procedurales (~50 KB) con variación infinita. Si el sistema es de alta frecuencia y muchas dimensiones, derivar a `/procedural-audio` antes de escribir el spec.
87
+
88
+ ---
89
+
90
+ ## Verdict
91
+
92
+ Por sistema especificado:
93
+
94
+ - **READY** — todo sonido tiene los 10 campos, todo evento tiene concurrencia y cooldown. Un externo puede producir y un programador puede implementar
95
+ - **CONCERNS** — hay campos vacíos o eventos sin límites; listar cuáles
96
+ - **BLOCKED** — falta el audio bible, o el sistema de gameplay no está definido lo suficiente para saber qué momentos existen
97
+
98
+ ## Anti-patterns
99
+
100
+ > → Read references/anti-patterns.md
101
+
102
+ ## Next steps
103
+
104
+ - `/middleware-integration` para implementar la event list en Wwise/FMOD
105
+ - `/procedural-audio` para los sistemas de alta frecuencia que no conviene samplear
106
+ - `/sfx-design` para profundizar el diseño de los sonidos hero
107
+ - `/audio-audit` para verificar los assets entregados contra el spec
@@ -0,0 +1,45 @@
1
+ # Audio Spec — Anti-patterns
2
+
3
+ ## Spec sin números
4
+
5
+ "Impacto potente y satisfactorio" no es producible. Duración, envolvente, bandas de frecuencia y nivel relativo sí. Si el campo no tiene número o rango, no está especificado.
6
+
7
+ ## Duración como valor único
8
+
9
+ "200 ms" fuerza al sound designer a mentir o a pedir aprobación. Un rango (180-260 ms) le da lugar a que el sonido termine donde tiene que terminar.
10
+
11
+ ## Eventos sin concurrencia
12
+
13
+ El spec dice "sonido de casquillo al caer". Llegan 40 enemigos, 300 casquillos, el voice budget explota y el mix desaparece. Todo evento que puede dispararse más de una vez por frame necesita máximo de instancias y política de descarte.
14
+
15
+ ## Cooldown ausente en eventos de UI
16
+
17
+ Navegación rápida en un menú dispara el mismo tick 15 veces en 200 ms. Suena como un glitch. Cooldown mínimo, siempre.
18
+
19
+ ## Una variante
20
+
21
+ Un sonido sin variantes se vuelve audible como *sonido* en vez de como evento en unos 20 minutos de juego. Si el evento es frecuente, mínimo 4-5 variantes o randomización de pitch, y preferentemente ambas.
22
+
23
+ ## Pitch randomization exagerada
24
+
25
+ ±3% es variación. ±20% cambia el objeto: el arma parece otra arma. Rango chico y round-robin le gana a rango grande.
26
+
27
+ ## Specs 3D sin curva
28
+
29
+ Marcar "3D" y no definir min/max distance ni forma de la curva deja la atenuación en el default del middleware, que casi nunca es el correcto. El sonido se oye a 200 m o desaparece a 3 m.
30
+
31
+ ## Especificar antes de decidir procedural
32
+
33
+ Escribir 360 spec sheets de footsteps es trabajo perdido si la respuesta correcta era síntesis. Evaluar el eje frecuencia × dimensiones antes de especificar.
34
+
35
+ ## Event list sin stop condition
36
+
37
+ Los loops son la fuente número uno de audio colgado. Todo sonido que loopea necesita decir explícitamente quién lo para y qué pasa si el objeto dueño se destruye primero.
38
+
39
+ ## Prioridades todas iguales
40
+
41
+ Si todo es prioridad alta, el voice limiter descarta al azar y se cae el feedback de la acción del jugador antes que un ambiente lejano. Las prioridades solo sirven si están diferenciadas.
42
+
43
+ ## IDs que no siguen el naming del bible
44
+
45
+ `shotgun2_FINAL_v3` rompe búsqueda, batch processing y automatización de banks. El ID sale del pattern del bible, sin excepciones.
@@ -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
+ ```