fix(demos): orquesta — real sema engine for sound/haptic + correct channel framing

Two fixes:
- Sound + haptic now fire from the REAL EngineSemantic.emit (channels filtered by the
  mixer; `visual` excluded so no data-event-* conflict with the toggleable facets) —
  replacing the Web Audio / Vibration approximation. One real emit, the chosen channels.
- Correct the architecture framing (was conflating LAYER with CHANNEL): the channels are
  SEMANTIC (sema). Sema projects the `visual` channel (-> eidos materializes color ·
  motion · depth · forma) and executes sound + haptic. motion/depth/shape/color are NOT
  "eidos channels" — they are sema visual channel, made visible by eidos. Mixer now groups
  "canal visual · sema -> eidos" vs "canales runtime · sema"; dropped the misleading
  per-track eidos/sema tag. CHANNELS_SYNTHESIS reframed: 8 book expression channels -> 3
  sema runtime channels (visual / sound / haptic).

Verified: check 0 errors; browser — two mixer groups render, real engine.emit fires
without error, visual facets compose.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 21057c398f
commit 898af6419a

@ -18,19 +18,26 @@ Todo canal —no solo motion— respeta el mismo eje doble:
El orden entre ambos lo fija `sequence` (`pre` / `coincident` / `post`). Eidos lee **los dos**:
`presets` sobre `data-state`, `signatures` sobre `data-event-*`.
## 2. Los 8 canales del libro → sistemas Eidos
## 2. Canales de expresión (libro) vs canales de sema (runtime)
| Canal (libro) | Sistema | RFC / motor |
El libro tiene **8 canales de expresión** (dimensiones perceptuales). El framework los implementa
con los **canales de sema** (runtime), que son **3** — y aquí está la clave que evita la confusión
clásica:
| Canal de sema (runtime) | Cubre (expresión del libro) | Quién lo materializa |
|---|---|---|
| tiempo + movimiento | **motion** | `arts/motion` + `eidos/lib/motion` (firma + presets) |
| presencia + profundidad | **depth** | `DEPTH_ENGINE_RFC` |
| **forma** | **shape** | `SHAPE_ENGINE_RFC` |
| color | **color** | `COLOR_ENGINE_RFC` |
| sonido | **sema · sound** | `sema/chans/sound` |
| háptica | **sema · haptic** | `sema/chans/haptic` |
| **visual** (sema proyecta `data-event-*`) | movimiento · presencia · profundidad · forma · color | **eidos** — CSS sobre `data-event-*` + `data-state` |
| **sound** | sonido | sema (`chans/sound`) |
| **haptic** | háptica | sema (`chans/haptic`) |
> **No te confundas**: **motion · depth · shape · color NO son "canales de eidos"** — son el
> **canal `visual` de sema**, que sema proyecta y **eidos materializa**. Eidos es el
> *materializador* del canal visual, no el dueño de unos canales propios. Los RFCs de eidos
> (`COLOR/DEPTH/SHAPE_ENGINE`) describen *cómo* eidos materializa cada faceta visual — no canales
> independientes.
Más el **espacio** (`STRUCTURE_ENGINE_RFC`) — estructural, **solo-estado** (el escenario, no el
suceso): no tiene momento-evento, y eso es deliberado.
Más el **espacio** (`STRUCTURE_ENGINE_RFC`) — **estructural, no de expresión**: no "ocurre", es el
escenario (solo-estado).
## 3. Quién posee qué (la narrativa de capas)
@ -54,9 +61,9 @@ contact · press → scale 0.96 (motion)
+ vibración (sema haptic)
```
Un `engine.emit(...)` estampa `data-event-*` (los canales visuales reaccionan) **y** dispara
sound + haptic — desde el **mismo** evento. No hay 6 sistemas coordinándose a mano; hay un
suceso que se expresa en 6 dimensiones.
Un `engine.emit(...)` estampa `data-event-*` (el canal **visual** reacciona — eidos lo materializa
en color/motion/depth/forma) **y** dispara sound + haptic — desde el **mismo** evento. No hay
sistemas coordinándose a mano; hay un suceso que se expresa por los canales de sema.
## 5. Builders runtime — el quinteto (jaula abierta)
@ -69,13 +76,15 @@ escala authored:
## 6. Posición frente a referentes
Ningún referente reúne los canales bajo **un modelo único**. Material tiene morph de forma +
motion (ad-hoc, sin sonido/háptica integrados); Apple, continuidad + materiales (atado a
plataforma); todos, color. **Eidos los reúne** —los 6 perceptuales + el estructural— sobre el
modelo de dos momentos, disparados desde un evento, con builders runtime y registro abierto.
Eso es el framework, no la suma de sus partes.
Ningún referente reúne los canales bajo **un modelo semántico único**. Material tiene morph de
forma + motion (ad-hoc, sin sonido/háptica integrados); Apple, continuidad + materiales (atado a
plataforma); todos, color. **La UIX los reúne** —el canal visual (sema → eidos) + sound + haptic
(sema)— sobre el modelo de dos momentos, disparados desde un evento, con builders runtime y
registro abierto. Eso es el framework, no la suma de sus partes.
## 7. Demo
`/temas/orquesta` — el mezclador: pulsa un control y silencia cada canal para oír su parte. Los
4 visibles usan tokens reales; sound + haptic los aproxima el demo (en la app, motor sema).
`/temas/orquesta` — el mezclador: pulsa un control y silencia cada faceta para oír su parte. Las
4 facetas del **canal visual** se materializan con tokens de eidos (toggleables para poder
silenciarlas una a una); **sound + haptic** los dispara el motor sema real
(`EngineSemantic.emit`, canales filtrados). Un evento, los canales de sema.

@ -1,17 +1,20 @@
<script lang="ts">
/**
* Capstone — "Diseñando lo que ocurre" made literal: ONE interaction firing many channels at
* Capstone — "Diseñando lo que ocurre" made literal: ONE interaction firing sema's channels at
* once, with a mixer to mute each and watch them layer.
*
* The 4 visible channels (color · motion · depth · shape) use REAL eidos tokens, composed
* explicitly per-channel so each is independently mutable. Sound + haptic are fired with the
* Web Audio / Vibration APIs directly — in-app the sema engine emits them from the SAME event
* (the canonical sound + haptic channels); here they're approximated so the mixer can mute them.
* The channels are SEMANTIC (sema). Sema executes `sound` + `haptic` (runtime) and projects the
* `visual` channel (via `data-event-*`), which it DELEGATES to eidos to materialize — into the
* book's visual facets: color · motion · depth · forma. So those four are NOT "eidos channels":
* they are sema's visual channel, made visible by eidos. Here the visual facets are toggled as
* eidos token effects (so each facet is independently mutable); sound + haptic fire from the
* REAL `EngineSemantic.emit` (channels filtered by the mixer).
*
* The point: an interaction is not color, nor motion, nor sound — it is one perceptual event
* across N channels, under the two-moment model. This is the book's thesis, playable.
* across sema's channels, under the two-moment model. This is the book's thesis, playable.
*/
import '@/uix/eidos/index.css'
import { EngineSemantic, type SemaChannelId } from '$uix/sema'
let theme = $state<'light' | 'dark'>('dark')
$effect(() => {
@ -25,16 +28,23 @@
})
type ChannelId = 'color' | 'motion' | 'depth' | 'shape' | 'sound' | 'haptic';
const CHANNELS: { id: ChannelId; label: string; note: string; eidos: boolean }[] = [
{ id: 'color', label: 'color', note: 'tinte de intención (→ affirm)', eidos: true },
{ id: 'motion', label: 'movimiento', note: 'escala bajo el dedo', eidos: true },
{ id: 'depth', label: 'profundidad', note: 'la sombra recede', eidos: true },
{ id: 'shape', label: 'forma', note: 'la esquina se afirma', eidos: true },
{ id: 'sound', label: 'sonido', note: 'un tick (motor sema)', eidos: false },
{ id: 'haptic', label: 'háptica', note: 'una vibración (motor sema)', eidos: false }
type Facet = { id: ChannelId; label: string; note: string };
// sema's channels. `visual` is ONE channel that eidos materializes — shown here as its facets,
// each independently mutable. `sound` + `haptic` are sema's runtime channels.
const VISUAL_FACETS: Facet[] = [
{ id: 'color', label: 'color', note: 'tinte de intención (→ affirm)' },
{ id: 'motion', label: 'movimiento', note: 'escala bajo el dedo' },
{ id: 'depth', label: 'profundidad', note: 'la sombra recede' },
{ id: 'shape', label: 'forma', note: 'la esquina se afirma' }
];
const RUNTIME_CHANNELS: Facet[] = [
{ id: 'sound', label: 'sonido', note: 'síntesis por intención' },
{ id: 'haptic', label: 'háptica', note: 'Vibration API' }
];
const ALL_CHANNELS = [...VISUAL_FACETS, ...RUNTIME_CHANNELS];
// Visible channels on by default; sound + haptic OFF (audible/tactile side effects — sema canon).
// Visual facets on by default; sound + haptic OFF (audible/tactile side effects — sema canon).
let channels = $state<Record<ChannelId, boolean>>({
color: true,
motion: true,
@ -48,33 +58,28 @@
let pressCount = $state(0);
let lastFired = $state<string[]>([]);
let timer: ReturnType<typeof setTimeout> | undefined;
let actx: AudioContext | undefined;
function tick(): void {
try {
actx ??= new (window.AudioContext ?? (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext)();
const o = actx.createOscillator();
const g = actx.createGain();
o.type = 'triangle';
o.frequency.value = 660;
o.connect(g);
g.connect(actx.destination);
const t = actx.currentTime;
g.gain.setValueAtTime(0.0001, t);
g.gain.exponentialRampToValueAtTime(0.16, t + 0.005);
g.gain.exponentialRampToValueAtTime(0.0001, t + 0.13);
o.start(t);
o.stop(t + 0.14);
} catch {
/* audio unavailable — no-op */
}
}
let targetEl = $state<HTMLElement | null>(null);
let engine: EngineSemantic | undefined;
function fire(): void {
pressCount += 1;
lastFired = CHANNELS.filter((c) => channels[c.id]).map((c) => c.label);
if (channels.sound) tick();
if (channels.haptic) navigator.vibrate?.(18);
lastFired = ALL_CHANNELS.filter((c) => channels[c.id]).map((c) => c.label);
// Real sema engine for sound + haptic — the canonical channels, from one emit. The 4 visible
// channels stay toggleable eidos classes (independently mutable), so we exclude 'visual' from
// the emit (no data-event-* stamp → no conflict). The mixer filters which channels fire.
const ch: SemaChannelId[] = [];
if (channels.sound) ch.push('sound');
if (channels.haptic) ch.push('haptic');
if (ch.length && targetEl) {
engine ??= new EngineSemantic({ visual: false, sound: true, haptic: true });
void engine.emit({
target: targetEl,
name: 'press',
family: 'contact',
intent: 'affirm',
channels: ch
});
}
pressed = true;
clearTimeout(timer);
timer = setTimeout(() => (pressed = false), 300);
@ -106,15 +111,17 @@
<div class="sec-head">
<h2 class="sec-title">El mezclador</h2>
<p class="sec-note">
Pulsa el botón: los canales activos disparan desde <em>un</em> evento. Apaga la forma y la
esquina deja de afirmarse; apaga la profundidad y la sombra no recede. Un instrumento
menos, el mismo gesto.
Pulsa: los canales de <em>sema</em> disparan desde <em>un</em> evento. Sema proyecta el
canal <em>visual</em> —que eidos materializa en color · movimiento · profundidad · forma— y
ejecuta <em>sonido</em> + <em>háptica</em>. Apaga la forma y la esquina deja de afirmarse;
apaga la profundidad y la sombra no recede. Un instrumento menos, el mismo gesto.
</p>
</div>
<div class="stage">
<div class="stage-target">
<button
bind:this={targetEl}
class="target"
class:ch-color={channels.color}
class:ch-motion={channels.motion}
@ -128,14 +135,24 @@
</div>
<div class="mixer">
{#each CHANNELS as c}
<span class="mixer-group">canal <b>visual</b> · sema proyecta → eidos materializa</span>
{#each VISUAL_FACETS as c}
<button class="track" data-on={channels[c.id]} onclick={() => (channels[c.id] = !channels[c.id])}>
<span class="track-led" data-on={channels[c.id]}></span>
<span class="track-meta">
<span class="track-label">{c.label}</span>
<span class="track-note">{c.note}</span>
</span>
</button>
{/each}
<span class="mixer-group">canales <b>runtime</b> · sema ejecuta</span>
{#each RUNTIME_CHANNELS as c}
<button class="track" data-on={channels[c.id]} onclick={() => (channels[c.id] = !channels[c.id])}>
<span class="track-led" data-on={channels[c.id]}></span>
<span class="track-meta">
<span class="track-label">{c.label}</span>
<span class="track-note">{c.note}</span>
</span>
<span class="track-tag" data-layer={c.eidos ? 'eidos' : 'sema'}>{c.eidos ? 'eidos' : 'sema'}</span>
</button>
{/each}
</div>
@ -175,13 +192,14 @@
<footer class="footnote">
<p>
<strong>Esto es el framework, no seis sistemas sueltos.</strong> Cada canal tiene su motor y
su demo (<code>/temas/{'{'}color · tipografia · profundidad · forma · estructura{'}'}</code>),
pero la tesis del libro es que <em>componen</em>: un evento sema reparte la firma por
motion + depth + shape + color (visual) y sound + haptic (sema), bajo el modelo de dos
momentos. Aquí los cuatro visibles usan tokens reales de eidos; sonido y háptica los
aproxima el demo (en la app los emite el motor sema desde el mismo evento). Ningún referente
reúne los seis canales sobre un modelo único.
<strong>Todo es sema; los canales son semánticos.</strong> Un evento sema reparte la firma por
sus canales: el canal <strong>visual</strong> —que sema proyecta vía <code>data-event-*</code>
y <strong>delega en eidos</strong> para materializarlo en color · movimiento · profundidad ·
forma— y los canales <strong>sound</strong> + <strong>haptic</strong> que sema ejecuta. No son
"canales de eidos": son el canal visual de sema, hecho visible por eidos. Aquí los disparamos
todos desde un <code>EngineSemantic.emit</code> real (sonido + háptica) + los tokens de eidos
para el visual, bajo el modelo de dos momentos. Ningún referente reúne los canales sobre un
modelo semántico único.
</p>
</footer>
</div>
@ -259,9 +277,9 @@
.track-meta { display: flex; flex-direction: column; gap: 0.1rem; flex: 1; min-inline-size: 0; }
.track-label { font-family: var(--font-family-mono); font-size: var(--font-size-sm); color: var(--color-content-primary); }
.track-note { font-size: var(--font-size-xs); color: var(--color-content-muted); }
.track-tag { font-family: var(--font-family-mono); font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.06em; padding: 0.15rem 0.45rem; border-radius: 4px; }
.track-tag[data-layer='eidos'] { background: color-mix(in oklab, #12a594 22%, transparent); color: #2dd4bf; }
.track-tag[data-layer='sema'] { background: color-mix(in oklab, var(--color-primary-solid) 24%, transparent); color: var(--color-primary-text, #c4b5fd); }
.mixer-group { font-family: var(--font-family-mono); font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-content-muted); margin-block-start: 0.5rem; padding-inline: 0.2rem; }
.mixer-group:first-child { margin-block-start: 0; }
.mixer-group b { color: var(--color-content-secondary); font-weight: 600; }
.trace { margin-block-start: 2rem; display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; font-family: var(--font-family-mono); font-size: var(--font-size-xs); color: var(--color-content-secondary); padding-block-start: 1.25rem; border-block-start: 1px solid color-mix(in srgb, var(--rule) 60%, transparent); }
.trace-k { color: var(--color-content-muted); text-transform: uppercase; letter-spacing: 0.06em; }

Loading…
Cancel
Save

Powered by TurnKey Linux.