gioser

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198635 Definiciones

feat(renaser): Fase 15 — la voz del sistema (acorde + eventos)

La bocina pertenecía al app enfocado (Fase 12), pero el kernel
necesita hablar también. Ahora tiene voz propia, prioritaria.

- `altavoz`: cola `SECUENCIA: Mutex<VecDeque<(u32,u32)>>` (freq, ms)
  + reloj `FIN_NOTA: AtomicU64`. `agendar(&[...])` encola;
  `atender()` (tarea del compositor cada fotograma) avanza la
  secuencia y silencia al acabar; `kernel_sonando()` gatea a los
  apps — mientras el kernel suena, `sys_tono` no-op.
- Catálogo: VOZ_BIENVENIDA (Do5-Mi5-Sol5, 500 ms), VOZ_LANZAR
  (700→1050 Hz), VOZ_CERRAR (900→520 Hz), VOZ_DESALOJO (180 Hz).
- Hitos: `kernel_main` agenda el acorde antes de `ejecutor.run`;
  `nacer_ventana` (Alt+N), `cerrar` (Alt+Q), `desalojar` (falla)
  agendan al hacer su trabajo.
- De paso: las pestañas de la barra de tareas calculan su tinta por
  brillo del fondo (ITU-R BT.601); la pestaña crema del desalojo por
  memoria, que llevaba texto blanco invisible, ahora luce su nombre
  en tinta oscura.

Verificado en QEMU con `-audiodev wav -machine pcspk-audiodev=spk`:
el PCM crudo trae, en orden, el acorde de bienvenida (~520, 630, 760
Hz), un brevísimo 180 Hz (las balizas de discola/glotona desalojadas)
y después la escala de Do mayor de tonada.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

28c2e6af189bd8467f59a79727354901bbeb1a33 · por sergio · (hace 5 meses) · padre 6a29152fe · ver el árbol en este commit

Delta semántico · 17

renaser/kernel/src/compositor.rs

renaser/kernel/src/drivers/altavoz.rs

renaser/kernel/src/main.rs

renaser/kernel/src/wasm/env.rs

8 archivos · +199 −3

diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md
index 081628c..44b9c6e 100644
--- a/renaser/CHANGELOG.md
+++ b/renaser/CHANGELOG.md
@@ -1008,3 +1008,41 @@ barra al pie con la lista de quien vive en la casa.
   enfocada, índigo), las desalojadas en sus colores de baliza. Un clic
   sobre `pulso` cambia el foco al instante: el borde índigo del compositor
   deja la maestra y envuelve a `pulso`, y la pestaña `pulso` se ilumina.
