⎇ 922ad1f86bbef5d4f8c5f59827e5fd6a9b17c1af
renaser/apps/tonada/src/lib.rs
Historia
Quién tocó qué
crudo
feat(renaser): Fase 12 — la bocina del PC como capacidad de host
1// =============================================================================
2// renaser :: apps/tonada — Fase 12 :: una melodia visual para estrenar la voz
3// -----------------------------------------------------------------------------
4// Con la Fase 11 el userspace gano un reloj; con la Fase 12, una voz: la
5// bocina del PC, capacidad `sys_tono`. `tonada` las junta. Toca una escala de
6// Do mayor, una y otra vez, y la dibuja como una escalera de barras — la nota
7// que suena, encendida.
8//
9// Su escena y su sonido son una FUNCION PURA del reloj del host: no guarda
10// estado entre fotogramas. Y la bocina pertenece a la ventana ENFOCADA —como
11// el teclado—: `tonada` pide su tono en cada fotograma, pero solo se oye
12// cuando tiene el foco. Mira la melodia siempre; escuchala cuando la enfocas.
13// =============================================================================
14
15#![no_std]
16
17// --- Las capacidades que el kernel `renaser` inyecta a esta aplicacion. ---
18#[link(wasm_import_module = "renaser")]
19extern "C" {
20 /// Compone un bufer de pixeles (de ESTA memoria lineal) en la region que el
21 /// kernel asigno a esta aplicacion.
22 fn sys_render_frame(ptr: u32, len: u32);
23 /// El reloj monotono del sistema: milisegundos desde el arranque.
24 fn sys_tiempo_mono() -> u64;
25 /// Hace sonar la bocina del PC a una frecuencia (un 0 la silencia). Solo se
26 /// oye si esta ventana tiene el foco — lo decide el host.
27 fn sys_tono(frecuencia_hz: u32);
28}
29
30/// Sin sistema operativo bajo nosotros, un panico solo puede detenerse en seco.
31#[panic_handler]
32fn al_fallar(_: &core::panic::PanicInfo) -> ! {
33 loop {}
34}
35
36// --- Geometria de la escena. El ancho y el alto DEBEN coincidir con la region
37// que el kernel asigna a esta app. ---
38const ANCHO: usize = 360;
39const ALTO: usize = 120;
40
41/// La melodia: una escala de Do mayor ascendente. Frecuencias en Hz —de Do4 a
42/// Do5—. El indice de la nota es, ademas, el indice de su barra en pantalla.
43const MELODIA: [u32; 8] = [262, 294, 330, 349, 392, 440, 494, 523];
44
45/// Duracion de cada nota, en milisegundos.
46const NOTA_MS: u64 = 420;
47
48/// Azul nocturno: el fondo del lienzo.
49const FONDO: u32 = 0x0A_18_30;
50/// Una nota en reposo — una barra de la escalera, apagada.
51const BARRA: u32 = 0x24_30_4E;
52/// La nota que suena AHORA — el indigo brillante del foco del compositor.
53const BARRA_VIVA: u32 = 0x8B_5C_F6;
54
55/// Margen lateral, en pixeles.
56const MARGEN: usize = 22;
57/// La linea base sobre la que se alzan las barras.
58const BASE_Y: usize = 100;
59/// Altura de la barra mas grave, y cuanto crece cada peldaño de la escala.
60const ALTO_MIN: usize = 13;
61const ALTO_PASO: usize = 11;
62
63/// El lienzo de la aplicacion, en SU propia memoria lineal. El kernel jamas lo
64/// ve directamente: solo recibe el (ptr, len) que cada fotograma le entrega.
65static mut LIENZO: [u32; ANCHO * ALTO] = [0; ANCHO * ALTO];
66
67/// Preparacion: el kernel la invoca UNA sola vez, al cargar el modulo.
68#[no_mangle]
69pub extern "C" fn init() {
70 pintar();
71}
72
73/// Un fotograma de trabajo: pide el tono de la nota actual, redibuja la
74/// escalera y RETORNA, cediendo la CPU al kernel y a las apps vecinas.
75#[no_mangle]
76pub extern "C" fn tick() {
77 pintar();
78}
79
80/// Pinta —y suena— la melodia en el instante ACTUAL. No guarda estado alguno:
81/// la nota viva es una funcion pura del reloj del host.
82fn pintar() {
83 // SEGURIDAD: durante `init` y `tick` esta es la unica via de acceso a
84 // LIENZO, y el kernel jamas reentra el modulo mientras una de ellas corre.
85 let lienzo: &mut [u32] = unsafe { &mut *core::ptr::addr_of_mut!(LIENZO) };
86
87 // La nota actual: se deriva SOLO del reloj del host.
88 // SEGURIDAD: `sys_tiempo_mono` es una capacidad del host; no toca memoria.
89 let tiempo = unsafe { sys_tiempo_mono() };
90 let ciclo = NOTA_MS * MELODIA.len() as u64;
91 let actual = ((tiempo % ciclo) / NOTA_MS) as usize;
92
93 // Pedir el tono de la nota actual. El host solo lo hara sonar si esta
94 // ventana tiene el foco — la app pide sin saberlo.
95 // SEGURIDAD: `sys_tono` es una capacidad del host; no toca memoria.
96 unsafe {
97 sys_tono(MELODIA[actual]);
98 }
99
100 // Fondo limpio.
101 for pixel in lienzo.iter_mut() {
102 *pixel = FONDO;
103 }
104
105 // Una barra por nota: la escalera de la melodia. La que suena, encendida.
106 let util = ANCHO - 2 * MARGEN;
107 let ranura = util / MELODIA.len();
108 let barra_ancho = ranura * 3 / 4;
109 let mut i = 0;
110 while i < MELODIA.len() {
111 let altura = ALTO_MIN + i * ALTO_PASO;
112 let x0 = MARGEN + i * ranura + (ranura - barra_ancho) / 2;
113 let color = if i == actual { BARRA_VIVA } else { BARRA };
114 columna(lienzo, x0, barra_ancho, altura, color);
115 i += 1;
116 }
117
118 volcar(lienzo);
119}
120
121/// Pinta una barra vertical: `ancho` pixeles desde la columna `x0`, `altura`
122/// pixeles alzandose sobre la linea base `BASE_Y`. Se recorta al lienzo.
123fn columna(lienzo: &mut [u32], x0: usize, ancho: usize, altura: usize, color: u32) {
124 let x1 = (x0 + ancho).min(ANCHO);
125 let y1 = BASE_Y.min(ALTO);
126 let y0 = y1.saturating_sub(altura);
127 let mut fila = y0;
128 while fila < y1 {
129 let base = fila * ANCHO;
130 let mut col = x0;
131 while col < x1 {
132 lienzo[base + col] = color;
133 col += 1;
134 }
135 fila += 1;
136 }
137}
138
139/// Entrega el lienzo completo al kernel. El (ptr, len) apunta SIEMPRE dentro de
140/// nuestra memoria lineal, y su tamaño es, exactamente, el de la region.
141fn volcar(lienzo: &[u32]) {
142 // SEGURIDAD: `sys_render_frame` es una capacidad del host; el (ptr, len)
143 // describe nuestra propia memoria lineal y el host lo verifica sin piedad.
144 unsafe {
145 sys_render_frame(lienzo.as_ptr() as u32, (ANCHO * ALTO * 4) as u32);
146 }
147}