refactor(sound): extraer el motor Web Audio de sema al art `$sound`
`sema/chans/sound.ts` tenía 407 líneas de las que ~255 (≈63%) eran maquinaria
Web Audio —ciclo de vida del AudioContext, síntesis, samples, desbloqueo por
gesto— mezclada con doctrina perceptiva. Deuda arrastrada, con tres síntomas
medidos: el import era estático, así que toda app con sema metía el
sintetizador en el bundle aunque el sonido estuviera apagado (que es el
default); faltaba ciudadanía que `$scene` ya resuelve; y la costura de
inyección (`audioContextFactory`) llevaba ahí sin usar desde el principio.
Mismo movimiento que `$motion` hizo desde eidos: el art se lleva el RUNTIME, la
capa conserva sus DATOS y su doctrina.
- `$sound` / `EngineSound`: UN AudioContext por documento (los navegadores los
limitan y el gesto de desbloqueo es por contexto), síntesis de earcon,
samples con caché y fallback a síntesis, `autoSuspend` OPT-IN —suspender con
la pestaña oculta es correcto para earcons y erróneo para contenido, así que
es decisión de quien compone— y aviso cuando un segundo contexto va vivo.
Puertos `SoundDom` / `SoundTimers` inyectados; no importa ningún otro art ni
nada de `$uix/sema`, que es la prueba objetiva del corte.
- `SoundChannel`: 407 → 136 líneas. Solo doctrina: el gate de `prepare`, la
política de reducción y el reparto «el canal resuelve el NIVEL, el motor
aplica la ganancia». Sema no gana ni un import: recibe el motor por puerto.
- `uix.sound` en standalone y attach con `ownsSound` (idioma ya shipped:
`ownsMotion` / `ownsScene`), fila `sound` en la tabla ejecutable
`contracts.ts`, y `defineEngineSound()` para el camino de app.
- Tests nuevos: `engine-sound.test.ts` (11), `sound-port.test.ts` (guard de
deriva de tipos + la regla de propiedad) y `sound-e2e.test.ts`, que recorre
`emit -> cascada -> canal -> art -> grafo real`: el camino que las 16 suites
previas no cubrían porque paraban en canales falsos.
Sin `diagnostics.ts` ni `errors.ts`, y es decisión: espejo de `$scene`, aquí
todo fallo es degradación documentada, no error de programador.
Verificación: 17 suites / 198 tests · `check` en la baseline exacta (73
errores, 0 propios) · `sound.test.ts` verde SIN tocar un solo assert, que era
el criterio de que el movimiento fue value-preserving.
Planes: `PLAN-sound-engine.md` (completo, con el registro de la revisión
adversarial E-1..E-7), `PLAN-audio-player.md` (aparcado tras el análisis del
reproductor, con sus correcciones en cabecera) y `CONTINUE-sound-engine.md`
(handoff).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
# CONTINUE — motor de sonido + reproductor
|
|
|
|
|
|
|
|
|
|
|
|
> **Kickoff para la sesión siguiente**: *"Lee `docs/process/CONTINUE-sound-engine.md`
|
|
|
|
|
|
> y sigue por donde toque."*
|
|
|
|
|
|
> **Fecha**: 2026-07-30 · Rama `alpha-0.1-sec-dom`.
|
|
|
|
|
|
|
|
|
|
|
|
Handoff corto. La verdad detallada vive en los dos planes; esto solo dice **en
|
|
|
|
|
|
qué punto está cada cosa y qué trampas evitar**.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Estado
|
|
|
|
|
|
|
|
|
|
|
|
| Iniciativa | Estado |
|
|
|
|
|
|
| --- | --- |
|
|
|
|
|
|
| [`PLAN-sound-engine.md`](./PLAN-sound-engine.md) | ✅ **COMPLETA** — F0…F4 hechas. Solo queda F5 (desbloquear el reproductor), que es decisión de sesión |
|
|
|
|
|
|
| [`PLAN-audio-player.md`](./PLAN-audio-player.md) | ⏸️ **APARCADO** — D-AP.1…D-AP.12 sin firmar. Su cabecera lleva **5 correcciones pendientes de incorporar** antes de presentar nada |
|
|
|
|
|
|
|
|
|
|
|
|
## Qué se construyó
|
|
|
|
|
|
|
|
|
|
|
|
**`$sound` / `EngineSound`** ([`src/arts/sound/`](../../src/arts/sound/README.md)) — el
|
|
|
|
|
|
runtime Web Audio extraído de `sema/chans/sound.ts`, que tenía ~63% de máquina
|
|
|
|
|
|
mezclada con doctrina. Un `AudioContext` por documento, síntesis de earcon,
|
|
|
|
|
|
samples con caché y fallback, desbloqueo por gesto, `autoSuspend` opt-in y aviso
|
|
|
|
|
|
al segundo contexto vivo.
|
|
|
|
|
|
|
|
|
|
|
|
- `SoundChannel`: **407 → 136 líneas**, solo doctrina (gate de `prepare`,
|
|
|
|
|
|
política de reducción, «el canal resuelve el NIVEL, el motor aplica la
|
|
|
|
|
|
ganancia»).
|
|
|
|
|
|
- Sema **no importa el art**: lo recibe por puerto inyectado.
|
|
|
|
|
|
- `uix.sound` en ambos modos de arranque, con `ownsSound` (idioma ya shipped:
|
|
|
|
|
|
`ownsMotion` / `ownsScene`), fila en la tabla ejecutable `contracts.ts`, y
|
|
|
|
|
|
`defineEngineSound()` para attach.
|
|
|
|
|
|
|
|
|
|
|
|
**Gate al cerrar**: 17 suites / 198 tests · `check` en la baseline exacta
|
|
|
|
|
|
(73 errores, 0 propios) · `sound.test.ts` verde **sin tocar un solo assert**.
|
|
|
|
|
|
|
|
|
|
|
|
## Lo que NO está verificado (y hay que verificar)
|
|
|
|
|
|
|
|
|
|
|
|
1. **La invariante de contexto único en un navegador real.** Hoy **ninguna**
|
|
|
|
|
|
página enchufa `soundEngine` a su propio `EngineSemantic` — el estudio de
|
|
|
|
|
|
sema incluido ([`temas/sema/_lib/audition.ts:35`](../../web/routes/temas/sema/_lib/audition.ts)
|
|
|
|
|
|
monta el suyo con `sound: true`), así que esa página abre un **segundo
|
|
|
|
|
|
contexto**. Con el aviso nuevo ya lo dirá por consola: **abrir `/temas/sema`
|
|
|
|
|
|
y mirar el warn es la primera comprobación de mañana.** Arreglo natural:
|
|
|
|
|
|
pasarle `soundEngine: uix.sound`.
|
|
|
|
|
|
2. **La escucha comparativa fina.** El usuario confirmó que suena, pero no se
|
|
|
|
|
|
hizo un A/B earcon a earcon contra el comportamiento previo.
|
|
|
|
|
|
|
|
|
|
|
|
## Trampas de este repo (verificadas a base de perder tiempo)
|
|
|
|
|
|
|
|
|
|
|
|
- **NO arrancar un dev server si el del usuario está vivo.** `npm run dev` es
|
|
|
|
|
|
`vite dev --force`: reescribe `node_modules/.vite/deps` por debajo del que
|
|
|
|
|
|
corre → dos runtimes de Svelte → la página deja de hidratar
|
|
|
|
|
|
(`lifecycle_outside_component`) → parece regresión de audio y no lo es.
|
|
|
|
|
|
Pasó el 2026-07-30. Verificar con **tests deterministas** (dobles de
|
|
|
|
|
|
`AudioContext`), no levantando servidores.
|
|
|
|
|
|
Recuperación (PowerShell): parar node →
|
|
|
|
|
|
`Remove-Item -Recurse -Force node_modules/.vite/deps` → `npm run dev` →
|
|
|
|
|
|
**Ctrl+Shift+R**.
|
|
|
|
|
|
- **El panel del navegador no pinta** en este entorno (viewport 0×0);
|
|
|
|
|
|
`javascript_tool` sí funciona.
|
|
|
|
|
|
- **Filtrar `npm run check` con cuidado**: las rutas llevan `\\` doble, así que
|
|
|
|
|
|
un patrón `sema.chans` NO casa con `sema\\chans` y da falsos «0 errores
|
|
|
|
|
|
propios». Ocurrió.
|
|
|
|
|
|
- **El árbol está compartido con otras sesiones** (palabras, menubar,
|
|
|
|
|
|
dropdown-menu, blocks/cta, alpha…). `git reset -q` + `add` solo lo propio,
|
|
|
|
|
|
siempre en un bloque atómico.
|
|
|
|
|
|
|
|
|
|
|
|
## Ajenos y preexistentes detectados (NO tocados)
|
|
|
|
|
|
|
|
|
|
|
|
- `contracts.test.ts` 3 rojos: `aura`, `menubar`, `radio-group`.
|
|
|
|
|
|
- `docs:check` 1 error: `eidos/components/callout/README.md:23` dice «8 roles»,
|
|
|
|
|
|
son 9.
|
|
|
|
|
|
- `Board.svelte:42` warn `binding_property_non_reactive` — inofensivo ahí
|
|
|
|
|
|
(lectura imperativa); se arregla con `let els = $state({})`.
|
|
|
|
|
|
- `temas/sema/_lib/audition.ts` tiene 2 errores de tipos preexistentes.
|
|
|
|
|
|
|
|
|
|
|
|
## Siguientes pasos posibles
|
|
|
|
|
|
|
|
|
|
|
|
1. **F5** — desbloquear el reproductor y reescribir su **D-AP.11** contra el art
|
|
|
|
|
|
ya existente (su premisa —«no hay motor»— era falsa).
|
|
|
|
|
|
2. **Cerrar la invariante**: pasar `soundEngine` al `EngineSemantic` del estudio
|
|
|
|
|
|
y comprobar que el warn desaparece.
|
|
|
|
|
|
3. **Retomar el reproductor** por su F0: firmar D-AP.\* con las 5 correcciones
|
|
|
|
|
|
de la cabecera ya incorporadas (segmentos, G-2 bloqueante, ducking…).
|
|
|
|
|
|
|
|
|
|
|
|
**No registrado en `next-features.md` a propósito**: ese fichero está modificado
|
|
|
|
|
|
por otra sesión y no se toca desde aquí.
|