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
+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);
}