Gráficos y sonidos en lenguaje Ensamblador
GRÁFICOS
Una de las características más interesantes y únicas de las computadoras ATARI son sus excelentes gráficos. En comparación con otras microcomputadoras populares por la calidad de sus gráficos, ATARI es generalmente la clara ganadora. De hecho, la mayoría de los juegos tipo arcade disponibles para varias computadoras diferentes se ven mejor en ATARI y la publicidad habitualmente utiliza fotografías tomadas de la pantalla generada en un ATARI.
"Pero", dirá, "eso sólo está disponible en BASIC". Existe la idea errónea entre los propietarios de ATARI de que los comandos gráficos como PLOT y DRAWTO no se pueden usar sin el cartucho BASIC instalado. De hecho, todas las rutinas gráficas se encuentran en el Sistema Operativo y, por tanto, están disponibles en cualquier lenguaje. Ahora veremos cómo utilizar estas rutinas desde el lenguaje Ensamblador.
Cualquier programa que requiera comandos como GRAPHICS n, PLOT u otros comandos gráficos, generalmente los utiliza muchas veces. Por lo tanto, es más fácil presentar estas rutinas como un conjunto de subrutinas en lenguaje Ensamblador, que puedan llamarse desde cualquier programa. Estas rutinas se pueden guardar en un disco como un grupo y hacerles ENTER en cualquier programa que requiera rutinas gráficas. Para utilizar las rutinas en el programa, tendrá que cargar los registros X e Y y el Acumulador con los parámetros que desea implementar, y luego hacer JSR a la rutina apropiada. Tenga en cuenta que este paso de parámetros se analiza en los comentarios de cada rutina, para que su uso sea claro. La discusión detallada de las subrutinas aparece en la sección que sigue a los listados de los programas.
LAS SUBRUTINAS DE GRÁFICOS DEL LENGUAJE ENSAMBLADOR
Listado 10.1
0100 ; ****************************** 0110 ; Equivalencias CIO 0120 ; ****************************** 0340 0130 ICHID = $0340 0341 0140 ICDNO = $0341 0342 0150 ICCOM = $0342 0343 0160 ICSTA = $0343 0344 0170 ICBAL = $0344 0345 0180 ICBAH = $0345 0346 0190 ICPTL = $0346 0347 0200 ICPTH = $0347 0348 0210 ICBLL = $0348 0349 0220 ICBLH = $0349 034A 0230 ICAX1 = $034A 034B 0240 ICAX2 = $034B E456 0250 CIOV = $E456 0260 ; ****************************** 0270 ; Se necesitan otras equivalencias 0280 ; ****************************** 02C4 0290 COLOR0 = $02C4 0055 0300 COLCRS = $55 0054 0310 ROWCRS = $54 02FB 0320 ATACHR = $02FB 00CC 0330 STORE1 = $CC 00CD 0340 STOCOL = $CD 0000 0350 *= $600 0360 ; ****************************** 0370 ; La rutina SETCOLOR 0380 ; ****************************** 0390 ; Antes de llamar a esta rutina, 0400 ; los registros deben configurarse 0410 ; al igual que el SETCOLOR de BASIC: 0420 ; SETCOLOR color, tono, luminancia 0430 ; almacenado respectivamente en 0440 ; Reg. X, Acumulador, Reg. Y. 0450 SETCOL 0600 0A 0460 ASL A ; Necesitamos multiplicar el 0601 0A 0470 ASL A ; tono por 16, y 0602 0A 0480 ASL A ; sumarlo a la lumimancia. 0603 0A 0490 ASL A ; Ahora que el tono esta * por 16 0604 85CC 0500 STA STORE1 ; temporalmente 0606 98 0510 TYA ; Entonces podemos sumar 0607 18 0520 CLC ; Antes de la suma 0608 65CC 0530 ADC STORE1 ; Ahora tenemos la suma 060A 9DC402 0540 STA COLOR0,X ; SETCOLOR real 060D 60 0550 RTS ; Todo listo 0560 ; ****************************** 0570 ; El comando COLOR 0580 ; ****************************** 0590 ; Para estas rutinas, simplemente 0600 ; almacenamos el valor actual de COLOR 0610 ; en STOCOL, de manera que el comando 0620 ; COLOR simplemente requiere que 0630 ; el Acumulador contenga el valor 0640 ; "n" del comando COLOR n 0650 COLOR 060E 85CD 0660 STA STOCOL ; !Eso es todo! 0610 60 0670 RTS ; Todo listo 0680 ; ****************************** 6900 ; El comando GRAPHICS 0700 ; ****************************** 0710 ; El parametro "n" del 0720 ; comando GRAPHICS n es 0730 ; pasado a esta rutina en el 0740 ; Acumulador 0750 GRAFIC 0611 48 0760 PHA ; Almacenar en la Pila 0612 A260 0770 LDX #$60 ; IOCB6 para pantalla 0614 A90C 0780 LDA #$C ; Comando CLOSE 0616 9D4203 0790 STA ICCOM,X ; en byte del comando 0619 2056E4 0800 JSR CIOV ; Haga el CLOSE 061C A260 0810 LDX #$60 ; La pantalla otra vez 061E A903 0820 LDA #3 ; Comando OPEN 0620 9D4203 0830 STA ICCOM,X ; en byte del comando 0623 A9AD 0840 LDA #NAME&255 ; El nombre es "S:" 0625 9D4403 0850 STA ICBAL,X ; Byte bajo 0628 A906 0860 LDA #NAME/256 ; Byte alto 062A 9D4503 0870 STA ICBAH,X 062D 68 0880 PLA ; Obtener GRAPHICS n 062E 9D4B03 0890 STA ICAX2,X ; Modo grafico 0631 29F0 0900 AND #$F0 ; Obtener los 4 bits mas altos 0633 4910 0910 EOR #$10 ; Dar vuelta el bit alto 0635 090C 0920 ORA #$C ; Lectura o escritura 0637 9D4A03 0930 STA ICAX1,X ; n+16, n+32 etc. 063A 2056E4 0940 JSR CIOV ; Configurar GRAPHICS n 063D 60 0950 RTS ; Todo listo 0960 ; ****************************** 0970 ; El comando POSITION 0980 ; ****************************** 0990 ; Identico al comando 1000 ; POSITION X,Y del BASIC. 1010 ; Dado que X puede ser mayor que 1020 ; 255 en GRAPHICS 8, necesitamos 1030 ; utilizar el Acumulador para el 1040 ; byte alto de X. 1050 POSITN 063E 8655 1060 STX COLCRS ; Byte bajo de X 0640 8556 1070 STA COLCRS+1 ; Byte alto de X 0642 8454 1080 STY ROWCRS ; Posicion Y 0644 60 1090 RTS ; Todo listo 1100 ; ****************************** 1110 ; El comando PLOT 1120 ; ****************************** 1130 ; Usaremos X,Y, y A tal 1140 ; como en el comando POSITION. 1150 PLOT 0645 203E06 1160 JSR POSITN ; Para almacenar informacion 0648 A260 1170 LDX #$60 ; Para la pantalla 064A A90B 1180 LDA #$B ; Poner registro 064C 9D4203 1190 STA ICCOM,X ; Byte del comando 064F A900 1200 LDA #0 ; Caso especial de 0651 9D4803 1210 STA ICBLL,X ; E/S usando el 0654 9D4903 1220 STA ICBLH,X ; Acumulador 0657 A5CD 1230 LDA STOCOL ; Obtener COLOR a usar 0659 2056E4 1240 JSR CIOV ; Trazar el punto 065C 60 1250 RTS ; Todo listo 1260 ; ****************************** 1270 ; El comando DRAWTO 1280 ; ****************************** 1290 ; Usaremos X,Y, y A tal 1300 ; como en el comando POSITION 1310 DRAWTO 065D 203E06 1320 JSR POSITN ; Para almacenar informacion 0660 A5CD 1330 LDA STOCOL ; Obtener COLOR 0662 8DFB02 1340 STA ATACHR ; Mantenga contento al CIO 0665 A260 1350 LDX #$60 ; La pantalla otra vez 0667 A911 1360 LDA #$11 ; Para DRAWTO 0669 9D4203 1370 STA ICCOM,X ; Byte del comando 066C A90C 1380 LDA #$C ; Como en XIO 066E 9D4A03 1390 STA ICAX1,X ; Auxiliar 1 0671 A900 1400 LDA #0 ; Borrar 0673 9D4B03 1410 STA ICAX2,X ; Auxiliar 2 0676 2056E4 1420 JSR CIOV ; Trazar la linea 0679 60 1430 RTS ; Todo listo 1440 ; ****************************** 1450 ; El comando FILL 1460 ; ****************************** 1470 ; Usaremos X,Y, y A tal 1480 ; como en el comando POSITION. 1490 ; Esto es similar a DRAWTO 1500 FILL 067A 203E06 1510 JSR POSITN ; Para almacenar informacion 067D A5CD 1520 LDA STOCOL ; Obtener COLOR 067F 8DFB02 1530 STA ATACHR ; Mantenga contento al CIO 0682 A260 1540 LDX #$60 ; La pantalla otra vez 0684 A912 1550 LDA #$12 ; Para FILL 0686 9D4203 1560 STA ICCOM,X ; Bytel del comando 0689 A90C 1570 LDA #$C ; Como en XIO 068B 9D4A03 1580 STA ICAX1,X ; Auxiliar 1 068E A900 1590 LDA #0 ; Limpiar 0690 9D4B03 1600 STA ICAX2,X ; Auxiliar 2 0693 2056E4 1610 JSR CIOV ; Hacer FILL al area 0696 60 1620 RTS ; Todo listo 1630 ; ****************************** 1640 ; El comando LOCATE 1650 ; ****************************** 1660 ; Usaremos X,Y, y A tal 1670 ; como en el comando POSITION 1680 ; y el Acumulador 1690 ; contendra el color de LOCATE 1700 LOCATE 0697 203E06 1710 JSR POSITN ; Para almacenar informacion 069A A260 1720 LDX #$60 ; La pantalla otra vez 069C A907 1730 LDA #7 ; Obtener registro 069E 9D4203 1740 STA ICCOM,X ; Byte del comando 06A1 A900 1750 LDA #0 ; Caso especial de 06A3 9D4803 1760 STA ICBLL,X ; transferencia de datos 06A6 9D4903 1770 STA ICBLH,X ; en el Acumulador 06A9 2056E4 1780 JSR CIOV ; Haga el LOCATE 06AC 60 1790 RTS ; Todo listo 1800 ; ****************************** 1810 ; El nombre de la pantalla 1820 ; ****************************** 06AD 53 1830 NAME .BYTE "S:",$9B 06AE 3A 06AF 9B
DISCUSIÓN SOBRE LAS SUBRUTINAS GRÁFICAS
El primer punto a tener en cuenta acerca de estas rutinas es que simplemente usan las equivalencias estándar del CIO, que hemos visto tantas veces antes, más seis nuevas. No necesitamos un conjunto completamente nuevo de equivalencias, ya que estamos usando las rutinas estándar del CIO de ATARI para todos los comandos gráficos. De las 6 nuevas equivalencias, 2 son simplemente ubicaciones de almacenamiento: STOCOL se usa para almacenar la información de COLOR utilizada en varias de las rutinas, y STORE1 se usa para el almacenamiento temporal de información. Estos se encuentran ubicados arbitrariamente en $CD y $CC respectivamente, pero puede ponerlos en cualquier ubicación de memoria segura. Uno de esos lugares sería $100 y $101, que son las dos ubicaciones inferiores de la Pila. Otra de las nuevas equivalencias es COLOR0, que es la primera de las 5 ubicaciones utilizadas para almacenar información de color en las computadoras ATARI, que se encuentra en las posiciones decimales 708 al 712. La segunda es COLCRS, una ubicación de almacenamiento de 2 bytes ubicada en las direcciones $55 y $56, que siempre almacena la ubicación de la columna actual del cursor. Dado que en GRAPHICS 8 hay 320 ubicaciones horizontales posibles y sabemos que cada byte puede almacenar solo 256 valores posibles, necesitamos 2 bytes para almacenar todas las posiciones horizontales posibles del cursor. Sin embargo, en todos los modos gráficos distintos del GRAPHICS 8, es obvio que la ubicación $56 siempre será igual a cero. La tercera nueva equivalencia es ROWCRS, ubicación $54, que simplemente realiza un seguimiento de la posición vertical del cursor. Ningún modo gráfico tiene más de 192 posibles posiciones verticales del cursor, por lo que sólo se requiere 1 byte para almacenar esta información. La nueva equivalencia final es ATACHR, ubicación $2FB, que se utiliza para almacenar el color de la línea que se dibuja en las rutinas FILL y DRAWTO.
Estas rutinas se han ensamblado utilizando un origen de $600 por conveniencia. Si planea usarlos en un programa de lenguaje Ensamblador más grande, simplemente vuelva a numerar estas subrutinas a algunos números de línea altos, como 25000 y más, y combine estas rutinas con su programa antes de ensamblarlo. De esta manera, tendrá todos los comandos gráficos normales disponibles en lenguaje Ensamblador, sin necesidad de ingresarlos laboriosamente en cada programa que escriba.
La primera rutina es el equivalente en lenguaje Ensamblador del comando SETCOLOR del BASIC. Sabemos que esta es la forma estándar del comando:
SETCOLOR registro de color, tono, luminancia
En la subrutina en lenguaje Ensamblador, primero necesitamos cargar los registros del 6502 con la información equivalente. Una rutina de llamada típica para simular el comando BASIC
SETCOLOR 2,4,10
sería la siguiente:
25 LDX #2 30 LDA #4 35 LDY #10 40 JSR SETCOL
Usamos la forma de 6 letras del nombre SETCOL en vez de SETCOLOR para que esta rutina sea compatible con todos los ensambladores disponibles para su ATARI. Si el ensamblador que está utilizando permite nombres de etiquetas de más de 6 caracteres, no dude en utilizar el nombre completo de la rutina. Esta misma convención se utilizará para todas las rutinas gráficas, por ejemplo, POSITN en vez de POSITION.
Para ejecutar el comando SETCOLOR, necesitamos sumar la luminancia con 16 veces el tono y almacenar el resultado en el registro de color apropiado. Para multiplicar el tono por 16, simplemente usaremos el Acumulador y realizaremos cuatro instrucciones ASL A. Como cada uno duplica el valor contenido en el Acumulador, el resultado es 16 veces el valor inicial. Después de la multiplicación, almacenaremos el resultado en nuestra ubicación de almacenamiento temporal y colocaremos la luminancia en el Acumulador con una instrucción TYA, preparándolo para la suma. Luego borramos el bit de Acarreo, como es habitual antes de la suma, y sumamos el resultado de nuestra multiplicación anterior a la luminancia. Finalmente, utilizamos el valor en el registro X, que es el registro de color deseado, como índice de las cinco ubicaciones de registros de color descritas anteriormente. Como en este ejemplo queremos hacer SETCOLOR 2, cargamos el registro X con 2 antes de llamar a la subrutina y la información del color se almacena en $2C6.
La siguiente rutina, el comando COLOR, es con diferencia la más sencilla de todas las rutinas. Para llamar al equivalente COLOR del comando BASIC
COLOR 3
simplemente necesitamos el siguiente código en lenguaje Ensamblador:
25 LDA #3 30 JSR COLOR
La rutina simplemente almacena el color seleccionado en nuestra ubicación de almacenamiento de color, STOCOL, donde estará disponible para las otras rutinas gráficas que lo requieran.
El comando GRAPHICS se implementa de manera similar. Para imitar el comando BASIC
GRAPHICS 23
simplemente usamos el siguiente código en lenguaje Ensamblador:
25 LDA #23 30 JSR GRAFIC
Lo primero que debemos hacer es almacenar el modo gráfico requerido. Podríamos almacenarlo en STORE1, pero colocarlo en la Pila es más rápido en este caso. No necesitamos hacer sumas o multiplicaciones, como hicimos en SETCOLOR. Las siguientes cuatro líneas de código simplemente cierran la pantalla como dispositivo. Esto es para estar seguros. Si la pantalla ya está cerrada, no hace daño. Sin embargo, si está abierto e intentamos volver a abrirlo, obtendremos un error, por lo que lo cerramos primero por seguridad. Tenga en cuenta que simplemente usando IOCB6 (Cargando el registro X con $60), especificamos la pantalla, usando el número de dispositivo predeterminado asignado por el ATARI.
El resto del comando GRAPHICS simplemente abre la pantalla en el modo gráfico particular que deseemos. En la línea 830 usamos nuevamente IOCB6, almacenando el comando OPEN en el byte de comando del IOCB. El nombre de la pantalla es S:, y cargamos la dirección de este nombre en ICBAL e ICBAH. Luego, el modo gráfico se recupera desde la Pila y se almacena en el segundo byte auxiliar. Los únicos bits importantes del modo gráfico en ICAX2 son los 4 bits inferiores, que especifican el modo gráfico en sí, en este caso, GRAPHICS 7. Los 4 bits superiores controlan el borrado de la pantalla, la presencia de la ventana de texto, etc., tal como se describe en el Capítulo 8. En este caso, hemos sumado 16 al modo gráfico, para eliminar la ventana de texto. Para aislar estos bits, hacemos AND entre el modo gráfico con $F0, lo que produce el nibble alto del modo gráfico. El Sistema Operativo requiere que el bit superior de esta información se invierta, por lo que A continuación, aplicamos EOR a este nibble con $10 para invertir el bit superior. Finalmente, configuramos el nibble bajo de este byte en $C, para permitir leer o escribir en la pantalla, y almacenamos el byte en ICAX1 del IOCB. La llamada al CIO completa nuestra rutina gráfica y configura la pantalla tal como queremos.
Como ya hemos comentado, el comando POSITION para GRAPHICS 8 requiere 320 ubicaciones X posibles, por lo que necesitamos 2 bytes para contener un número tan grande. Por lo tanto, para simular el comando
POSITION 285,73
Almacenamos el byte bajo de la coordenada X en el registro X, el byte alto en el Acumulador y la coordenada Y en el registro Y, de la siguiente manera:
25 LDX #30 30 LDA #1 35 LDY #73 40 JSR POSITN
Obviamente, en cualquier modo gráfico que no sea GRAPHICS 8, el Acumulador siempre se carga con un cero antes de llamar a POSITN, y el registro X simplemente contiene la coordenada X. La rutina misma almacena la información apropiada en las ubicaciones requeridas. La coordenada X se almacena en COLCRS y COLCRS+1, y la coordenada Y se almacena en ROWCRS.
El comando PLOT de
PLOT 258,13
en BASIC se simula mediante el siguiente código en lenguaje Ensamblador:
25 LDX #3 30 LDA #1 35 LDY #13 40 JSR PLOT
Esto utiliza la misma convención que el comando POSITN anterior. De hecho, la rutina PLOT comienza con un JSR POSITN, que almacena la información pasada a la rutina en las ubicaciones correctas para que las utilice el Sistema Operativo después de la llamada al CIO. Como queremos mostrar la salida en la pantalla, usamos IOCB6 y el byte de comando es $B para poner registro (put record). En este caso, simplemente queremos generar un solo byte de información, por lo que usamos el caso especial de E/S del Acumulador al que se accede estableciendo la longitud del buffer de salida en cero. Luego cargamos el Acumulador con la información del color que queremos trazar y la llamada al CIO traza el punto por nosotros.
Las rutinas para DRAWTO y FILL son tan similares que las discutiremos juntas. La secuencia de llamada es idéntica a los comandos PLOT y POSITION, por lo que para imitar el comando BASIC
DRAWTO 42,80
usamos la secuencia
25 LDX #42 30 LDA #0 35 LDY #80 40 JSR DRAWTO
Para usar el comando FILL, simplemente cambie la línea 40 a JSR FILL.
La rutina comienza con una llamada a la rutina POSITN para almacenar la información requerida. La información de color luego se almacena en ATACHR, y usamos IOCB6 nuevamente, cargando ICCOM con $11 para DRAWTO y con $12 para FILL. ICAX1 necesita un $C y limpiamos ICAX2 antes de completar la rutina llamando al CIO. Estas rutinas son absolutamente análogas a los respectivos comandos BASIC XIO que logran los mismos fines. Por ejemplo, para dibujar una línea, podemos usar este comando:
XIO 17,#6,12,0,"S:"
En este comando, el 17 es el byte del comando $11, el #6 es el número IOCB, el 12 se almacena en ICAX1, el cero en ICAX2 y el nombre del dispositivo es S:. Nuevamente, se puede usar exactamente el mismo comando XIO para llenar un área, simplemente cambiando el 17 por el 18 ($12).
La rutina final, el comando LOCATE, es prácticamente idéntica al comando PLOT, excepto que usamos el comando obtener registro (get record), en lugar del comando poner registro. Se hace el mismo uso del modo especial de E/S del Acumulador de un solo byte, poniendo a cero tanto ICBLL como ICBLH. La rutina de llamada para duplicar el comando BASIC
LOCATE 10,12,A
es como sigue:
25 LDX #10 30 LDA #0 35 LDY #12 40 JSR LOCATE
En este caso, el Acumulador contendrá el valor de color encontrado en las coordenadas 10, 12 después de la llamada a la rutina LOCATE, por lo que un comando STA podría guardar esta información, o podría usarse inmediatamente, comparándola con algún valor deseado. O de alguna otra manera.
Con esto concluye la discusión sobre las contrapartes en lenguaje Ensamblador de los comandos gráficos del BASIC. Úselo en algunos programas sencillos y verá cómo pronto le resultan familiares y fáciles de usar. De hecho, son casi tan fáciles de usar como los comandos BASIC. Sin embargo, dado que tanto BASIC como el lenguaje Ensamblador utilizan las mismas rutinas del Sistema Operativo para realizar operaciones como DRAWTO y FILL, no espere que las rutinas del lenguaje Ensamblador sean mucho más rápidas que las rutinas BASIC a las que está acostumbrado. Serán un poco más rápidos, ya que no tiene que pagar los gastos generales que BASIC requiere en términos de tiempo, pero no experimentará ni de lejos la diferencia en velocidad que ahora espera al convertir de BASIC a programación en lenguaje Ensamblador. Para lograr este tipo de aceleración, tendrá que escribir sus propias rutinas DRAWTO y FILL, usando una lógica totalmente diferente a la utilizada por el Sistema Operativo del ATARI. Estas rutinas son mucho más rápidas que las rutinas del Sistema Operativo, pero no son de dominio público y tendrá que escribir las suyas si la velocidad es un punto crítico.
Ahora que domina el lenguaje Ensamblador, es posible que desee cambiar las rutinas centrales del ATARI para sus propios fines. Si desea probar esto, se recomienda encarecidamente comprar las notas técnicas del usuario y los listados del Sistema Operativo. Luego puede consultar el código fuente comentado de las rutinas del Sistema Operativo y modificarlo para sus propias rutinas. Simplemente inclúyalos como parte de sus propios programas, realizando las modificaciones que desee. Sin embargo, recuerde que el código del Sistema Operativo pertenece a ATARI. Puede utilizar dichas modificaciones en programas para su propio uso, pero asegúrese de obtener el permiso de ATARI antes de intentar ofrecer a la venta cualquier programa que contenga partes del Sistema Operativo. Un cambio fácil de probar es permitir trazar y dibujar sin comprobar si el cursor está fuera de rango, lo que ralentiza bastante el proceso. Sólo asegúrese de que su programa calcule los valores correctamente, o si no...
Recuerde que todo lo que es posible desde el BASIC también lo es en lenguaje Ensamblador. Un ejemplo frecuentemente utilizado de esto es la animación mediante la rotación de los registros de color, posible usando los modos especiales GTIA o incluso los modos gráficos regulares. Una rutina muy sencilla puede rotar los registros de color estándar de forma prácticamente instantánea:
15 LDA $708 20 STA STOCOL 25 LDA $709 30 STA $708 35 LDA $710 40 STA $709 45 LDA $711 50 STA $710 55 LDA $712 60 STA $711 65 LDA STOCOL 70 STA $712
Ahora que puede dibujar gráficos detallados a partir de programas en lenguaje Ensamblador. Este truco se puede utilizar para animar imágenes prácticamente sin ralentización en la ejecución del programa. Por ejemplo, la implementación de un juego del tipo "abajo de la trinchera" es sencilla, ya que permite que la rotación de los colores dé la ilusión de movimiento a lo largo de la trinchera.
GRÁFICOS PLAYER-MISSILE DESDE EL LENGUAJE ENSAMBLADOR
Otra característica interesante de las computadoras ATARI son los gráficos Player-Missile. Ya hemos visto un ejemplo usando una subrutina en lenguaje Ensamblador para mover un Player. Pero en ese programa, toda la configuración para los gráficos Player-Missile estaba en BASIC, y sólo la rutina para mover el Player estaba en lenguaje Ensamblador. Para mostrar cómo realizar estas mismas operaciones en un programa puramente en lenguaje Ensamblador, se presenta A continuación, este programa BASIC totalmente traducido al lenguaje Ensamblador En este programa se utilizan en su mayor parte direcciones decimales, ya que así fue escrito el programa BASIC; este programa en lenguaje Ensamblador es lo más similar al programa BASIC.
Listado 10.2
0100 ; ****************************** 0110 ; Equivalencias CIO 0120 ; ****************************** 0340 0130 ICHID = $0340 0341 0140 ICDNO = $0341 0342 0150 ICCOM = $0342 0343 0160 ICSTA = $0343 0344 0170 ICBAL = $0344 0345 0180 ICBAH = $0345 0346 0190 ICPTL = $0346 0347 0200 ICPTH = $0347 0348 0210 ICBLL = $0348 0349 0220 ICBLH = $0349 034A 0230 ICAX1 = $034A 034B 0240 ICAX2 = $034B E456 0250 CIOV = $E456 0260 ; ****************************** 0270 ; Se necesitan otras equivalencias 0280 ; ****************************** 00CC 0290 YLOC = $CC ; Direccion indirecta para Y 00CE 0300 XLOC = $CE ; Para recordar la posicion X 00D0 0310 INITX = $D0 ; Valor inicial de X 00D1 0320 INITY = $D1 ; Valor inicial de Y 0100 0330 STOTOP = $100 ; Almacenamiento temporal (ADRESS) D300 0340 STICK = $D300 ; PORTA - Ubicacion Hardware STICK(0) D000 0350 HPOSP0 = $D000 ; Posicion Horizontal del Player 0 0000 0360 *= $600 0370 ; ****************************** 0380 ; Primero, bajamos la parte superior de la RAM 0390 ; ****************************** 0600 A56A 0400 LDA 106 ; Obtenemos la parte superior de la RAM 0602 8D0001 0410 STA STOTOP ; Almacenamiento temporal 0605 38 0420 SEC ; Configurar para restar 0606 E908 0430 SBC #8 ; Guardar 8 paginas para PMG 0608 856A 0440 STA 106 ; Digale a Atari cual es el nuevo RAMTOP 060A 8D07D4 0450 STA 54279 ; PMBASE 060D 85CF 0460 STA XLOC+1 ; Para borrar RAM de PM 060F A900 0470 LDA #0 ; ponga la direccion 0611 85CE 0480 STA XLOC ; indirecta aqui. 0490 ; ****************************** 0500 ; A continuacion, reinicie GRAPHICS 0 0510 ; ****************************** 0613 A900 0520 LDA #0 ; GRAPHICS 0 0615 48 0530 PHA ; Almacenar en la Pila 0616 A260 0540 LDX #$60 ; IOCB6 para pantalla 0618 A90C 0550 LDA #$C ; comando CLOSE 061A 9D4203 0560 STA ICCOM,X ; en byte del comando 061D 2056E4 0570 JSR CIOV ; Haga CLOSE 0620 A260 0580 LDX #$60 ; La pantalla otra vez 0622 A903 0590 LDA #3 ; comando OPEN 0624 9D4203 0600 STA ICCOM,X ; en byte del comando 0627 A9ED 0610 LDA #NAME&255 ; El nombre es "S:" 0629 9D4403 0620 STA ICBAL,X ; Byte bajo 062C A906 0630 LDA #NAME/256 ; Byte alto 062E 9D4503 0640 STA ICBAH,X 0631 68 0650 PLA ; Obtener GRAPHICS 0 0632 9D4B03 0660 STA ICAX2,X ; Modo gradicto 0635 29F0 0670 AND #$F0 ; Obtener los 4 bits altos 0637 4910 0680 EOR #$10 ; Voltear bit alto 0639 090C 0690 ORA #$C ; Leer o escribir 063B 9D4A03 0700 STA ICAX1,X ; n+16, n+32 etc. 063E 2056E4 0710 JSR CIOV ; Configurar GRAPHICS 0 0720 ; ****************************** 0730 ; Ahora configure los graficos Player/Missile 0740 ; ****************************** 0641 A978 0750 LDA #120 ; Valor X inicial 0643 85D0 0760 STA INITX ; Pongalo en su lugar 0645 A932 0770 LDA #50 ; Valor Y inicial 0647 85D1 0780 STA INITY ; Pongalo en su lugar 0649 A92E 0790 LDA #46 ; Linea de doble 064B 8D2F02 0800 STA 559 ; resolucion - SDMCTL 0810 ; ****************************** 0820 ; Ahora borre el area de los PM en la RAM 0830 ; ****************************** 064E A000 0840 LDY #0 ; Usar como contador 0850 CLEAR 0650 A900 0860 LDA #0 ; Byte a almacenar 0652 91CE 0870 STA (XLOC),Y ; Borrar el 1er byte 0654 88 0880 DEY ; ?Pagina terminada? 0655 D0FB 0890 BNE CLEAR ; Pagina no terminada 0657 E6CF 0900 INC XLOC+1 ; Pagina terminada 0659 A5CF 0910 LDA XLOC+1 ; On to next page 065B CD0001 0920 CMP STOTOP ; ?Terminamos? 065E F0F2 0930 BEQ CLEAR ; Una pagina mas 0660 90F0 0940 BCC CLEAR ; Continue 0950 ; ****************************** 0960 ; Ahora insertaremos el Player en 0970 ; el lugar apropiado en el 0980 ; area RAM de los PMG 0990 ; ****************************** 0662 A56A 1000 LDA 106 ; Primero, calcule la 0664 18 1010 CLC ; posicion correcta de Y. 0665 6902 1020 ADC #2 ; PMBASE+512 (2 paginas) 0667 85CD 1030 STA YLOC+1 ; Byte alto de YLOC 0669 A5D1 1040 LDA INITY ; Sumar coordenada Y de la pantalla 066B 85CC 1050 STA YLOC ; Para el byte bajo 066D A000 1060 LDY #0 ; Como contador 1070 INSERT 066F B9F006 1080 LDA PLAYER,Y ; Obtener byte del Player 0672 91CC 1090 STA (YLOC),Y ; Pongalo en su lugar 0674 C8 1100 INY ; Para el siguiente byte 0675 C008 1110 CPY #8 ; ?Terminamos? 0677 D0F6 1120 BNE INSERT ; No 0679 A5D0 1130 LDA INITX ; Obtener X inicial 067B 8D00D0 1140 STA HPOSP0 ; Digale al Atari 067E 85CE 1150 STA XLOC ; Tambien aqui 0680 A944 1160 LDA #68 ; Hacer que el Player sea rojo 0682 8DC002 1170 STA 704 ; como en BASIC - PCOLR0 0685 A903 1180 LDA #3 ; Para habilitar los graficos 0687 8D1DD0 1190 STA 53277 ; Player/Missile - GRACTL 1200 ; ****************************** 1210 ; El ciclo principal - muy corto 1220 ; ****************************** 1230 MAIN 068A 209A06 1240 JSR RDSTK ; Leer palanca - mover el player 068D A205 1250 LDX #5 ; Para controlar al 068F A000 1260 LDY #0 ; Player, tenemos 1270 DELAY 0691 88 1280 DEY ; que agregar 0692 D0FD 1290 BNE DELAY ; un retraso: esta 0694 CA 1300 DEX ; rutina ralentiza 0695 D0FA 1310 BNE DELAY ; las cosas. 0697 4C8A06 1320 JMP MAIN ; Y hazlo de nuevo 1330 ; ****************************** 1340 ; Ahora lea el joystick #1 1350 ; ****************************** 1360 RDSTK 069A AD00D3 1370 LDA STICK ; Obtener el valor del joystick 069D 2901 1380 AND #1 ; ?Es el bit 0 = 1? 069F F016 1390 BEQ UP ; No - 11, 12 o 1 en punto 06A1 AD00D3 1400 LDA STICK ; Lo obtenemos de nuevo 06A4 2902 1410 AND #2 ; ?Es el bit 1 = 1? 06A6 F020 1420 BEQ DOWN ; No - 5, 6 o 7 en punto 06A8 AD00D3 1430 SIDE LDA STICK ; Lo obtenemos de nuevo 06AB 2904 1440 AND #4 ; ?Es el bit 3 = 1? 06AD F02E 1450 BEQ LEFT ; No - 8, 9 o 10 en punto 06AF AD00D3 1460 LDA STICK ; Lo obtenemos de nuevo 06B2 2908 1470 AND #8 ; ?Es el bit 4 = 1? 06B4 F02F 1480 BEQ RIGHT ; No - 2, 3 o 4 en punto 06B6 60 1490 RTS ; Joystick hacia arriba 1500 ; ****************************** 1510 ; Ahora mueva el Player apropiadamente, 1520 ; comenzando con un movimiento ascendente. 1530 ; ****************************** 06B7 A001 1540 UP LDY #1 ; Configuracion para mover el byte 1 06B9 C6CC 1550 DEC YLOC ; Ahora 1 menos que YLOC 06BB B1CC 1560 UP1 LDA (YLOC),Y ; Obtener 1er byte 06BD 88 1570 DEY ; Para subirlo una posicion 06BE 91CC 1580 STA (YLOC),Y ; Muevalo 06C0 C8 1590 INY ; Ahora el valor original 06C1 C8 1600 INY ; Ahora configurado para el siguiente byte 06C2 C00A 1610 CPY #10 ; ?Terminamos? 06C4 90F5 1620 BCC UP1 ; No 06C6 B0E0 1630 BCS SIDE ; !!!Bifurcacion forzada!!! 1640 ; ****************************** 1650 ; Mover el Player hacia abajo 1660 ; ****************************** 06C8 A007 1670 DOWN LDY #7 ; Mover el byte superior primero 06CA B1CC 1680 DOWN1 LDA (YLOC),Y ; Obtener el byte superior 06CC C8 1690 INY ; para moverlo hacia abajo en la pantalla 06CD 91CC 1700 STA (YLOC),Y ; Muevalo 06CF 88 1710 DEY ; Ahora volvamos al valor inicial 06D0 88 1720 DEY ; Prepararse para el siguiente byte inferior 06D1 10F7 1730 BPL DOWN1 ; Si Y >= 0 continue 06D3 C8 1740 INY ; Establecer en cero 06D4 A900 1750 LDA #0 ; para borrar el byte superior 06D6 91CC 1760 STA (YLOC),Y ; Limpielo 06D8 E6CC 1770 INC YLOC ; Ahora es 1 mayor 06DA 18 1780 CLC ; Configuracion para bifurcacion forzada 06DB 90CB 1790 BCC SIDE ; Bifurcacion forzada otra vez 1800 ; ****************************** 1810 ; Ahora de lado a lado: primero a la izquierda 1820 ; ****************************** 06DD C6CE 1830 LEFT DEC XLOC ; Para moverlo a la izquierda 06DF A5CE 1840 LDA XLOC ; Obtengalo 06E1 8D00D0 1850 STA HPOSP0 ; Muevalo 06E4 60 1860 RTS ; Volver a MAIN - terminamos 1870 ; ****************************** 1880 ; Ahora movimiento a la derecha 1890 ; ****************************** 06E5 E6CE 1900 RIGHT INC XLOC ; Para moverlo a la derecha 06E7 A5CE 1910 LDA XLOC ; Obtengalo 06E9 8D00D0 1920 STA HPOSP0 ; Muevalo 06EC 60 1930 RTS ; Volver a MAIN - terminamos 1940 ; ****************************** 1950 ; Declaraciones DATA 1960 ; ****************************** 06ED 53 1970 NAME .BYTE "S:",$9B 06EE 3A 06EF 9B 06F0 FF 1980 PLAYER .BYTE 255,129,129,129,129,129,129,255 06F1 81 06F2 81 06F3 81 06F4 81 06F5 81 06F6 81 06F7 FF
Este programa utiliza muchas de las rutinas que ya hemos comentado: una rutina para borrar el área de memoria de los Player/Missile, lectura del joystick y movimiento del Player, y el comando GRAPHICS 0. Aquí, simplemente los juntamos todos en un programa grande que realiza todas las tareas necesarias para implementar un ejemplo simple de gráficos Player/Missile en lenguaje Ensamblador.
Dado que es análogo al programa BASIC que ya hemos escrito, la versión en Assembler comienza reduciendo RAMTOP en 8 páginas para dejar espacio para la memoria de los Player/Missile: Las líneas 400 a 440 realizan esta función. La línea 410 almacena el valor antiguo de RAMTOP para la rutina de borrado posterior. La línea 450 le dice al ATARI cual es la ubicación de PMBASE, el nuevo valor de RAMTOP. Usaremos XLOC y XLOC+1 como ubicación de dirección indirecta temporal en la Página Cero, para ayudar en la rutina de borrado. Las líneas 520 a 710 luego restablecen una pantalla GRAPHICS 0 debajo de la nueva ubicación de RAMTOP. Las líneas 750 a 780 simplemente almacenan los valores iniciales de X e Y, las coordenadas en la pantalla donde queremos que aparezca el Player. Estos valores se utilizarán más adelante y estas líneas están aquí sólo para mantener la analogía con el programa BASIC. No es necesario almacenar estos valores. También podríamos haber usado los números directamente más adelante en el programa. Sin embargo, cualquiera de las dos formas funciona y es un poco más fácil cambiar el programa más adelante si está escrito de esta manera. Las líneas 790 y 800 configuran una resolución de doble línea, y luego borramos toda el área del Player/Missile en las líneas 840 a 940.
En el programa BASIC, el lugar en la memoria donde insertamos el Player para lograr la posición Y correcta en la pantalla es:
PMBASE+512+INITY
Sabemos que 512 bytes por encima de PMBASE son 2 páginas, ya que cada página contiene 256 bytes. Por lo tanto, sabemos que el byte alto de esta dirección, en nuestro programa en lenguaje Ensamblador, debe ser 2 más que PMBASE. En las líneas 1000 a 1030, obtenemos PMBASE, sumamos 2 y almacenamos el resultado en YLOC+1, el byte alto de la posición Y en la memoria. El byte bajo es simplemente INITY, la posición Y inicial. Recuerde: Cuanto más abajo en la pantalla desee que aparezca el Player, más arriba en la memoria deberá almacenarse.
Para insertar el Player en el lugar correcto de la memoria, leemos un byte a la vez de la tabla de datos llamada PLAYER y lo almacenamos mediante direccionamiento indirecto en la ubicación de memoria que acabamos de configurar. Cuando Y, nuestro contador, es igual a 8, terminamos, ya que comenzamos en cero y solo tenemos 8 bytes para transferir. Si nuestro Player hubiera sido más grande, simplemente habríamos tenido que cambiar el byte único en la línea 1110 a 1 byte más que el número de bytes en el Player. La posición X inicial del Player se lee desde INITX y se almacena en el registro de posición horizontal para el Player Cero, 53248. También se almacena en XLOC para que la utilice la rutina de mover Player.
Luego hacemos que el Player sea rojo almacenando el número 68 en el registro de color para el Player Cero, 704, y habilitamos gráficos Player/Missile almacenando un 3 en 53277, GRACTL.
El ciclo principal de este programa es la simplicidad misma. Realizamos JSR a la rutina que lee el joystick y mueve al Player, y luego ingresamos a un breve ciclo de retardo. ¡Si omitimos este ciclo, el Player se moverá tan rápido que no podremos controlarlo en absoluto! A continuación, simplemente retrocedemos para leer el joystick y mover el Player nuevamente.
Obviamente, si desea agregar algo de interés a este programa, puede insertar su propia lógica de programa en este ciclo principal, para detectar colisiones entre Players y campos de juego (Playfield), crear obstáculos o cualquier otra cosa que desee en su juego. Sin embargo, si va a alargar el programa, debe cambiar el origen a algún lugar más alto en la memoria. Tal como están las cosas, este programa ya ocupa prácticamente toda la Página 6 por lo que, si lo agranda sin cambiar el origen, comenzará a sobrescribir el DOS y no podrá guardarlo ni cargarlo. Simplemente cambie el origen a $6000 o alguna otra ubicación segura de memoria alta. Para probar el programa después de ensamblarlo, simplemente escriba BUG para ingresar al depurador y luego escriba G600 para la versión original (o G6000 si cambia el origen).
Ahora que ha visto cómo implementar gráficos Player/Missile desde el lenguaje Ensamblador, puede escribir sus propios programas utilizando estas mismas técnicas. Al hacer esto, como ya hemos visto por la necesidad de insertar un ciclo de retardo en el programa anterior, acelerará enormemente las cosas y creará movimientos suaves de los Players que mejorarán enormemente sus juegos. ¡Diviértase!
CREAR SONIDO EN LAS COMPUTADORAS ATARI
Comenzaremos nuestra discusión sobre el sonido aprendiendo cómo el ATARI produce sonidos y luego escribiremos una subrutina en lenguaje Ensamblador para imitar el comando SOUND del BASIC.
Veamos primero las equivalencias utilizadas para la generación de sonido. El chip POKEY es responsable de la creación de todos los sonidos en las computadoras ATARI y reside en la memoria, desde la dirección $D200 hasta la $D2FF. Los sonidos que añaden mucho al disfrute de los juegos e incluso pueden contribuir a la facilidad de uso de los programas comerciales si se usan correctamente, se dividen en cuatro voces. Cada voz está controlada por dos registros, ubicados en pares desde $D200 hasta $D207. El primero de cada par es el control de frecuencia y el segundo de cada par controla tanto el volumen como la distorsión del sonido producido por ese canal. Estos son:
100 AUDF1 = $D200 ; Frecuencia del canal de audio 1 110 AUDC1 = $D201; Control del canal de audio 1 120 AUDF2 = $D202 ; Frecuencia del canal de audio 2 130 AUDC2 = $D203; Control del canal de audio 2 140 AUDF3 = $D204 ; Frecuencia del canal de audio 3 150 AUDC3 = $D205; Control del canal de audio 3 160 AUDF4 = $D206 ; Frecuencia del canal de audio 4 170 AUDC4 = $D207; Control del canal de audio 4
Los respectivos registros de frecuencia controlan el tono del sonido o nota que se reproduce. Estos registros en realidad dividen la frecuencia del sonido por el número almacenado aquí. Es decir, si almacenamos un 12, entonces la frecuencia producida es una doceava parte de la frecuencia de entrada.
La frecuencia de entrada se controla mediante la inicialización de POKEY, configurando AUDCTL. El siguiente cuadro describe el uso de cada bit en AUDCTL:
Bit | Uso |
---|---|
0 | Establecer en 1 para cambiar el reloj principal de 64 kHz a 15 kHz |
1 | Establecer en 1 para insertar un filtro de paso alto en el canal 2 |
2 | Establecer en 1 para insertar un filtro de paso alto en el canal 1 |
3 | Establecer en 1 para unir los canales 4 y 3 para una resolución de 16 bits |
4 | Establecer en 1 para unir los canales 2 y 1 para una resolución de 16 bits |
5 | Establecer en 1 para sincronizar el canal 3 con 1,79 MHz |
6 | Establecer en 1 para sincronizar el canal 1 con 1,79 MHz |
7 | Establecer en 1 para convertir el policontador de 17 bits en 9 bits |
¿Qué significa todo esto? Veámoslo poco a poco. Supongamos que se almacena un 10 en el registro de frecuencia de la voz 1. Ya sabemos que esto hará que salga un pulso de esa voz por cada diez que entren. El bit 0 de AUDCTL puede cambiar la frecuencia de los pulsos entrantes entre 64 kHz y 15 kHz. Kilohertz (kHz) significa miles de ciclos o pulsos por segundo. Obviamente, si AUDCTL se establece con el bit 0 igual a cero, entonces la frecuencia de salida de la voz 1 es de 6,4 kHz. Sin embargo, si almacenamos un uno en el bit 0 de AUDCTL, entonces la frecuencia de salida de la voz 1 será de 1,5 kHz y el resultado será un tono notablemente más bajo. Los bits 5 y 6 funcionan exactamente igual, pero si los establecemos en 1, las voces controladas por ellos, los canales 3 y 1 respectivamente, producirán tonos mucho más altos, ya que estarían sincronizados a 1,79 MHz (millones de ciclos por segundo), muchas veces más rápido que cualquiera de las frecuencias anteriores.
Los bits 1 y 2 de AUDCTL insertan filtros de paso alto en los canales 2 y 1, respectivamente. Estos filtros de paso alto están sincronizados por los canales 4 y 3, respectivamente. Es decir, solo los sonidos con una frecuencia más alta que los que suenan actualmente en el canal 3 se escucharán en el canal 1, y solo los sonidos con una frecuencia más alta que los que suenan actualmente en el canal 4 se escucharán en el canal 2. Usando estos filtros de paso alto es posible realizar algunos efectos especiales espectaculares. Seguramente querrá experimentar para ver qué se puede hacer.
Dado que la frecuencia se almacena en un solo byte, las voces del ATARI están limitadas a un rango de aproximadamente cinco octavas. Sin embargo, utilizando AUDCTL, es posible emparejar conjuntos de dos voces utilizando los bits 3 ó 4. Esto permite que los dos registros de frecuencia de estas voces formen un número de 16 bits, lo que da un rango de nueve octavas. Esto disminuye el número de voces disponibles, pero sería perfectamente factible producir una voz de nueve octavas y dos voces de cinco octavas, o incluso dos voces de nueve octavas. Cuando dos registros de frecuencia se combinan en uno, el mayor de los dos registros de frecuencia controla el byte alto del número de 2 bytes y el menor de los dos controla el byte inferior.
El bit alto de AUDCTL controla el contador polinomial. Este es quizás el concepto más difícil de comprender sobre la generación de sonido en las computadoras ATARI. Básicamente, los policontadores producen una secuencia aleatoria de pulsos que se repite después de un tiempo. Cuanto mayor sea el número de bits en el policontador, más larga será la secuencia aleatoria antes de que se repita el patrón. Hay tres policontadores diferentes en el ATARI y todos funcionan de la siguiente manera.
Supongamos que desea que se produzca algo de ruido. La música tiene un tono regular, pero el ruido es irregular y es más difícil de producir. El ATARI genera ruido al producir una secuencia aleatoria de pulsos desde el policontador y haciendo AND de manera efectiva con estos pulsos junto con la salida de los registros de frecuencia discutidos anteriormente. Sólo cuando ambos pulsos están activados se produce un sonido. Por ejemplo, si el registro de frecuencia dice que se debe producir un pulso o sonido, pero el patrón aleatorio del policontador está desactivado en ese momento, no se produce ningún sonido. Por lo tanto, aunque la salida del sistema de división por n del registro de frecuencia es una frecuencia pura, la combinación AND de estos pulsos con la secuencia aleatoria generada por los policontadores produce ruido. Debería ser evidente que diferentes policontadores producen diferentes sonidos de ruido, y el policontador utilizado puede seleccionarse mediante el bit 7 de AUDCTL, que es un policontador de 9 o 17 bits.
Además, la distorsión o tipo de ruido del sonido producido también se puede cambiar mediante el ajuste de distorsión, tal como en BASIC. La distorsión está controlada por los 3 bits superiores del registro de control de cada voz, AUDC1-4. Básicamente, estos bits eligen cómo se tratará el sonido y cuál de los tres policontadores se utilizará para la distorsión, de la siguiente manera:
Bit 7 | Bit 6 | Bit 5 | Significado |
---|---|---|---|
0 | 0 | 0 | Seleccione usando 5 bits, luego un policontador de 17 bits, divida por 2 |
0 | E | 1 | Seleccione usando un policontador de 5 bits, divida por 2 |
0 | 1 | 0 | Seleccione usando 5 bits, luego un policontador de 4 bits , divida por 2 |
1 | 0 | 0 | Seleccione usando un policontador de 17 bits, divida por 2 |
1 | E | 1 | No se utilizan contadores policontador, solo divida por 2 |
1 | 1 | 0 | Seleccione usando un policontador de 4 bits, divida por 2 |
"E" en la tabla anterior significa que el bit puede ser 1 ó 0. Primero, por supuesto, la velocidad del reloj se divide por la frecuencia. Veamos un ejemplo de cómo funciona el sistema de distorsión. Supondremos que el reloj funciona a 15 kHz, que hemos almacenado 30 en el registro de frecuencia apropiado y que los 3 bits altos del registro de control para esa voz son 010. Primero, la velocidad del reloj se divide por registro de frecuencia, en este caso, 15000/30 = 500 Hz. A continuación, dado que los bits de distorsión son 010, los pulsos a 500 Hz de salida de esta operación se combinan efectivamente haciendo AND con la salida del policontador de 5 bits. Luego, la salida de esta operación se aplica efectivamente haciendo AND con la salida del policontador de 4 bits, y la frecuencia de los pulsos que logran completar toda esta operación se divide por 2 para producir el sonido distorsionado final.
Debería ser obvio que, con tantas opciones para elegir, ATARI es capaz de generar muchos, muchos, muchos efectos de sonido diferentes. Es evidente que aquí es necesario realizar algo de experimentación. ¡Es posible que escuche algunos sonidos realmente extraños!
UNA SUBRUTINA DE SONIDO
La creación de sonidos en las computadoras ATARI es extremadamente fácil en BASIC, ya que el comando SOUND nos permite activar o desactivar cualquiera de las cuatro voces disponibles con cualquier distorsión, tono, volumen y frecuencia deseada. Exactamente las mismas funciones están disponibles en lenguaje Ensamblador. Podemos escribir una subrutina para imitar los efectos del comando SOUND del BASIC, tal como lo hicimos con los comandos gráficos en la sección anterior.
Listado 10.3
0100 ; ****************************** 0110 ; Equivalencias SOUND 0120 ; ****************************** D200 0130 AUDF1 = $D200 ; Frecuencia de Audio 1 D201 0140 AUDC1 = $D201 ; Control de Audio 1 D208 0150 AUDCTL = $D208 ; Control de Audio D20F 0160 SKCTL = $D20F ; Control del puerto serie 0101 0170 STORE2 = $101 ; Almacenamiento temporal 0000 0180 *= $600 0190 ; ****************************** 0200 ; El comando SOUND 0210 ; ****************************** 0220 ; Antes de llamar a esta rutina, 0230 ; el registro X debe contener la 0240 ; voz deseada, el Acumulador 0250 ; debe contener la distorsion, 0260 ; el registro Y debe contener 0270 ; el volumen deseado y STORE2 0280 ; debe contener la frecuencia 0290 ; deseada. 0300 SOUND 0600 48 0310 PHA ; Almacenar distorcion 0601 8A 0320 TXA ; Valor de voz doble 0602 0A 0330 ASL A ; para desplazamiento del 0603 AA 0340 TAX ; control de la voz 0604 AD0101 0350 LDA STORE2 ; Frecuencia en el 0607 9D00D2 0360 STA AUDF1,X ; canal correcto 060A 8C0101 0370 STY STORE2 ; Para uso posterior 060D A900 0380 LDA #0 ; Para inicializar el 060F 8D08D2 0390 STA AUDCTL ; chip POKEY 0612 A903 0400 LDA #3 ; configurelos tal como se 0614 8D0FD2 0410 STA SKCTL ; indica 0617 68 0420 PLA ; Recuperar distorcion 0618 0A 0430 ASL A ; Ahora multiplicar por 0619 0A 0440 ASL A ; 16 para obtener la 061A 0A 0450 ASL A ; distorcion en 061B 0A 0460 ASL A ; el nibble alto 061C 18 0470 CLC ; Configuracion para sumar 061D 6D0101 0480 ADC STORE2 ; Sume el volumen 0620 9D01D2 0490 STA AUDC1,X ; en la voz correcta 0623 60 0500 RTS ; Eso es todo
En este programa, duplicamos el valor originalmente almacenado en el registro X, que seleccionará la voz particular que se utilizará. Esto se debe a que los registros de sonido están dispuestos en pares, por lo que cada uno de los registros de control y cada uno de los registros de frecuencia están separados por dos bytes en la memoria. Luego, la frecuencia se recupera desde STORE2, que se utiliza porque necesitamos cuatro datos de información para el sonido, y entre los registros X e Y y el Acumulador, solo tenemos tres ubicaciones de almacenamiento a mano. Luego, la frecuencia se almacena en el registro de frecuencia apropiado en la línea 360 y el volumen se almacena temporalmente hasta que lo necesitemos. Luego inicializamos POKEY en las líneas 380 a 410 y convertimos la distorsión a los bits superiores del Acumulador, agregando el volumen para obtener el número que debe almacenarse en el registro de control de audio apropiado para producir el sonido deseado. ¡Eso es todo al respecto!
Sin embargo, la generación de sonido en lenguaje Ensamblador sufre el mismo problema que en BASIC: no se puede especificar la duración del sonido producido. Ya sea el comando SOUND en BASIC, o nuestra rutina equivalente en lenguaje Ensamblador, estos simplemente inician el sonido. Deberemos apagar el sonido una vez transcurrido un tiempo predeterminado, para crear la nota o efecto de sonido que queramos. Para apagar el sonido, simplemente almacene un cero en el registro de control apropiado, AUDC1-4. Para medir el tiempo transcurrido, use los temporizadores ya discutidos, en las ubicaciones del 18 al 20 decimal, o use una rutina como la que usamos en el ejemplo del Player/Missile para retrasos cortos:
LDX #50 LDY #0 LOOP DEY BNE LOOP DEX BNE LOOP
El retardo de tiempo en este tipo de rutina está controlado por el valor inicial cargado en el registro X; cuanto mayor sea el número, mayor será el retraso. Estos ciclos anidados tardarían una eternidad en ejecutarse si programáramos la contraparte de esta rutina en BASIC, pero después del ensamblaje el retraso es muy corto. Pruébelo para ver qué tan rápido pueden ser 256 X 50, ó 12.800 ciclos.
TEMPORIZADORES DE CUENTA REGRESIVA
La tercera forma de realizar un seguimiento del tiempo en las computadoras ATARI implica el uso de temporizadores de cuenta regresiva. AUDF1, AUDF2 y AUDF4 pueden actuar como temporizadores de cuenta regresiva de la siguiente manera: Cuando se almacena cualquier valor distinto de cero en STIMER ($D209), los valores almacenados en AUDF1, AUDF2 y AUDF4 comienzan a disminuir. Cada uno de estos tres registros tiene una ubicación vectorial apropiada, tal como se describe a continuación:
Temporizador | Ubicación del vector |
---|---|
AUDF1 | $210, $211 (VTIMR1) |
AUDF2 | $212, $213 (VTIMR2) |
AUDF4 | $214, $215 (VTIMR4) |
Si colocamos la dirección de una rutina corta para apagar el sonido en una de estas ubicaciones vectoriales, se generará una interrupción cuando el temporizador apropiado haya llegado a cero y el control cambiará a su rutina para apagar el sonido. Sólo recuerde finalizar esta rutina con un RTI en lugar de un RTS. Antes de usar este tipo de temporizador, debe colocar el valor apropiado en el byte de habilitación de solicitud de interrupción, IRQEN ($D20E). Para habilitar VTIMR1, establezca el bit 0 de IRQEN; para habilitar VTIMR2, configure el bit 1 de IRQEN; y para habilitar VTIMR4, configure el bit 2 de IRQEN.
Ahora debe poder crear cualquier sonido disponible desde BASIC usando lenguaje Ensamblador, y aquí hay uno que no se puede producir desde BASIC debido a la velocidad requerida. Si el bit 4 de cualquiera de los registros de control de audio se establece en 1, se puede escuchar un breve "pop". Esto se debe a que se empuje el cono del altavoz hacia afuera una vez, comprimiendo el aire frente al altavoz, lo que se escucha como un "pop": si configuramos y reiniciamos este bit rápidamente, podemos producir un sonido simplemente comprimiendo el aire frente al altavoz. Pruebe esto:
LOOP LDA #16 STA AUDC1 (insertar retardo para frecuencia) LDA #31 STA AUDC1 (insertar retardo para frecuencia) JMP LOOP
El altavoz de su televisor o monitor vibrará hacia adelante y hacia atrás, produciendo un sonido totalmente diferente al descrito anteriormente. En este ejemplo, simplemente movemos el altavoz directamente para producir sonido.
Índice de Contenido | Capítulo anterior | Siguiente apéndice