From 28c2e6af189bd8467f59a79727354901bbeb1a33 Mon Sep 17 00:00:00 2001 From: sergio Date: Sat, 23 May 2026 02:21:37 +0000 Subject: [PATCH] =?UTF-8?q?feat(renaser):=20Fase=2015=20=E2=80=94=20la=20v?= =?UTF-8?q?oz=20del=20sistema=20(acorde=20+=20eventos)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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>` (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 --- renaser/CHANGELOG.md | 38 +++++++++++++ renaser/CLAUDE.md | 6 +- renaser/DIARIO.md | 21 +++++++ renaser/ROADMAP.md | 18 ++++++ renaser/kernel/src/compositor.rs | 23 +++++++- renaser/kernel/src/drivers/altavoz.rs | 82 +++++++++++++++++++++++++++ renaser/kernel/src/main.rs | 7 +++ renaser/kernel/src/wasm/env.rs | 7 +++ 8 files changed, 199 insertions(+), 3 deletions(-) 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>`) 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 { 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> = 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); }