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>
This commit is contained in:
sergio
2026-05-23 02:21:37 +00:00
parent 6a29152feb
commit 28c2e6af18
8 changed files with 199 additions and 3 deletions
+38
View File
@@ -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.
+4 -2
View File
@@ -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
+21
View File
@@ -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.*
+18
View File
@@ -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.
+22 -1
View File
@@ -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> {
+82
View File
@@ -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)];
+7
View File
@@ -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();
+7
View File
@@ -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);
}