compudanzas

tutorial uxn: día 7, más dispositivos

in english: uxn tutorial day 7

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: archivo, fechahora ("datetime") y audio.

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!

los dispositivos de archivo

los dispositivos de archivo en el ordenador varvara nos permiten leer y escribir en archivos externos.

existen dos de ellos que funcionan de una manera idéntica.

puertos

sus puertos se definen normalmente de la siguiente manera:

|a0 @Archivo0 [ &vector $2 &éxito $2 &estad $2 &borrar $1 &adjuntar $1 &nombre $2 &largo $2 &leer $2 &escribir $2 ]
|b0 @Archivo1 [ &vector $2 &éxito $2 &estad $2 &borrar $1 &adjuntar $1 &nombre $2 &largo $2 &leer $2 &escribir $2 ]

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 detalles para manejar, pero veremos que no son demasiado problema!

leer un archivo

en toda la discusión que sigue nos enfocaremos en un solo dispositivo de archivo. como sea, ¡no olvides que para usos más avanzados tienes los dos a tu disposición!

para leer un archivo, necesitamos saber lo siguiente:

¡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 carga-archivo bajo otra:

@carga-archivo ( -- )
    ;archivo/nombre .Archivo0/nombre DEO2 ( dirección de la ruta del archivo )
    #00ff .Archivo0/largo DEO2 ( intentará leer 255 bytes )

    ( establecer la dirección de los datos a leer y hacer  lectura )
    ;archivo/datos .Archivo0/leer DEO2

    ( comprobar el byte éxito y saltar según corresponda )
    Archivo0/éxito DEI2 #0000 EQU2 ,&fallo JCN

    &éxito
        LIT 'Y .Consola/escribe DEO
    RTN

    &fallo
        LIT 'N .Consola/escribe 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 `éxito` sea cero o no, pero podríamos decidir realizar cualquier acción que consideremos apropiada.

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/éxito hasta que se produzca otra lectura o escritura!

es importante recordar que, como siempre en este contexto, estamos tratando con bytes crudos.

¡no solo 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:

¡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":

@guardar-archivo ( -- )
    ;archivo/nombre .Archivo0/nombre DEO2 ( establecer el nombre de archivo )
    #0006 .Archivo0/largo DEO2 ( intentará escribir 6 bytes )

    ( establecer la dirección de inicio de los datos y hacer la escritura )
    ;archivo/datos .Archivo0/escribir DEO2

    ( leer y evaluar el byte éxito )
    .Archivo0/éxito DEI2 #0006 NEQ2 ,&fallo JCN

    &éxito
        LIT 'Y .Consola/escribe DEO
    RTN

    &fallo
        LIT 'N .Consola/escribe 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.

este archivo tiene el nombre de ".theme" y se escribe en un directorio local de nasu cada vez que se guarda una hoja de sprites.

temas uxn

leyendo el archivo de temas

podríamos adaptar nuestra subrutina anterior para cargar el archivo de temas y aplicar sus datos como colores del sistema:

@cargar-tema ( -- )
    ;tema/nombre .Archivo0/nombre DEO2 ( establecer la dirección de la ruta del archivo )
    #0006 .Archivo0/largo DEO2 ( intentará leer 6 bytes )

    ( establecer la dirección de los datos a leer y hacer  lectura )
    ;tema/datos .Archivo0/leer DEO2

    ( comprobar el byte éxito y saltar según corresponda )
    .Archivo0/éxito DEI2 #0006 NEQ2 ,&fallo JCN

    &éxito
        ( establecer los colores del sistema a partir de los datos leídos )
        ;tema/r LDA2 .Sistema/r DEO2
        ;tema/g LDA2 .Sistema/g DEO2
        ;tema/b LDA2 .Sistema/b DEO2
    RTN

    &fallo
RTN

@tema
    &nombre ".theme 00
    &datos ( reservando 6 bytes para los datos: )
    &r $2 &g $2 &b $2

observe cómo las etiquetas &datos y &r apuntan a la misma ubicación: ¡no es un problema! :)

escribiendo el archivo de temas

y para hacer la operación contraria, podemos leer los colores del sistema en nuestro espacio reservado en memoria y luego escribirlos en el archivo:

@guardar-tema ( -- )
    ( leer los colores del sistema en la memoria del programa )
    .Sistema/r DEI2 ;tema/r STA2
    .Sistema/g DEI2 ;tema/g STA2
    .Sistema/b DEI2 ;tema/b STA2

    ;tema/nombre .Archivo0/nombre DEO2 ( establecer la dirección de la ruta del archivo )
    #0006 .Archivo0/leer DEO2 ( intentará escribir 6 bytes )

    ( establecer la dirección de los datos y hacer la escritura )
    ;tema/datos .Archivo0/escribir DEO2

    ( comprobar el byte de éxito y saltar según corresponda )
    .Archivo0/éxito DEI2 #0006 NEQ2 ,&fallo JCN

    &éxito
        ( ¿informar el éxito? )
    RTN

    &fallo