+
+## Fase 15 — La voz del sistema — 2026-05-23
+
+La bocina pertenecía a la ventana enfocada (Fase 12), pero el kernel necesita
+hablar también: un acorde al abrir la casa, un repique al recibir un inquilino
+nuevo, un bajo al echar a uno que se rompió. La Fase 15 le da al sistema su
+propia voz, prioritaria sobre las de los apps.
+
+### Añadido
+- **`altavoz::SECUENCIA`** — una `VecDeque<(u32, u32)>` (frecuencia, duración
+  ms) que el kernel encola con `altavoz::agendar(&[(...)])`. `FIN_NOTA` (un
+  `AtomicU64`) recuerda el milisegundo del reloj monótono en que la nota
+  actual debe terminar.
+- **`altavoz::atender()`** — invocada por la tarea del compositor cada
+  fotograma; si la nota actual ya terminó, saca la siguiente de la cola y la
+  toca; si la cola está vacía, silencia.
+- **`altavoz::kernel_sonando()`** — `true` mientras `FIN_NOTA` esté en el
+  futuro. `sys_tono` lo consulta y, en ese caso, ignora la llamada del app:
+  el kernel no se interrumpe a sí mismo.
+- **Catálogo de voces**: `VOZ_BIENVENIDA` (Do5-Mi5-Sol5 ascendente, 500 ms),
+  `VOZ_LANZAR` (repique 700→1050 Hz), `VOZ_CERRAR` (descendente 900→520 Hz),
+  `VOZ_DESALOJO` (bajo 180 Hz, 260 ms).
+- **Hitos sonoros**: `kernel_main` agenda `VOZ_BIENVENIDA` justo antes de
+  `ejecutor.run()`. `nacer_ventana` agenda `VOZ_LANZAR`. `cerrar` agenda
+  `VOZ_CERRAR`. `desalojar` agenda `VOZ_DESALOJO`.
+
+### Cambiado
+- Las pestañas de la barra de tareas calculan su tinta por brillo del fondo
+  (ITU-R BT.601): la pestaña amarilla pálida del desalojo por memoria, que
+  llevaba texto blanco invisible, ahora luce su nombre en tinta oscura.
+
+### Verificado
+- QEMU con `-audiodev wav -machine pcspk-audiodev=spk`. El PCM crudo revela,
+  en orden, las tres notas del acorde de bienvenida (≈520, 630, 760 Hz),
+  inmediatamente un brevísimo bajo de 180 Hz (la baliza de discola/glotona
+  desalojadas), y después la escala de `tonada` tomando la bocina.
+- Captura: la pestaña de `glotona` (crema) muestra ahora su nombre legible
+  en tinta oscura; la de `discola` (púrpura) sigue clara, como antes.
diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md
index fe80f58..71b700d 100644
--- a/renaser/CLAUDE.md
+++ b/renaser/CLAUDE.md
@@ -84,8 +84,10 @@ la Fase 11 COMPLETA —el reloj del sistema como capacidad de host
 como capacidad de host (`sys_tono`) + la app `tonada`—, la Fase 13 COMPLETA
 —ratón PS/2, puntero, clic-para-enfocar y arrastre de ventanas flotantes—,
 infraestructura `memory::mmio` (mapeador propio de regiones MMIO en la tabla
-L4) y la Fase 14 COMPLETA —nombres en cada ventana y barra de tareas con
-clic-para-enfocar—. Todo verificado en QEMU. Ver `ROADMAP.md`.
+L4), la Fase 14 COMPLETA —nombres en cada ventana y barra de tareas con
+clic-para-enfocar— y la Fase 15 COMPLETA —la voz del sistema: acorde al
+arrancar, repique al lanzar o cerrar, bajo al desalojar, con prioridad
+sobre `sys_tono`—. Todo verificado en QEMU. Ver `ROADMAP.md`.
 
 ## Flujo de trabajo
 
diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md
index 5b70cff..e23ef7f 100644
--- a/renaser/DIARIO.md
+++ b/renaser/DIARIO.md
@@ -513,6 +513,27 @@ mirada al inquilino elegido, su borde se ilumina, y el escritorio se recoloca
 para honrarlo. Por fin la casa no se navega sólo a tientas con flechas: tiene un
 directorio en su umbral.
 
+## La voz de la casa — el sistema aprende a hablar
+
+La bocina la tenían, hasta hoy, los inquilinos. La casa les prestaba su único
+hilo de sonido y se quedaba muda: por más cosas que ocurrieran —que llegara
+alguien nuevo, que cayera otro, que se abriera la puerta— ella no decía nada,
+sólo lo pintaba. La voz era de quien tuviera la atención puesta encima.
+
+Hoy la casa estrenó voz propia. No para hablar todo el rato —no le hacía
+falta—, sino para los momentos importantes. Cuando despierta entera y queda
+preparada para vivir, lanza al aire un breve acorde de Do mayor: tres notas
+que ascienden como tres ventanas que se van abriendo, una tras otra. Cuando
+un inquilino llega de visita —`Alt+N`—, ella lo recibe con un repique
+ascendente, dos notitas que suben. Cuando uno se despide en paz, con un
+repique descendente. Y cuando uno se cae al suelo y hay que retirarlo, ella
+da un bajo grave de aviso, breve y firme: «atención, hubo un fallo aquí».
+
+Y para no atropellar a los inquilinos cuando ella habla, hay una cortesía
+sencilla: mientras la casa esté diciendo lo suyo, los demás callan. En cuanto
+ella termina, devuelve la bocina al inquilino que la tenía, y la música del
+cuarto enfocado vuelve a sonar donde se quedó.
+
 ---
 
 *El diario continúa. La próxima página la escribirá la próxima jornada.*
diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md
index 08d87dd..fb51b0a 100644
--- a/renaser/ROADMAP.md
+++ b/renaser/ROADMAP.md
@@ -255,6 +255,24 @@ silencioso. Los marcos para tablas intermedias salen del banco DMA. Esta
 mejora resolvió el #PF inexplicable en máquinas con un OVMF que coloca el BAR
 del virtio-blk fuera de los primeros 4 GiB.
 
+## Fase 15 — la voz del sistema (completada)
+
+La bocina pertenecía al app enfocado (Fase 12), pero el kernel necesita hablar
+también: un acorde al arrancar, un repique al lanzar una app, un bajo al
+desalojarla. Verificada en QEMU con captura PCM a WAV.
+
+- `altavoz` gana una cola de notas (`SECUENCIA: Mutex<VecDeque<(u32,u32)>>`) y
+  un reloj de fin (`FIN_NOTA: AtomicU64`). `agendar(&[(frec, ms)])` encola;
+  `atender()` —invocada por la tarea del compositor cada fotograma— pasa a la
+  nota siguiente cuando la actual termina. `kernel_sonando()` gatea a los apps:
+  mientras el kernel habla, `sys_tono` ignora a las apps.
+- Catálogo: `VOZ_BIENVENIDA` (Do-Mi-Sol), `VOZ_LANZAR` (repique ascendente),
+  `VOZ_CERRAR` (descendente), `VOZ_DESALOJO` (bajo grave).
+- Hitos: `kernel_main` agenda el acorde antes de `ejecutor.run`. `nacer_ventana`,
+  `cerrar` y `desalojar` lo agendan al hacer su trabajo.
+- Pestañas de la barra de tareas: tinta calculada por brillo del fondo, así la
+  pestaña crema del desalojo por memoria ya no lleva texto invisible.
+
 Líneas abiertas posteriores: reciclado de las ranuras de ventana cerradas;
 audio con varias voces (PCM) más allá del tono único de la bocina.
 
diff --git a/renaser/kernel/src/compositor.rs b/renaser/kernel/src/compositor.rs
index ec75a64..758227d 100644
--- a/renaser/kernel/src/compositor.rs
+++ b/renaser/kernel/src/compositor.rs
@@ -356,6 +356,8 @@ pub fn desalojar(indice: usize, color: Color) {
         }
         ventana.baliza = Some(color);
     }
+    // Fase 15: la voz del kernel anuncia el desalojo.
+    crate::drivers::altavoz::agendar(&crate::drivers::altavoz::VOZ_DESALOJO);
 
     if escritorio.flotantes.is_empty() {
         let marco = escritorio.ventanas[indice].marco;
@@ -629,7 +631,7 @@ fn recomponer(escritorio: &Escritorio) {
             },
             nombre: &ventana.nombre,
             fondo,
-            tinta: Color::TEXTO,
+            tinta: tinta_para(fondo),
         });
         cx += CELDA_TASKBAR_ANCHO + CELDA_TASKBAR_HUECO;
     }
@@ -690,6 +692,8 @@ fn cerrar() {
         Some(v) if v.baliza.is_none() && !v.cerrada => {}
         _ => return,
     }
+    // Fase 15: el kernel se despide de la app con un repique descendente.
+    crate::drivers::altavoz::agendar(&crate::drivers::altavoz::VOZ_CERRAR);
     // Marcar la baja y liberar el respaldo: la cache de un fotograma puede
     // pesar un megabyte — no tiene sentido retenerla en una ranura inerte.
     let ventana = &mut escritorio.ventanas[foco];
@@ -752,6 +756,8 @@ pub fn nacer_ventana(nat_ancho: usize, nat_alto: usize, nombre: &str) -> usize {
     escritorio.orden.push(indice);
     aplicar_teselado(&mut escritorio);
     recomponer(&escritorio);
+    // Fase 15: el kernel saluda al nacimiento con un repique ascendente.
+    crate::drivers::altavoz::agendar(&crate::drivers::altavoz::VOZ_LANZAR);
     indice
 }
 
@@ -961,6 +967,21 @@ fn area_taskbar(ancho_pantalla: usize, alto_pantalla: usize) -> RegionPantalla {
     }
 }
 
