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.
- package/CHANGELOG.md +7 -0
- package/README.md +5 -4
- 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/docs/SKILLS-CATALOG.md +22 -2
- package/docs/USER-GUIDE.md +2 -2
- package/package.json +1 -1
- package/profiles/audio.yaml +45 -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/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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Verifica conformidad mecanica de assets WAV contra los standards del proyecto.
|
|
3
|
+
|
|
4
|
+
Solo stdlib: usa el modulo `wave` para leer cabeceras y frames. Los chequeos que
|
|
5
|
+
necesitarian una herramienta externa se reportan como [SKIP], no rompen la corrida.
|
|
6
|
+
|
|
7
|
+
Uso:
|
|
8
|
+
python check_assets.py assets/audio
|
|
9
|
+
python check_assets.py assets/audio --rate 48000 --bits 16
|
|
10
|
+
python check_assets.py assets/audio --mono-3d --naming "^(sfx|vo|mus)_"
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import struct
|
|
19
|
+
import sys
|
|
20
|
+
import wave
|
|
21
|
+
|
|
22
|
+
# Umbrales de deteccion. Explicitos para que se puedan discutir.
|
|
23
|
+
LEADING_SILENCE_MS = 10.0 # silencio inicial que ya se percibe como lag
|
|
24
|
+
SILENCE_FLOOR = 0.002 # ~-54 dBFS: por debajo se considera silencio
|
|
25
|
+
DC_OFFSET_LIMIT = 0.01 # 1% de fondo de escala
|
|
26
|
+
CLIP_SAMPLE_LIMIT = 3 # muestras en fondo de escala para llamarlo clipping
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Finding:
|
|
30
|
+
__slots__ = ("path", "severity", "check", "detail")
|
|
31
|
+
|
|
32
|
+
def __init__(self, path: str, severity: str, check: str, detail: str):
|
|
33
|
+
self.path = path
|
|
34
|
+
self.severity = severity # FAIL | WARN | SKIP
|
|
35
|
+
self.check = check
|
|
36
|
+
self.detail = detail
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def read_samples(handle: wave.Wave_read, limit: int | None = None) -> list[int]:
|
|
40
|
+
"""Devuelve las muestras del primer canal, normalizadas a int con signo."""
|
|
41
|
+
width = handle.getsampwidth()
|
|
42
|
+
channels = handle.getnchannels()
|
|
43
|
+
frames = handle.getnframes() if limit is None else min(limit, handle.getnframes())
|
|
44
|
+
raw = handle.readframes(frames)
|
|
45
|
+
|
|
46
|
+
if width == 1:
|
|
47
|
+
# 8-bit WAV es unsigned; centrar en 0
|
|
48
|
+
values = [byte - 128 for byte in raw]
|
|
49
|
+
elif width == 2:
|
|
50
|
+
values = list(struct.unpack(f"<{len(raw) // 2}h", raw[: len(raw) // 2 * 2]))
|
|
51
|
+
elif width == 3:
|
|
52
|
+
values = []
|
|
53
|
+
for offset in range(0, len(raw) - 2, 3):
|
|
54
|
+
chunk = raw[offset:offset + 3]
|
|
55
|
+
value = int.from_bytes(chunk, "little", signed=True)
|
|
56
|
+
values.append(value)
|
|
57
|
+
elif width == 4:
|
|
58
|
+
values = list(struct.unpack(f"<{len(raw) // 4}i", raw[: len(raw) // 4 * 4]))
|
|
59
|
+
else:
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
return values[::channels] if channels > 1 else values
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def full_scale(width: int) -> float:
|
|
66
|
+
return float(2 ** (8 * width - 1))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def check_file(path: str, args: argparse.Namespace) -> list[Finding]:
|
|
70
|
+
findings: list[Finding] = []
|
|
71
|
+
name = os.path.basename(path)
|
|
72
|
+
|
|
73
|
+
if args.naming and not re.search(args.naming, name):
|
|
74
|
+
findings.append(Finding(path, "FAIL", "naming",
|
|
75
|
+
f"no matchea {args.naming!r}"))
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
with wave.open(path, "rb") as handle:
|
|
79
|
+
rate = handle.getframerate()
|
|
80
|
+
width = handle.getsampwidth()
|
|
81
|
+
channels = handle.getnchannels()
|
|
82
|
+
frames = handle.getnframes()
|
|
83
|
+
duration = frames / rate if rate else 0.0
|
|
84
|
+
|
|
85
|
+
if args.rate and rate != args.rate:
|
|
86
|
+
findings.append(Finding(path, "FAIL", "sample-rate",
|
|
87
|
+
f"{rate} Hz, se esperaba {args.rate} Hz"))
|
|
88
|
+
if args.bits and width * 8 != args.bits:
|
|
89
|
+
findings.append(Finding(path, "FAIL", "bit-depth",
|
|
90
|
+
f"{width * 8}-bit, se esperaba {args.bits}-bit"))
|
|
91
|
+
if args.mono_3d and channels != 1:
|
|
92
|
+
findings.append(Finding(
|
|
93
|
+
path, "FAIL", "channels",
|
|
94
|
+
f"{channels} canales; una fuente 3D tiene que ser mono o el "
|
|
95
|
+
f"middleware la colapsa y se degrada la localizacion"))
|
|
96
|
+
if args.rate and rate and rate != args.rate and args.rate % rate and rate % args.rate:
|
|
97
|
+
findings.append(Finding(path, "WARN", "resample",
|
|
98
|
+
f"{rate} Hz no es multiplo ni divisor de "
|
|
99
|
+
f"{args.rate} Hz: el resampleo es mas costoso"))
|
|
100
|
+
if frames == 0:
|
|
101
|
+
findings.append(Finding(path, "FAIL", "empty", "0 frames"))
|
|
102
|
+
return findings
|
|
103
|
+
if args.max_duration and duration > args.max_duration:
|
|
104
|
+
findings.append(Finding(path, "WARN", "duration",
|
|
105
|
+
f"{duration:.2f} s > {args.max_duration:.2f} s"))
|
|
106
|
+
|
|
107
|
+
samples = read_samples(handle)
|
|
108
|
+
if not samples:
|
|
109
|
+
findings.append(Finding(path, "SKIP", "content",
|
|
110
|
+
f"{width * 8}-bit no soportado por el lector stdlib"))
|
|
111
|
+
return findings
|
|
112
|
+
|
|
113
|
+
scale = full_scale(width)
|
|
114
|
+
peak = max(abs(value) for value in samples)
|
|
115
|
+
|
|
116
|
+
clipped = sum(1 for value in samples if abs(value) >= scale - 1)
|
|
117
|
+
if clipped >= CLIP_SAMPLE_LIMIT:
|
|
118
|
+
findings.append(Finding(path, "FAIL", "clipping",
|
|
119
|
+
f"{clipped} muestras en fondo de escala"))
|
|
120
|
+
|
|
121
|
+
offset = sum(samples) / len(samples) / scale
|
|
122
|
+
if abs(offset) > DC_OFFSET_LIMIT:
|
|
123
|
+
findings.append(Finding(path, "WARN", "dc-offset",
|
|
124
|
+
f"{offset * 100:+.2f}% - produce clicks al concatenar"))
|
|
125
|
+
|
|
126
|
+
floor = SILENCE_FLOOR * scale
|
|
127
|
+
if peak < floor:
|
|
128
|
+
# Archivo enteramente silencioso: el chequeo de silencio inicial
|
|
129
|
+
# sobra, seria el mismo hallazgo dos veces.
|
|
130
|
+
findings.append(Finding(path, "FAIL", "silent", "el archivo es silencio"))
|
|
131
|
+
return findings
|
|
132
|
+
|
|
133
|
+
leading = 0
|
|
134
|
+
for value in samples:
|
|
135
|
+
if abs(value) > floor:
|
|
136
|
+
break
|
|
137
|
+
leading += 1
|
|
138
|
+
leading_ms = leading / rate * 1000
|
|
139
|
+
if leading_ms > LEADING_SILENCE_MS:
|
|
140
|
+
findings.append(Finding(
|
|
141
|
+
path, "WARN", "leading-silence",
|
|
142
|
+
f"{leading_ms:.1f} ms de silencio inicial - se percibe como lag de input"))
|
|
143
|
+
|
|
144
|
+
except wave.Error as error:
|
|
145
|
+
findings.append(Finding(path, "SKIP", "format",
|
|
146
|
+
f"no es un WAV PCM legible ({error})"))
|
|
147
|
+
except OSError as error:
|
|
148
|
+
findings.append(Finding(path, "FAIL", "io", str(error)))
|
|
149
|
+
|
|
150
|
+
return findings
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main(argv: list[str] | None = None) -> int:
|
|
154
|
+
parser = argparse.ArgumentParser(
|
|
155
|
+
description="Verifica conformidad de assets WAV contra los standards del proyecto.")
|
|
156
|
+
parser.add_argument("root", help="carpeta a auditar (recursivo)")
|
|
157
|
+
parser.add_argument("--rate", type=int, help="sample rate esperado, ej 48000")
|
|
158
|
+
parser.add_argument("--bits", type=int, choices=(8, 16, 24, 32),
|
|
159
|
+
help="bit depth esperado")
|
|
160
|
+
parser.add_argument("--mono-3d", action="store_true",
|
|
161
|
+
help="exigir mono (assets usados como fuentes 3D)")
|
|
162
|
+
parser.add_argument("--naming", help="regex que el nombre de archivo debe matchear")
|
|
163
|
+
parser.add_argument("--max-duration", type=float,
|
|
164
|
+
help="duracion maxima en segundos")
|
|
165
|
+
args = parser.parse_args(argv)
|
|
166
|
+
|
|
167
|
+
if not os.path.isdir(args.root):
|
|
168
|
+
print(f"[FAIL] no es una carpeta: {args.root}", file=sys.stderr)
|
|
169
|
+
return 1
|
|
170
|
+
|
|
171
|
+
paths = []
|
|
172
|
+
for directory, _subdirs, files in os.walk(args.root):
|
|
173
|
+
for name in sorted(files):
|
|
174
|
+
if name.lower().endswith(".wav"):
|
|
175
|
+
paths.append(os.path.join(directory, name))
|
|
176
|
+
|
|
177
|
+
if not paths:
|
|
178
|
+
print(f"[SKIP] no hay .wav en {args.root}")
|
|
179
|
+
return 0
|
|
180
|
+
|
|
181
|
+
findings: list[Finding] = []
|
|
182
|
+
for path in paths:
|
|
183
|
+
findings.extend(check_file(path, args))
|
|
184
|
+
|
|
185
|
+
fails = [f for f in findings if f.severity == "FAIL"]
|
|
186
|
+
warns = [f for f in findings if f.severity == "WARN"]
|
|
187
|
+
skips = [f for f in findings if f.severity == "SKIP"]
|
|
188
|
+
|
|
189
|
+
for group in (fails, warns, skips):
|
|
190
|
+
for finding in group:
|
|
191
|
+
relative = os.path.relpath(finding.path, args.root)
|
|
192
|
+
print(f"[{finding.severity}] {relative}: {finding.check} - {finding.detail}")
|
|
193
|
+
|
|
194
|
+
print()
|
|
195
|
+
print(f"{len(paths)} assets | {len(fails)} FAIL | {len(warns)} WARN | {len(skips)} SKIP")
|
|
196
|
+
|
|
197
|
+
if fails:
|
|
198
|
+
print("NON-COMPLIANT")
|
|
199
|
+
return 1
|
|
200
|
+
if warns:
|
|
201
|
+
print("CONCERNS")
|
|
202
|
+
return 0
|
|
203
|
+
print("COMPLIANT")
|
|
204
|
+
return 0
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
if __name__ == "__main__":
|
|
208
|
+
sys.exit(main())
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audio-bible
|
|
3
|
+
description: "Audio Bible authoring: sonic palette, emotional targets per game state, frequency allocation, mix hierarchy, audio standards. Usar para: direccion de audio, paleta sonora, identidad sonora del juego."
|
|
4
|
+
category: "audio"
|
|
5
|
+
argument-hint: "[full|core|standards]"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Write, Edit
|
|
8
|
+
---
|
|
9
|
+
# audio-bible — Sonic Identity Specification
|
|
10
|
+
|
|
11
|
+
Define la identidad sonora del juego. Toda produccion de audio se gatea contra este documento — es el equivalente auditivo de `/art-bible`.
|
|
12
|
+
|
|
13
|
+
## Cuándo usar
|
|
14
|
+
|
|
15
|
+
- Después de tener game concept aprobado, antes de producir un solo sonido
|
|
16
|
+
- Cuando necesitás estandarizar el criterio sonoro entre sound designers y compositores
|
|
17
|
+
- Antes de escribir specs de SFX (`/audio-spec`) o diseñar el sistema de música (`/adaptive-music`)
|
|
18
|
+
|
|
19
|
+
## Input
|
|
20
|
+
|
|
21
|
+
1. Leer game concept (elevator pitch, pilares, core fantasy) y art bible si existe
|
|
22
|
+
2. Preguntar al usuario: scope (full bible / core secciones 1-4 / solo standards), plataformas target, y referencias sonoras (juegos, films, discos)
|
|
23
|
+
3. Si ya existe audio bible: detectar secciones completas vs vacías, trabajar solo las incompletas
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Secciones del Audio Bible
|
|
28
|
+
|
|
29
|
+
### Core (secciones 1-4) — definen el lenguaje sonoro
|
|
30
|
+
|
|
31
|
+
| # | Sección | Qué define | Criterio de calidad |
|
|
32
|
+
|---|---------|-----------|-------------------|
|
|
33
|
+
| 1 | **Sonic Identity Statement** | Regla sonora de 1 línea + 2-3 principios de soporte | Resuelve cualquier ambigüedad sonora. Cada principio ancla en un pilar del juego |
|
|
34
|
+
| 2 | **Emotional Targets por Game State** | Qué debe sentir el jugador en exploración, combate, tensión, victoria, menú | Cada estado auditivamente distinto sin mirar la pantalla |
|
|
35
|
+
| 3 | **Sonic Palette** | Fuentes y texturas: acústico vs sintético, orgánico vs procesado, época/lugar, vocabulario tímbrico | Un sonido nuevo se puede aceptar o rechazar leyendo esto |
|
|
36
|
+
| 4 | **Frequency Allocation** | Qué categoría es dueña de qué banda — evita masking antes de que exista el mix | Diálogo, música, SFX y ambiente no compiten por la misma banda |
|
|
37
|
+
|
|
38
|
+
### Production (secciones 5-8) — reglas concretas
|
|
39
|
+
|
|
40
|
+
| # | Sección | Qué define |
|
|
41
|
+
|---|---------|-----------|
|
|
42
|
+
| 5 | **Mix Hierarchy** | Prioridad entre categorías, qué duckea a qué, qué nunca se pisa |
|
|
43
|
+
| 6 | **Music Direction** | Instrumentación, escalas/modos, tempo range, rol del silencio, estrategia adaptativa |
|
|
44
|
+
| 7 | **SFX Direction** | Filosofía de impacto, realismo vs estilización, tratamiento del feedback de UI, uso de variación |
|
|
45
|
+
| 8 | **Audio Standards** | Sample rate, bit depth, formatos, naming, targets de loudness, budgets de memoria y voces por plataforma |
|
|
46
|
+
|
|
47
|
+
### Reference (sección 9)
|
|
48
|
+
|
|
49
|
+
| # | Sección | Qué define |
|
|
50
|
+
|---|---------|-----------|
|
|
51
|
+
| 9 | **References & Prohibitions** | 3-5 referencias con qué tomar y qué evitar de cada una. Prohibiciones sonoras explícitas |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Frequency allocation — el punto que más se saltea
|
|
56
|
+
|
|
57
|
+
La mayoría de los mixes de juego se rompen porque nadie asignó bandas antes de producir. Definir dueño primario por banda evita rehacer assets:
|
|
58
|
+
|
|
59
|
+
| Banda | Dueño primario | Nota |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| Sub (20-60 Hz) | Impactos, explosiones | Reservado, no lo llenes de música |
|
|
62
|
+
| Low (60-250 Hz) | Música (bajo, kick), body de impactos | Zona de masking más frecuente |
|
|
63
|
+
| Low-mid (250-500 Hz) | Ambiente, body de props | Acumula barro si no se controla |
|
|
64
|
+
| Mid (500 Hz-2 kHz) | **Diálogo** — intocable | El resto cede acá |
|
|
65
|
+
| High-mid (2-6 kHz) | Feedback de UI, transientes, definición | Fatiga rápido; usar con precisión |
|
|
66
|
+
| High (6-20 kHz) | Aire, detalle, brillo | Primero en irse en compresión lossy |
|
|
67
|
+
|
|
68
|
+
Cuando un asset nuevo pelea una banda que no le corresponde, la solución es de diseño (cambiar la fuente), no de EQ.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Proceso
|
|
73
|
+
|
|
74
|
+
Para cada sección:
|
|
75
|
+
1. **Draftar** basándose en game concept, pilares y referencias
|
|
76
|
+
2. **Presentar** al usuario para review
|
|
77
|
+
3. **Escribir** a archivo inmediatamente después de aprobación — preguntar "¿Escribo esta sección a `<path>`?" antes de usar Write
|
|
78
|
+
|
|
79
|
+
No batchear — escribir cada sección aprobada antes de pasar a la siguiente.
|
|
80
|
+
|
|
81
|
+
### Conflictos
|
|
82
|
+
|
|
83
|
+
Si hay tensión entre dirección de audio y constraints técnicos (ej: música en stems para adaptatividad, pero el budget de memoria no da), surfacear el conflicto explícitamente con ambas posiciones. No resolver silenciosamente.
|
|
84
|
+
|
|
85
|
+
Si la dirección sonora conflictúa con accesibilidad (ej: información crítica solo por audio), surfacear y dejar que el usuario decida.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Principios
|
|
90
|
+
|
|
91
|
+
- El audio bible es un **documento de restricción**: narrows solution space en favor de coherencia sonora
|
|
92
|
+
- Cada sección debe conectar con los pilares del juego
|
|
93
|
+
- Específico > genérico: "dark and tense" no alcanza — decir la emoción exacta, la fuente tímbrica, el rango de frecuencia, un elemento sonoro que carga el mood
|
|
94
|
+
- Para cada referencia: qué tomar Y qué evitar
|
|
95
|
+
- El silencio es una decisión de diseño, no la ausencia de una — documentarlo
|
|
96
|
+
- Los standards deben ser producibles: un contractor externo debería poder entregar assets válidos sin briefing adicional
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Anti-patterns
|
|
101
|
+
|
|
102
|
+
> → Read references/anti-patterns.md for common mistakes to avoid
|
|
103
|
+
|
|
104
|
+
## Verdict
|
|
105
|
+
|
|
106
|
+
Al cerrar el documento, emitir un verdict por sección:
|
|
107
|
+
|
|
108
|
+
- **READY** — la sección resuelve ambigüedades y es accionable para producción
|
|
109
|
+
- **CONCERNS** — está escrita pero es genérica o no ancla en pilares; listar qué falta
|
|
110
|
+
- **BLOCKED** — falta input upstream (game concept, plataformas, budget) que no se puede inventar
|
|
111
|
+
|
|
112
|
+
Si alguna sección Core (1-4) queda en BLOCKED, el bible no habilita producción todavía.
|
|
113
|
+
|
|
114
|
+
## Next steps
|
|
115
|
+
|
|
116
|
+
- `/audio-spec` para bajar la dirección a spec sheets de SFX y event lists
|
|
117
|
+
- `/adaptive-music` para diseñar el sistema de música contra los emotional targets de la sección 2
|
|
118
|
+
- `/audio-mix` para implementar la mix hierarchy de la sección 5
|
|
119
|
+
- `/audio-audit` para verificar assets producidos contra los standards de la sección 8
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Audio Bible — Anti-patterns
|
|
2
|
+
|
|
3
|
+
## Dirección vacía
|
|
4
|
+
|
|
5
|
+
**Síntoma:** "El juego suena oscuro e inmersivo."
|
|
6
|
+
|
|
7
|
+
Cualquier sonido pasa ese filtro, así que no filtra nada. Una statement útil excluye: "Todo suena grabado dentro de una habitación de concreto — nada nace limpio, todo tiene el cuarto encima." Eso rechaza el 90% de una library.
|
|
8
|
+
|
|
9
|
+
## Paleta sin fuentes
|
|
10
|
+
|
|
11
|
+
Listar adjetivos (cálido, agresivo, etéreo) sin decir de dónde sale el sonido deja la decisión al sound designer, que es exactamente lo que el bible debería resolver. Nombrar fuentes: instrumentos, materiales, técnicas de grabación, cadenas de procesamiento.
|
|
12
|
+
|
|
13
|
+
## Frequency allocation ausente
|
|
14
|
+
|
|
15
|
+
Se produce todo, se llega al mix, y diálogo compite con música y SFX en 500 Hz-2 kHz. La solución tardía es EQ quirúrgico que adelgaza todo. La solución temprana es asignar bandas antes de producir.
|
|
16
|
+
|
|
17
|
+
## Copiar la referencia entera
|
|
18
|
+
|
|
19
|
+
"Suena como Hollow Knight" sin decir qué se toma produce pastiche. Para cada referencia: qué tomar y qué evitar explícitamente.
|
|
20
|
+
|
|
21
|
+
## Emotional targets que no se distinguen
|
|
22
|
+
|
|
23
|
+
Si exploración y tensión comparten instrumentación, tempo y densidad, el jugador no lee el estado por audio. Test: describir los estados sin nombrarlos y ver si son distinguibles.
|
|
24
|
+
|
|
25
|
+
## Standards no producibles
|
|
26
|
+
|
|
27
|
+
"Audio de alta calidad, sin artefactos" no es un standard. Sample rate, bit depth, formato por plataforma, naming pattern, target de loudness con tolerancia, headroom y budget de voces sí lo son.
|
|
28
|
+
|
|
29
|
+
## El silencio sin documentar
|
|
30
|
+
|
|
31
|
+
Los juegos con mejor audio usan silencio deliberadamente. Si el bible no dice dónde y por qué, alguien va a llenar cada segundo con ambiente.
|
|
32
|
+
|
|
33
|
+
## Ignorar la plataforma
|
|
34
|
+
|
|
35
|
+
Un bible que asume monitores de estudio falla en el speaker de un handheld o en earbuds bluetooth. Los targets de loudness y la allocation de frecuencias cambian por plataforma — documentar cuáles importan.
|
|
36
|
+
|
|
37
|
+
## Bible que nadie gatea
|
|
38
|
+
|
|
39
|
+
Si no hay un paso de review que compare assets contra el bible, es decoración. Conectarlo con `/audio-audit` y hacerlo condición de entrega.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audio-mix
|
|
3
|
+
description: "Mezcla de juego: jerarquia de buses, ducking y sidechain, mix states/snapshots, frequency masking, headroom y targets de loudness. Usar para: estructura de buses, ducking de dialogo, mix dinamico, resolver que el mix suena barroso."
|
|
4
|
+
category: "audio"
|
|
5
|
+
argument-hint: "[buses | ducking | states | loudness | review]"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Write, Edit
|
|
8
|
+
---
|
|
9
|
+
# audio-mix — Mezcla Dinámica
|
|
10
|
+
|
|
11
|
+
Diseña la estructura de buses y las reglas que deciden **qué se escucha cuando todo suena a la vez**.
|
|
12
|
+
|
|
13
|
+
El mix de un juego no es un mix estático: es un sistema de reglas, porque no se sabe de antemano qué va a sonar simultáneamente.
|
|
14
|
+
|
|
15
|
+
## Cuándo usar
|
|
16
|
+
|
|
17
|
+
- Definir la arquitectura de buses del proyecto
|
|
18
|
+
- El mix suena barroso, o el diálogo no se entiende
|
|
19
|
+
- Falta control dinámico: momentos importantes no destacan
|
|
20
|
+
- Cumplir targets de loudness de plataforma
|
|
21
|
+
|
|
22
|
+
## Input
|
|
23
|
+
|
|
24
|
+
1. Leer audio bible, sección 4 (frequency allocation) y 5 (mix hierarchy)
|
|
25
|
+
2. Listar las categorías de audio del proyecto
|
|
26
|
+
3. Preguntar al usuario: plataformas target, si hay diálogo, y si existe algún requisito de certificación
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Jerarquía de buses
|
|
31
|
+
|
|
32
|
+
Estructura base que sirve para casi cualquier juego:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Master
|
|
36
|
+
├── Music
|
|
37
|
+
├── SFX
|
|
38
|
+
│ ├── Player (feedback de la acción del jugador)
|
|
39
|
+
│ ├── World (enemigos, props, entorno)
|
|
40
|
+
│ └── Ambience
|
|
41
|
+
├── Voice
|
|
42
|
+
│ ├── Dialogue (narrativa, crítico)
|
|
43
|
+
│ └── Barks (chatter de combate)
|
|
44
|
+
└── UI
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Por qué esta forma:**
|
|
48
|
+
- `Player` separado de `World` permite proteger el feedback de la acción del jugador cuando hay 40 enemigos
|
|
49
|
+
- `Dialogue` separado de `Barks` permite duckear por diálogo importante sin que cada bark baje la música
|
|
50
|
+
- `UI` afuera de `SFX` porque tiene su propio nivel (−18 a −24 dB) y su propio control de usuario
|
|
51
|
+
- `Ambience` separado porque es lo primero que se sacrifica cuando el mix se llena
|
|
52
|
+
|
|
53
|
+
**Cada bus necesita:** volumen base, control de usuario expuesto o no, y si va a reverb de zona.
|
|
54
|
+
|
|
55
|
+
## Ducking
|
|
56
|
+
|
|
57
|
+
Bajar una categoría cuando otra suena. Los casos reales:
|
|
58
|
+
|
|
59
|
+
| Trigger | Qué duckea | Cuánto | Attack / Release |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| Diálogo | Music, Ambience | −6 a −9 dB | 50 ms / 400 ms |
|
|
62
|
+
| UI crítico | Music, SFX | −3 a −6 dB | 20 ms / 200 ms |
|
|
63
|
+
| Momento narrativo | Todo menos Voice | −12 dB | 200 ms / 800 ms |
|
|
64
|
+
| Barks | nada, o −2 dB | mínimo | rápido |
|
|
65
|
+
|
|
66
|
+
**El release siempre más largo que el attack.** Un release corto produce bombeo audible: la música sube y baja a cada palabra. 300-500 ms de release para diálogo es lo que suena natural.
|
|
67
|
+
|
|
68
|
+
**El ducking no reemplaza al arreglo.** Si la música vive en 500 Hz-2 kHz y el diálogo también, hay que duckear tanto que se escucha. La solución es dejar ese rango libre en el arreglo (ver `/music-composition`, orquestación production-aware). El ducking es el ajuste fino, no la solución estructural.
|
|
69
|
+
|
|
70
|
+
**Sidechain vs ducking por evento:** el ducking por evento (el middleware baja el bus cuando el evento está activo) es más predecible y más barato. El sidechain real (detectar la señal) responde mejor a diálogo de duración variable. Para VO grabado, ducking por evento alcanza.
|
|
71
|
+
|
|
72
|
+
## Frequency masking
|
|
73
|
+
|
|
74
|
+
Dos sonidos que ocupan la misma banda compiten, y el más fuerte gana. El EQ correctivo adelgaza todo; la solución es la **asignación de bandas** del audio bible.
|
|
75
|
+
|
|
76
|
+
Cuando ya está producido y hay que arreglarlo:
|
|
77
|
+
|
|
78
|
+
| Problema | Arreglo de mezcla |
|
|
79
|
+
|---|---|
|
|
80
|
+
| Diálogo tapado por música | EQ dinámico en Music: hueco en 1-3 kHz solo cuando hay VO |
|
|
81
|
+
| Impactos sin peso | High-pass en Music y Ambience por debajo de 80 Hz |
|
|
82
|
+
| Mix barroso | High-pass en todo lo que no necesita graves; casi nada necesita por debajo de 60 Hz |
|
|
83
|
+
| UI perdido | Reducir el brillo de la percusión, no subir el UI |
|
|
84
|
+
| Todo suena chico | Demasiadas fuentes; el problema es voice count, no EQ |
|
|
85
|
+
|
|
86
|
+
## Mix states
|
|
87
|
+
|
|
88
|
+
Snapshots de la mezcla que se activan por contexto. El mix dinámico es lo que hace que un juego suene "bien mezclado" en situaciones que nadie mezcló a mano.
|
|
89
|
+
|
|
90
|
+
| Estado | Qué cambia |
|
|
91
|
+
|---|---|
|
|
92
|
+
| Exploration | Base |
|
|
93
|
+
| Combat | Player y World arriba, Ambience abajo |
|
|
94
|
+
| Cutscene | Voice arriba, todo lo demás abajo, sin UI |
|
|
95
|
+
| Menu (pausa) | Todo salvo UI y Music muy abajo o silenciado |
|
|
96
|
+
| Low health | Filtro en Music, tinnitus, resto atenuado |
|
|
97
|
+
| Death | Corte o filtro extremo |
|
|
98
|
+
|
|
99
|
+
**Interpolar entre estados**, nunca saltar. 200-500 ms según qué tan dramático sea el cambio. Y definir qué pasa si dos estados quieren activarse a la vez: prioridad explícita.
|
|
100
|
+
|
|
101
|
+
## Headroom y loudness
|
|
102
|
+
|
|
103
|
+
**Headroom:** dejar margen en el master para que los picos simultáneos no clippeen. El mix se hace apuntando a que el caso peor (explosión + música + diálogo) tenga espacio, no el caso promedio.
|
|
104
|
+
|
|
105
|
+
**Targets de loudness.** La referencia de industria en juegos es **ASWG-R001** (Sony), que es la única especificación de una plataforma mayor:
|
|
106
|
+
|
|
107
|
+
| Contexto | Loudness promedio | True peak máximo |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| Consola / home | **−23 LUFS** (±2) | **−1 dBTP** |
|
|
110
|
+
| Portable / handheld | **−18 LUFS** | −1 dBTP |
|
|
111
|
+
|
|
112
|
+
El target portable es más alto porque se juega en ambientes ruidosos y con speakers chicos.
|
|
113
|
+
|
|
114
|
+
Notas de aplicación:
|
|
115
|
+
- La medición es del **programa completo en gameplay representativo**, no de un asset aislado
|
|
116
|
+
- Verificar los targets vigentes de cada plataforma antes de certificación: las recomendaciones se revisan (el promedio de ASWG estuvo también citado como −24 LKFS)
|
|
117
|
+
- El true peak de −1 dBTP deja margen para la codificación lossy, que puede generar picos por encima del valor medido en PCM
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Proceso
|
|
122
|
+
|
|
123
|
+
1. **Definir la jerarquía de buses** con sus volúmenes base y controles de usuario
|
|
124
|
+
2. **Definir reglas de ducking** con attack/release y cuánto
|
|
125
|
+
3. **Definir mix states** con sus transiciones y prioridades
|
|
126
|
+
4. **Verificar masking** contra la frequency allocation del bible
|
|
127
|
+
5. **Definir targets de loudness** por plataforma y cómo se van a medir
|
|
128
|
+
6. **Escribir** — preguntar "¿Escribo el diseño de mezcla a `<path>`?" antes de usar Write
|
|
129
|
+
|
|
130
|
+
### Verificación
|
|
131
|
+
|
|
132
|
+
El mix se valida **jugando**, en el caso peor: máxima densidad de enemigos, con diálogo, con música de combate, en el dispositivo más chico del target. Si ahí se entiende el diálogo y se lee el feedback del jugador, el mix funciona.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Verdict
|
|
137
|
+
|
|
138
|
+
- **COMPLIANT** — buses definidos, ducking con release más largo que attack, mix states interpolados con prioridad, y loudness medido dentro del target de cada plataforma
|
|
139
|
+
- **CONCERNS** — la estructura existe pero el loudness no se midió en gameplay real, o el ducking está tapando un problema de arreglo
|
|
140
|
+
- **NON-COMPLIANT** — el loudness está fuera del target de plataforma, o hay clipping en el caso peor
|
|
141
|
+
|
|
142
|
+
## Anti-patterns
|
|
143
|
+
|
|
144
|
+
> → Read references/anti-patterns.md
|
|
145
|
+
|
|
146
|
+
## Next steps
|
|
147
|
+
|
|
148
|
+
- `/audio-audit` para medir loudness, picos y voice count en gameplay real
|
|
149
|
+
- `/middleware-integration` para implementar buses, ducking y states
|
|
150
|
+
- `/ui-sound-design` para el nivel del bus UI
|
|
151
|
+
- `/music-composition` si el masking se tiene que resolver en el arreglo y no en la mezcla
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Audio Mix — Anti-patterns
|
|
2
|
+
|
|
3
|
+
## Mezclar en aislamiento
|
|
4
|
+
|
|
5
|
+
Aprobar cada categoría por separado y esperar que el conjunto funcione. El mix se valida jugando el caso peor: máxima densidad, con diálogo, con música de combate, en el dispositivo más chico.
|
|
6
|
+
|
|
7
|
+
## Release de ducking demasiado corto
|
|
8
|
+
|
|
9
|
+
La música sube y baja a cada palabra y se escucha el bombeo. El release siempre más largo que el attack: 300-500 ms para diálogo.
|
|
10
|
+
|
|
11
|
+
## Ducking como solución al masking
|
|
12
|
+
|
|
13
|
+
Si la música y el diálogo viven los dos en 500 Hz-2 kHz, hay que duckear tanto que el efecto es audible. Eso se resuelve en el arreglo, dejando el rango libre. El ducking es ajuste fino.
|
|
14
|
+
|
|
15
|
+
## Todo duckea a todo
|
|
16
|
+
|
|
17
|
+
Barks duckeando música, UI duckeando SFX, ambiente duckeando barks. El resultado es un mix que respira todo el tiempo y nunca está quieto. Definir qué duckea a qué, y que sea poco.
|
|
18
|
+
|
|
19
|
+
## Un solo bus de SFX
|
|
20
|
+
|
|
21
|
+
Con 40 enemigos, el feedback de la acción del jugador se pierde entre el resto. Separar `Player` de `World` es lo que permite protegerlo.
|
|
22
|
+
|
|
23
|
+
## Diálogo y barks en el mismo bus
|
|
24
|
+
|
|
25
|
+
Cada bark de combate duckea la música. Separarlos permite duckear por diálogo importante nada más.
|
|
26
|
+
|
|
27
|
+
## UI dentro de SFX
|
|
28
|
+
|
|
29
|
+
El UI tiene otro nivel (−18 a −24 dB) y suele necesitar control de usuario propio. Metido en SFX, hereda un volumen que no le corresponde.
|
|
30
|
+
|
|
31
|
+
## Mix states que saltan
|
|
32
|
+
|
|
33
|
+
Cambiar de snapshot instantáneamente se escucha como un corte. Interpolar 200-500 ms según el cambio.
|
|
34
|
+
|
|
35
|
+
## Estados sin prioridad
|
|
36
|
+
|
|
37
|
+
Combat y Low health quieren activarse a la vez y el resultado depende del orden de evaluación. Prioridad explícita.
|
|
38
|
+
|
|
39
|
+
## EQ correctivo en todo
|
|
40
|
+
|
|
41
|
+
Adelgazar cada fuente con EQ hasta que dejen de pelear produce un mix delgado y sin peso. El problema de raíz es la asignación de bandas, o hay demasiadas fuentes sonando.
|
|
42
|
+
|
|
43
|
+
## Confundir "suena chico" con problema de EQ
|
|
44
|
+
|
|
45
|
+
Cuando todo suena chico, casi siempre hay demasiadas voces activas y ninguna tiene lugar. Es un problema de voice count, y el EQ no lo arregla.
|
|
46
|
+
|
|
47
|
+
## Nada de headroom
|
|
48
|
+
|
|
49
|
+
Mezclar apuntando al caso promedio y clippear cuando coinciden explosión, música y diálogo. El margen se calcula contra el peor caso.
|
|
50
|
+
|
|
51
|
+
## Medir loudness en un asset aislado
|
|
52
|
+
|
|
53
|
+
Los targets de LUFS son del programa completo en gameplay representativo. Medir un archivo no dice nada del cumplimiento.
|
|
54
|
+
|
|
55
|
+
## Asumir un target sin verificar
|
|
56
|
+
|
|
57
|
+
Los targets de plataforma se revisan. Confirmar el vigente antes de certificación en vez de confiar en un número memorizado.
|
|
58
|
+
|
|
59
|
+
## Ignorar el true peak
|
|
60
|
+
|
|
61
|
+
Un mix que llega a 0 dBFS en PCM puede generar picos por encima al codificarse en lossy. −1 dBTP existe por eso.
|
|
62
|
+
|
|
63
|
+
## Mezclar solo con auriculares buenos
|
|
64
|
+
|
|
65
|
+
El mix suena perfecto en los monitores del sound designer y se cae en el speaker de un handheld. Validar en el dispositivo más chico del target.
|