RTN

¡te invito a comparar estas subrutinas con las presentes en los programas 100r como nasu!

código fuente de nasu

el dispositivo fechahora

el dispositivo fechahora (o "datetime") puede ser útil para el cronometraje de baja precisión o para las visualizaciones del tiempo.

tiene varios campos que podemos leer, todos ellos basados en la hora del sistema actual y la zona horaria:

|c0 @FechaHora [ &año $2 &mes $1 &día $1 &hora $1 &minuto $1 &segundo $1 &ddls $1 &dda $2 &eshdv $1 ]

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 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 algo de 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:

|30 @Audio0 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]
|40 @Audio1 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]
|50 @Audio2 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]
|60 @Audio3 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]

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:

@piano-pcm
	8182 8588 8d91 959b a1a6 aaad b2b5 b8bd
	c1c7 cbd0 d5d9 dde1 e5e5 e4e4 e1dc d7d1
	cbc5 bfb8 b2ac a6a2 9c97 928d 8884 807c
	7977 7574 7372 7272 7273 7372 706d 6964
	605b 5650 4d49 4643 4342 4244 4548 4a4d
	5052 5556 5758 5554 5150 4c4a 4744 423f
	3d3c 3a38 3835 3431 3030 2f31 3336 393e
	4449 4e54 5a60 666b 7175 7b82 8990 989e
	a6ab b1b6 babd bebf bfbe bbb9 b6b3 b0ae
	aaa8 a6a3 a19e 9c9a 9997 9696 9798 9b9e
	a1a4 a6a9 a9ac adad adae aeaf b0b0 b1b1
	b3b3 b4b4 b4b3 b3b1 b0ad abab a9a9 a8a8
	a7a5 a19d 9891 8b84 7e77 726e 6b6b 6b6c
	6f71 7477 7776 7370 6c65 5e56 4e48 423f
	3d3c 3b3a 3a39 3838 3839 393a 3c3e 4146
	4a50 575b 6064 686a 6e70 7274 7677 7a7d

@violín-pcm
	8186 8d94 9ba0 a3a7 acb1 b5bc c2c7 cacc
	cecf d0d1 d3d5 d8db dee1 e3e5 e6e5 e5e3
	dfdc d7d0 c8c2 bbb2 a99f 968c 847c 746e
	675f 5851 4b43 3e3a 3533 312e 2c2b 2826
	2422 2122 2327 2d34 3c44 4c57 5f68 7075
	7b80 8487 8789 8a8c 8d90 9397 999c 9ea0
	a2a2 a2a0 9c97 9491 8f8e 908f 918f 8e88
	827a 726a 6058 5047 423f 3f40 4245 4748
	4949 4746 4545 4a4f 5863 717f 8b9a a6b1
	b8be c1c1 bfbd bab5 b1af acac aeb1 b7bc
	c2c9 cfd3 d5d4 d3d3 d1ce cbc6 c0ba b3ab
	a39a 8f85 7b72 6c67 6462 605f 5e5d 5b58
	5550 4d49 4848 4949 4a4d 5052 5558 5b5e
	6164 686c 7074 7677 7979 7a7b 7b7a 7977
	7473 6f6e 6b69 696b 6f72 7576 7574 716b
	655d 554e 4742 3f3f 4045 4b52 5a62 6b74

@sin-pcm
	8083 8689 8c8f 9295 989b 9ea1 a4a7 aaad
	b0b3 b6b9 bbbe c1c3 c6c9 cbce d0d2 d5d7
	d9db dee0 e2e4 e6e7 e9eb ecee f0f1 f2f4
	f5f6 f7f8 f9fa fbfb fcfd fdfe fefe fefe
	fffe fefe fefe fdfd fcfb fbfa f9f8 f7f6
	f5f4 f2f1 f0ee eceb e9e7 e6e4 e2e0 dedb
	d9d7 d5d2 d0ce cbc9 c6c3 c1be bbb9 b6b3
	b0ad aaa7 a4a1 9e9b 9895 928f 8c89 8683
	807d 7a77 7471 6e6b 6865 625f 5c59 5653
	504d 4a47 4542 3f3d 3a37 3532 302e 2b29
	2725 2220 1e1c 1a19 1715 1412 100f 0e0c
	0b0a 0908 0706 0505 0403 0302 0202 0202
	0102 0202 0202 0303 0405 0506 0708 090a
	0b0c 0e0f 1012 1415 1719 1a1c 1e20 2225
	2729 2b2e 3032 3537 3a3d 3f42 4547 4a4d
	5053 5659 5c5f 6265 686b 6e71 7477 7a7d