+/// El color de tinta —oscuro o claro— que da contraste legible sobre `fondo`.
+/// Sin esto, la pestaña amarilla palida del desalojo por memoria quedaba con
+/// texto blanco sobre crema: ilegible. La regla de luminancia ITU-R BT.601 fija
+/// el umbral: fondos claros llevan tinta oscura, fondos oscuros la clara.
+fn tinta_para(fondo: Color) -> Color {
+    let brillo =
+        (fondo.r as u32 * 299 + fondo.g as u32 * 587 + fondo.b as u32 * 114) / 1000;
+    if brillo > 160 {
+        // Fondo claro: tinta del reposo del lienzo, casi negra.
+        Color::LIENZO_EN_REPOSO
+    } else {
+        Color::TEXTO
+    }
+}
+
 /// Tesela el area de apps en `n` marcos con el modo dado. El vector resultante
 /// tiene exactamente `n` elementos, en el orden de las celdas del teselado.
 fn teselar(n: usize, ancho: usize, alto: usize, modo: LayoutMode) -> Vec<RegionPantalla> {
diff --git a/renaser/kernel/src/drivers/altavoz.rs b/renaser/kernel/src/drivers/altavoz.rs
index a97151f..8fcc6f4 100644
--- a/renaser/kernel/src/drivers/altavoz.rs
+++ b/renaser/kernel/src/drivers/altavoz.rs
@@ -12,6 +12,10 @@
 //  ofrece al userspace, gobernada por el foco del compositor.
 // =============================================================================
 
+use core::sync::atomic::{AtomicU64, Ordering};
+
+use alloc::collections::VecDeque;
+use spin::Mutex;
 use x86_64::instructions::port::Port;
 
 /// Frecuencia del cristal del PIT, en Hz — el divisor se calcula contra ella.
@@ -71,3 +75,81 @@ fn silenciar() {
         control.write(estado & !0b11);
     }
 }
