esta es la séptima y última sección del {tutorial de uxn}! aquí hablamos de los dispositivos del ordenador varvara que aún no hemos cubierto: audio, archivo y fechahora o "datetime".
este debería ser un final ligero y tranquilo de nuestro recorrido, ya que tiene que ver menos con la lógica de programación y más con las convenciones de entrada y salida en estos dispositivos.
¡comencemos!
# el dispositivo de archivo
el dispositivo de archivo en el ordenador varvara nos permite leer y escribir en archivos externos.
sus puertos se definen normalmente de la siguiente manera:
* el corto éxito almacena la longitud de los datos que se han leído o escrito con éxito, o cero si ha habido un error
* el corto nombre es para la dirección de memoria donde se almacena el nombre del archivo (terminado en cero, es decir, con un 00)
* el corto largo es la cantidad de bytes a leer o escribir: ¡no olvidemos que la memoria del programa es ffff más 1 byte de largo, y que el programa mismo se almacena allí!
* el short leer es para la dirección de memoria inicial donde los datos de lectura deben ser almacenados
* el corto escribir es para la dirección de memoria inicial donde se almacenan los datos a escribir
* el byte borrar borra el archivo cuando se escribe cualquier valor en él.
* establecer el byte adjuntar a 01 hace que `escribir` añada datos al final del archivo. cuando el byte adjuntar tiene el valor por defecto, 00, `escribir` sobrescribe el contenido desde el principio
una operación de lectura se inicia cuando se escribe en el corto `leer`, y una operación de escritura se inicia cuando se escribe en el corto `escribir`.
¡estos pueden parecer muchos campos para manejar, pero veremos que no son demasiado problema!
## leer un archivo
para leer un archivo, necesitamos saber lo siguiente:
* la ruta del archivo, escrita como una cadena de texto etiquetada en la memoria del programa y terminada por un 00 - esta ruta sería relativa a la ubicación donde se ejecuta uxnemu.
* la cantidad de bytes que queremos leer del archivo: no pasa nada si este número no es igual al tamaño del archivo; puede ser menor o incluso mayor.
* la etiqueta para una sección reservada de la memoria del programa donde se almacenarán los datos leídos
¡y eso es todo!
podemos usar una estructura como la siguiente, donde el nombre del archivo y la memoria reservada están bajo una etiqueta, y la subrutina de carga del archivo bajo otra:
```
@carga-archivo ( -- )
;archivo/nombre .Archivo/nombre DEO2 ( dirección de la ruta del archivo )
( establecer la dirección de los datos a leer y hacer la lectura )
;archivo/datos .Archivo/lectura DEO2
( comprobar el byte éxito y saltar según corresponda )
Archivo/exito DEI2 #0000 EQU2 ,&failed JCN
&exito
LIT 'Y .Consola/escribir DEO
RTN
&fallo
LIT 'N .Consola/escritura DEO
RTN
@archivo
&nombre "prueba.txt 00
&datos $ff ( reservando 255 bytes para los datos )
```
nótese que para el nombre del archivo estamos usando la runa de cadena cruda o `raw` (") que nos permite escribir varios caracteres en la memoria del programa hasta encontrar un espacio en blanco.
en este ejemplo estamos escribiendo un carácter en la consola en función de que el corto `exito` sea cero o no, pero podríamos decidir realizar cualquier acción que consideremos oportuna.
además, en este ejemplo no nos preocupa realmente cuántos bytes se han leído realmente: ¡tenga en cuenta que esta información se almacena en `Archivo/exito` hasta que se produzca otra lectura o escritura!
es importante recordar que, como siempre en este contexto, estamos tratando con bytes crudos.
¡no sólo podemos elegir tratar estos bytes como caracteres de texto, sino que también podemos elegir usarlos como sprites, coordenadas, dimensiones, colores, etc!
## escribir un archivo
para escribir un archivo, necesitamos:
* la ruta del archivo, escrita como una cadena en la memoria del programa como el caso anterior
* la cantidad de bytes que queremos escribir en el archivo
* la etiqueta de la sección de la memoria del programa que escribiremos en el archivo
¡tenga en cuenta que el archivo se sobrescribirá completamente a menos que `adjuntar` se establezca a 01!
el siguiente programa escribirá "hola" y una nueva línea (0a) en un archivo llamado "prueba.txt":
( establecer la dirección de inicio de los datos, y hacer la escritura )
;archivo/datos .Archivo/escribir DEO2
( leer y evaluar el byte éxito )
.Archivo/exito DEI2 #0006 NEQ2 ,&fallo JCN
&exito
LIT 'Y .Consola/escribir DEO
RTN
&fallo
LIT 'N .Consola/escribir DEO
RTN
@archivo
&nombre "prueba.txt 00
&datos "hola 0a
```
¡observe lo similar que es a la subrutina cargar-archivo!
las únicas diferencias, además del uso de `Archivo/escribir` en lugar de `Archivo/leer`, son la longitud del archivo y la comparación para el corto éxito: en este caso sabemos con seguridad cuántos bytes deberían haberse escrito.
## un breve estudio de caso: el archivo de temas
los programas para el ordenador varvara escritos por 100r suelen tener la capacidad de leer un archivo "tema" que contiene seis bytes correspondientes a los tres cortos para los colores del sistema.
estos seis bytes están en orden: los dos primeros son para el canal rojo, los dos siguientes para el canal verde y los dos últimos para el canal azul.
* el byte año corresponde al número de año de la llamada era común
* el byte mes cuenta los meses desde enero (es decir, enero es 0, febrero 1, etc.)
* el byte día cuenta los días del mes a partir del 1
* Los bytes hora, minuto y segundo corresponden a lo que se espera: sus valores van de 0 a 23, o de 0 a 59 respectivamente.
* ddls (día de la semana) es un byte que cuenta los días desde el domingo (es decir, el domingo es 0, el lunes es 1, el martes es 2, etc.).
* dda (día del año) es un byte que cuenta los días desde el 1 de enero (es decir, el 1 de enero es 0, el 2 de enero es 1, etc.)
* eshdv ( es horario de verano) es una bandera, 01 si es horario de verano y 00 si no lo es.
basándonos en esto, debería ser sencillo utilizarlos. por ejemplo, para leer la hora del día en la pila, haríamos:
```
.FechaHora/hora DEI
```
## algunas posibilidades basadas en el tiempo
¡te invito a desarrollar una visualización creativa del tiempo!
tal vez puedas usar estos valores como coordenadas para algunos sprites, o tal vez puedas usarlos como tamaños o límites para figuras creadas con bucles.
o ¿qué tal dibujar sprites condicionalmente, y/o cambiar los colores del sistema dependiendo de la hora? :)
¡también puedes utilizar los valores de la fecha y la hora como semillas para generar alguna pseudo-aleatoriedad!
por último, recuerda que para cronometrar eventos con más precisión que segundos puedes contar las veces que se ha disparado el vector pantalla.
# el dispositivo de audio
por fin, ¡el dispositivo de audio! o debería decir, ¡los dispositivos de audio!
varvara tiene cuatro dispositivos estéreo idénticos (o "canales"), que se mezclan antes de pasar a los altavoces/auriculares:
de forma similar a como en el dispositivo de pantalla podemos dibujar apuntando a direcciones con datos de sprite, en los dispositivos de audio podremos reproducir sonidos apuntando a direcciones con datos de audio (muestras o "samples").
extendiendo la analogía: de forma similar a como podemos dibujar sprites en diferentes posiciones en la pantalla, podemos reproducir nuestras muestras a diferentes velocidades, volumen y envolventes.
supondremos que no estás familiarizado con estos conceptos, así que los discutiremos brevemente.
## muestras
como hemos mencionado anteriormente, podemos pensar en los datos de las muestras como el equivalente a los datos de los sprites.
tienen que estar en la memoria del programa, tienen una longitud que debemos conocer, y podemos referirnos a ellos mediante etiquetas.
el ejemplo piano.tal en el repositorio uxn, tiene varios de ellos, todos de 256 bytes de largo:
en el contexto de varvara, podemos entenderlos como múltiples bytes sin signo (u8) que corresponden a las amplitudes de la onda sonora que componen la muestra.
un "cabezal de reproducción" visita cada uno de estos números durante un tiempo determinado, y los utiliza para establecer la amplitud de la onda sonora.
las siguientes imágenes muestran la forma de la onda (o "waveform") de cada una de estas muestras.
cuando hacemos un bucle con estas formas de onda, ¡obtenemos un tono basado en su forma!
piano-pcm:
=> ./img/screenshot_uxn-waveform_piano.png forma de onda de la muestra de piano
violín-pcm:
=> ./img/screenshot_uxn-waveform_violin.png forma de onda de la muestra de violín
sin-pcm:
=> ./img/screenshot_uxn-waveform_sin.png forma de onda de la muestra de sin
de forma similar a como hemos tratado los sprites, y de forma parecida al dispositivo de archivo comentado anteriormente, para fijar una muestra en el dispositivo de audio sólo tenemos que escribir su dirección y su longitud:
la frecuencia a la que se reproduce esta muestra (es decir, a la que la amplitud de la onda toma el valor del siguiente byte) viene determinada por el byte pitch.
## pitch
el byte pitch hace que la muestra comience a reproducirse cada vez que le escribimos, de forma similar a como el byte de sprite realiza el dibujo del sprite cuando le escribimos.
los primeros 7 bits (de derecha a izquierda) del byte corresponden a una nota midi, y por tanto a la frecuencia a la que se reproducirá la muestra.
el octavo bit es una bandera: cuando es 0 la muestra se reproducirá en bucle, y cuando es 1 la muestra se reproducirá sólo una vez.
normalmente querremos hacer un bucle de la muestra para generar un tono basado en ella. sólo cuando la muestra sea lo suficientemente larga tendrá sentido no hacer un bucle y reproducirla una vez.
con respecto a los bits para la nota midi, es una buena idea tener una tabla midi alrededor para ver los valores hexadecimales correspondientes a las diferentes notas.
=> https://wiki.xxiivv.com/site/midi.html tabla midi
el Do medio (C4, o 3c en midi) se asume como el tono por defecto de las muestras.
### un programa de "muestra"
en teoría, parecería que el siguiente programa debería reproducir nuestra muestra a esa frecuencia, ¿o no?
para poder escuchar el sonido, necesitamos dos cosas más: ajustar el volumen del dispositivo y ajustar la envolvente ADSL.
## volumen
el byte de volumen se divide en dos nibbles: el nibble alto corresponde al volumen del canal izquierdo, y el nibble bajo corresponde al volumen del canal derecho.
por lo tanto, cada canal tiene 16 niveles posibles: 0 es el mínimo, y f el máximo.
lo siguiente establecería el volumen máximo en el dispositivo:
¡aunque las muestras son mono, podemos panoramizarlas con el byte de volumen para obtener un sonido estéreo!
## envolvente ADSR
el último componente que necesitamos para reproducir audio es la envolvente ADSR.
ADSR son las siglas de ataque, decaimiento, sostenimiento y relajación. es el nombre de una "envolvente" común que modula la amplitud de un sonido de principio a fin.
en el ordenador varvara, los componentes ADSR funcionan de la siguiente manera:
* la sección de ataque es el tiempo que tarda en pasar la amplitud del sonido que se está reproduciendo de 0 a 100%.
* luego, la sección de decaimiento es el tiempo que se necesita para llevar la amplitud del 100% al 50%.
* luego, durante la sección de sostenimiento la amplitud se mantiene al 50%
* y finalmente, en la sección de relajación la amplitud pasa del 50% al 0%.
cada una de estas transiciones se realiza de forma lineal.
en el corto ADSR del dispositivo de audio, hay un nibble para cada uno de los componentes: por lo tanto, cada uno puede tener una duración de 0 a f.
las unidades para estas duraciones son 15vos de segundo.
como ejemplo, si la duración del componente de ataque es 'f', entonces durará un segundo (15/15 de segundo, en decimal).
lo siguiente establecerá la duración máxima de cada uno de los componentes, haciendo que el sonido dure 4 segundos en total:
```
#ffff .Audio0/adsr
```
ok, ¡ahora estamos listos para reproducir el sonido!
## reproduciendo la muestra
¡el siguiente programa tiene ahora los cinco componentes que necesitamos para reproducir un sonido: una dirección de muestra, su longitud, las duraciones de adsr, el volumen, y su pitch!
nota (!) que sólo se reproducirá el sonido una vez, y lo hace cuando se inicia el programa.
### algunos experimentos sugeridos
te invito a que experimentes modificando los valores del ADSR: ¿cómo cambia el sonido cuando sólo hay uno de ellos? ¿o cuando todos son números pequeños o con diferentes combinaciones de duraciones?
también, prueba a cambiar el byte pitch: ¿corresponden con tus oídos los valores midi que esperas?
¿y cómo cambia el sonido cuando usas una muestra diferente? ¿puedes encontrar o crear otras diferentes?
## tocar más de una vez
una vez que hemos configurado nuestro dispositivo de audio con una muestra, longitud, envolvente ADSR y volumen, podríamos reproducirlo una y otra vez (re)escribiendo un tono en un momento diferente; los demás parámetros pueden dejarse intactos.
por ejemplo, una macro como la siguiente podría permitirnos reproducir una nota de nuevo según el pitch dado en la parte superior de la pila:
```
%REPR-NOTA { .Audio0/pitch DEO } ( pitch -- )
```
cuando ocurriera un evento específico, podrías llamarlo:
```
#3c REPR-NOTA ( reproducir Do central )
```
ten en cuenta que cada vez que escribes un pitch, la reproducción de la muestra y la forma de la envolvente vuelve a empezar, independientemente de dónde se encuentre.
### algunas ideas
¿qué tal si implementas la reproducción de diferentes tonos presionando diferentes teclas en el teclado? podrías usar nuestros ejemplos anteriores, pero escribiendo un tono en el dispositivo en lugar de, por ejemplo, incrementar una coordenada :)
¿o qué tal si complementas nuestro programa pong de {tutorial de uxn día 6} con efectos de sonido, haciendo que el dispositivo toque una nota cada vez que la pelota rebote?
¿o qué tal si utilizas el vector de la pantalla para cronometrar la reproducción repetitiva de una nota? ¿o qué tal si haces que toque una melodía siguiendo una secuencia de notas? ¿podría venir esta secuencia de un archivo de texto? :)
## información de la reproducción
el dispositivo de audio nos proporciona dos formas de comprobar el estado de la reproducción durante el tiempo de ejecución:
* el corto de posición
* el byte de salida
cuando leemos el corto de posición, obtenemos la posición actual de la "cabeza lectora" en la muestra, empezando por 0 (es decir, la cabeza lectora está al principio de la muestra) y terminando en la longitud de la muestra menos uno.
el byte de salida nos permite leer la amplitud de la envolvente. devuelve 0 cuando la muestra no se está reproduciendo, por lo que se puede utilizar como una forma de saber que la reproducción ha terminado.
## polifonía
la idea de tener cuatro dispositivos de audio es que podemos tenerlos todos sonando a la vez, y cada uno puede tener una muestra, envolvente ADSR, volumen y tono diferentes.
esto nos da muchas más posibilidades:
¿quizás en un juego podría haber una melodía sonando de fondo junto con sonidos incidentales relacionados con la jugabilidad?
¿tal vez se pueda construir un secuenciador en el que se puedan controlar los cuatro dispositivos como pistas diferentes?
¿o tal vez crear una plataforma de livecoding para tener un diálogo con cada uno de los cuatro instrumentos?
en cualquier caso, ¡no dudes en compartir lo que crees! :)
# el final
aunque no lo creas, ¡este es el final!
¡has llegado al final de la serie del tutorial! ¡felicidades!
¡espero que lo hayas disfrutado y que lo veas como el comienzo de tu viaje uxn!
¡nos encantaría ver lo que creas! no dudes en compartirlo en mastodon, en el foro o en el canal irc (¡o incluso por correo electrónico!)
=> https://llllllll.co/t/uxn-virtual-computer/ uxn en el foro lines (inglés)
=> ./contacto.gmi {contacto}
canal irc: #uxn en irc.esper.net
pero antes de hacer todo esto, ¡no te olvides de tomar un descanso! :)
¡nos vemos pronto!
# apoyo
si te ha gustado este tutorial y te ha resultado útil, considera compartirlo y darle tu {apoyo} :)