@tri-pcm
	8082 8486 888a 8c8e 9092 9496 989a 9c9e
	a0a2 a4a6 a8aa acae b0b2 b4b6 b8ba bcbe
	c0c2 c4c6 c8ca ccce d0d2 d4d6 d8da dcde
	e0e2 e4e6 e8ea ecee f0f2 f4f6 f8fa fcfe
	fffd fbf9 f7f5 f3f1 efed ebe9 e7e5 e3e1
	dfdd dbd9 d7d5 d3d1 cfcd cbc9 c7c5 c3c1
	bfbd bbb9 b7b5 b3b1 afad aba9 a7a5 a3a1
	9f9d 9b99 9795 9391 8f8d 8b89 8785 8381
	7f7d 7b79 7775 7371 6f6d 6b69 6765 6361
	5f5d 5b59 5755 5351 4f4d 4b49 4745 4341
	3f3d 3b39 3735 3331 2f2d 2b29 2725 2321
	1f1d 1b19 1715 1311 0f0d 0b09 0705 0301
	0103 0507 090b 0d0f 1113 1517 191b 1d1f
	2123 2527 292b 2d2f 3133 3537 393b 3d3f
	4143 4547 494b 4d4f 5153 5557 595b 5d5f
	6163 6567 696b 6d6f 7173 7577 797b 7d7f

@sierra-pcm
	8282 8183 8384 8685 8888 8889 8a8b 8c8c
	8e8e 8f90 9092 9193 9494 9596 9699 9899
	9b9a 9c9c 9c9d 9ea0 a1a0 a2a2 a3a5 a4a6
	a7a7 a9a8 a9aa aaac adad aeae b0b0 b1b3
	b2b4 b5b5 b6b7 b9b8 b9bb babc bdbc bdbe
	bfc1 bfc1 c3c1 c4c5 c5c6 c6c7 c9c7 cbca
	cbcc cdcd cfcf d2d0 d2d2 d2d5 d4d5 d6d7
	d8d8 d9dc d9df dadf dce1 dde5 dce6 dceb
	cb1f 1b1e 1c21 1c21 1f23 2025 2127 2329
	2529 2829 2a2b 2b2e 2d2f 302f 3231 3234
	3334 3536 3836 3939 3a3b 3b3d 3e3d 3f40
	4042 4242 4444 4646 4748 474a 4a4b 4d4c
	4e4e 4f50 5052 5252 5554 5557 5759 5959
	5b5b 5c5d 5d5f 5e60 6160 6264 6365 6566
	6867 6969 6a6c 6c6d 6d6e 706f 7071 7174
	7475 7576 7777 797a 7a7c 7b7c 7e7d 7f7f

piano.tal código fuente

¿y qué significan estos números?

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:

forma de onda de la muestra de piano

violín-pcm:

forma de onda de la muestra de violín

sin-pcm:

forma de onda de la muestra de sin

tri-pcm:

forma de onda de la muestra de tri

sierra-pcm:

forma de onda de la muestra de sierra

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 solo tenemos que escribir su dirección y su longitud:

;sierra-pcm .Audio0/direc DEO2 ( establecer la dirección de la muestra )
#0100 .Audio0/largo DEO2 ( establecer la longitud de la muestra )

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 tono.

tono

el byte tono 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á solo una vez.

normalmente querremos hacer un bucle de la muestra para generar un tono basado en ella. solo 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.

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?

( hola-sonido.tal )

( dispositivos )
|30 @Audio0 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]

( programa principal )
|0100
    ;sierra-pcm .Audio0/direc DEO2 ( establecer la dirección de la muestra )
    #0100 .Audio0/largo DEO2 ( establecer la longitud de la muestra )

    #3c .Audio0/tono DEO ( establecer el tono como Do medio )
BRK

¡no realmente!

¡pero ya casi! 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:

#ff .Audio0/volumen DEO ( establecer el volumen máximo en izquierda y derecha )

¡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:

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 tono!

( hola-sonido.tal )

( dispositivos )
|30 @Audio0 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]

( programa principal )
|0100
    ;sierra-pcm .Audio0/direc DEO2 ( establecer la dirección de la muestra )
    #0100 .Audio0/largo DEO2 ( establecer la longitud de la muestra )
    #ffff .Audio0/adsr DEO2 ( establecer la envolvente )
    #ff .Audio0/volumen DEO ( establecer el volumen máximo )

    #3c .Audio0/tono DEO ( establecer el tono como Do central )
BRK

nota (!) que solo 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 solo 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 tono: ¿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 tono dado en la parte superior de la pila:

%REPR-NOTA { .Audio0/tono DEO } ( tono -- )

cuando ocurriera un evento específico, podrías llamarlo:

#3c REPR-NOTA ( reproducir Do central )

ten en cuenta que cada vez que escribes un tono, 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 del 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:

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 este 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 la sala xmpp, en el foro o en el canal irc (¡o incluso por correo electrónico!)

xmpp:uxñ@salas.archipielago.uno

uxn en el foro lines (inglés)

canal irc: #uxn en irc.esper.net (inglés)

contacto

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 :)

enlaces entrantes