+
+// =============================================================================
+//  SECUENCIAS DEL KERNEL — la voz propia del sistema (Fase 15)
+// -----------------------------------------------------------------------------
+//  La bocina es de la ventana enfocada (Fase 12), pero el kernel tambien
+//  necesita hablar: un acorde al arrancar, un repique al lanzar una app, un
+//  bajo al desalojarla. Una cola de notas pendientes —`(frecuencia, ms)`— y
+//  un reloj de fin —`FIN_NOTA`— que la tarea del compositor consulta cada
+//  fotograma: si la nota actual ya termino, pasa a la siguiente. Mientras el
+//  kernel suena, las llamadas de los apps a `sys_tono` se ignoran — el
+//  kernel manda en su propia voz.
+// =============================================================================
+
+/// La cola de notas pendientes — `(frecuencia_hz, duracion_ms)`. Solo la
+/// tocan tareas cooperativas: agendar (desde los hitos del kernel) y atender
+/// (desde la tarea del compositor). Ninguna IRQ se la disputa.
+static SECUENCIA: Mutex<VecDeque<(u32, u32)>> = Mutex::new(VecDeque::new());
+
+/// Milisegundo (lectura del reloj monotono) en que la nota actual acaba. Lo
+/// consulta `kernel_sonando` para gatear a las apps.
+static FIN_NOTA: AtomicU64 = AtomicU64::new(0);
+
+/// Agenda una secuencia de notas: cada `(frecuencia_hz, duracion_ms)` se hara
+/// sonar en orden. Un `frecuencia_hz=0` es una pausa silenciosa. Si ya habia
+/// una secuencia sonando, las nuevas notas se encolan al final.
+pub fn agendar(secuencia: &[(u32, u32)]) {
+    let mut cola = SECUENCIA.lock();
+    for &(frec, dur) in secuencia {
+        cola.push_back((frec, dur));
+    }
+}
+
+/// ¿Esta el kernel sonando una nota suya? Mientras dure, las llamadas de los
+/// apps a `sys_tono` quedan silenciadas — el kernel no se interrumpe a si
+/// mismo.
+pub fn kernel_sonando() -> bool {
+    crate::async_system::reloj::milisegundos() < FIN_NOTA.load(Ordering::Relaxed)
+}
+
+/// Atiende el reloj de la secuencia: si la nota actual ya termino, saca la
+/// siguiente de la cola y la hace sonar; si la cola esta vacia, calla la
+/// bocina. La invoca la tarea del compositor cada fotograma.
+pub fn atender() {
+    let ahora = crate::async_system::reloj::milisegundos();
+    if ahora < FIN_NOTA.load(Ordering::Relaxed) {
+        return; // la nota actual sigue sonando
+    }
+    let siguiente = SECUENCIA.lock().pop_front();
+    match siguiente {
+        Some((frec, dur)) => {
+            tono(frec);
+            FIN_NOTA.store(ahora + dur as u64, Ordering::Relaxed);
+        }
+        None => {
+            // Sin notas que sonar: silenciar. Las apps recuperaran la bocina
+            // en cuanto su proxima llamada a `sys_tono` vea `kernel_sonando`
+            // ya en `false`.
+            tono(0);
+        }
+    }
+}
+
+// =============================================================================
+//  CATALOGO DE VOCES — los hitos del sistema y su sonido
+// =============================================================================
+
+/// Acorde de bienvenida: Do — Mi — Sol del Do mayor. Suena una vez, al
+/// completarse el arranque del kernel.
+pub const VOZ_BIENVENIDA: [(u32, u32); 3] = [(523, 130), (659, 130), (784, 240)];
+
+/// Llamada al lanzar una app NUEVA en vivo: dos notas ascendentes.
+pub const VOZ_LANZAR: [(u32, u32); 2] = [(700, 70), (1050, 90)];
+
+/// Llamada al cerrar una app LIMPIAMENTE (`Alt+Q`): dos notas descendentes.
+pub const VOZ_CERRAR: [(u32, u32); 2] = [(900, 70), (520, 100)];
+
+/// Llamada al DESALOJAR una app por falla: un bajo de aviso.
+pub const VOZ_DESALOJO: [(u32, u32); 1] = [(180, 260)];
diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs
index b2a2816..324f116 100644
--- a/renaser/kernel/src/main.rs
+++ b/renaser/kernel/src/main.rs
@@ -159,6 +159,9 @@ async fn tarea_compositor() {
         // vuelta tranquila en que ninguna app pinto.
         compositor::atender_raton();
         compositor::refrescar_puntero();
+        // FASE 15 :: atender la voz del kernel — pasar a la nota siguiente
+        // de la secuencia agendada, o silenciar al acabar.
+        drivers::altavoz::atender();
         // FASE 10 :: atender las altas en vivo. Por cada `Alt+N` pendiente,
         // dar a luz una aplicacion nueva — el compositor solo conto la
         // peticion; instanciar el WASM es trabajo del orquestador.
@@ -524,6 +527,10 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
     // disco de forma ASINCRONA: la demostracion de que la IRQ del disco
     // conduce la E/S sin detener a las aplicaciones visuales.
     ejecutor.spawn(tarea_sonda_disco());
+    // FASE 15 :: la voz del sistema da los buenos dias con un acorde de Do
+    // mayor. La tarea del compositor lo hara sonar nota a nota una vez que
+    // el reactor arranque y las interrupciones empiecen a llegar.
+    drivers::altavoz::agendar(&drivers::altavoz::VOZ_BIENVENIDA);
     traza("ejecutor :: arrancando reactor");
     x86_64::instructions::interrupts::enable();
     ejecutor.run();
diff --git a/renaser/kernel/src/wasm/env.rs b/renaser/kernel/src/wasm/env.rs
index 0d26930..f7efa45 100644
--- a/renaser/kernel/src/wasm/env.rs
+++ b/renaser/kernel/src/wasm/env.rs
@@ -469,6 +469,13 @@ pub(crate) fn enlazar_capacidades(
         "renaser",
         "sys_tono",
         |caller: Caller<'_, ContextoCapacidades>, frecuencia_hz: u32| {
+            // Prioridad del kernel: mientras suena una nota agendada por el
+            // sistema (acorde de bienvenida, repique al lanzar o cerrar una
+            // app, bajo de desalojo), las llamadas de los apps se ignoran. El
+            // kernel no se interrumpe a si mismo en mitad de su voz propia.
+            if crate::drivers::altavoz::kernel_sonando() {
+                return;
+            }
             if crate::compositor::foco() == caller.data().indice_app {
                 crate::drivers::altavoz::tono(frecuencia_hz);
             }