programacion:ensamblador:efectos-slideshow

No renderer 'odt' found for mode 'odt'

Crea tu propio slideshow

Aprovechando que me han pedido hacer un slideshow temático para la revista, he decidido dedicar el artículo de este número a examinar su código. Por falta de tiempo no he añadido ningún efecto, tan sólo he modificado ligeramente el primer efecto de líneas entrelazadas, que en FSC3 copiaba los atributos al principio y ahora deja los atributos en blanco y negro y los copia de la imagen original al final. También he realizado diversos cambios y optimizaciones, aunque seguro que siguen quedando partes mejorables.

Con este código, que podéis modificar a vuestro antojo para incluir nuevos efectos o mejoras, y vuestras propias fotos, veréis lo sencillo que resulta hacer un slideshow resultón.


El principal ingrediente de vuestro slideshow deben ser las fotos, para crearlas no tenéis mas que coger las fotos originales en formato .jpg y utilizar los programas BMP2SCR EXP (la versión PRO también es válida) y SevenuP (especialmente para el retoque, la conversión no funciona demasiado bien para fotos reales) para convertirlas al formato del Spectrum. No me voy a extender mas porque el asunto se saldría de lo que es la programación en ensamblador en sí misma.

Una vez que tengáis las fotos en formato .SCR, las podéis pasar a TAP con mi programa BIN2CODE, modificáis el código fuente del visor para que aparezca el número de fotos que tengáis en la etiqueta N_S y lo compiláis con Pasmo (ZMAC y AS80 también funcionan, para otros sería necesario hacer algunos pequeños cambios), lo pasáis a cinta con BIN2TAP y por último concatenáis los archivos del programa y las pantallas para obtener el slideshow completo.


Esta parte del código no tiene en principio ninguna complicación, tan sólo se dedica a cargar una pantalla en la memoria, escoger un efecto para esa pantalla y aplicar dicho efecto para mostrarla. Todas las pantallas se cargan en la dirección 49512, que tiene la ventaja de que tan sólo se diferencia en un bit con 16384, de este hecho se van a aprovechar varios de los efectos. Una vez mostrada, se hace una pequeña pausa, se comprueba si quedan mas pantallas, y de ser así se incrementa el número de efecto y se vuelve al principio, reseteando dicho número de efecto si ya hemos mostrado todos los disponibles.

El código ya está comentado, así que no necesitaré extenderme demasiado. Vamos a verlo:

;SLIDESHOW ampliable, Version 2 by Metalbrain
 
N_S             EQU     18              ; Numero de pantallas
N_E             EQU     4               ; Numero de efectos
LD_BYTES        EQU     1366            ; Rutina de carga de la ROM

Aquí se definen etiquetas globales, que serán sustituidas por su valor numérico donde aparezcan, por lo que no ocupan memoria y no son variables.

                ORG     32768   ; Direccion de comienzo del codigo
 
                ; Nucleo principal del slide-show
                XOR     A               ;Iniciar numero de pantalla a 0
                LD      (N_SCREEN),A    
EXTBUC:         XOR     A               ;Iniciar numero de efecto a 0
INTBUC:         LD      (N_EFFECT),A    ;Guardar efecto actual
 
                LD      DE,17           ;Cabecera, no hace falta en verdad
                LD      IX,49152        ;
                XOR     A               ;
                SCF                     ;Se puede suprimir quitando tambien 
                CALL    LD_BYTES        ; las cabeceras de las pantallas
 
                LD      DE,6912         ;Numero de bytes
                LD      IX,49152        ;Direccion de comienzo
                LD      A,255           ;Identificador de bloque
                SCF                     ;Carry flag a 1 para cargar
                CALL    LD_BYTES        ;Llamar a la rutina de carga

La rutina LD_BYTES que utilizamos para cargar, carga un bloque de DE bytes que tenga el identificador especificado por A en la dirección que indica IX. Para que cargue en lugar de verificar se necesita que la bandera de acarreo esté a 1 (por eso se utiliza SCF).

                LD      HL,TABLE-2
                LD      A,(N_EFFECT)    ; Seleccionar efecto
                INC     A
                LD      B,A
SELEFF:         INC     HL
                INC     HL
                DJNZ    SELEFF    

Cada efecto tiene su dirección de comienzo en una tabla. Con esto HL queda apuntando a la dirección de la rutina del siguiente efecto, contenida en dicha tabla.

                LD      A,(HL)          ;Introducir efecto en la direccion
                INC     HL              ; a la que se llama con CALL
                LD      (THEJUMP1),A
                LD      A,(HL)
                LD      (THEJUMP2),A
 
THEJUMP:        DEFB    205             ;CALL
THEJUMP1:       DEFB    0
THEJUMP2:       DEFB    0

La dirección de comienzo de cada efecto se introduce directamente como dato en la llamada CALL, por lo que estamos usando código automodificable. Esto no es una buena práctica de programación en procesadores más potentes (o falla o se carga la caché), pero con el Z80 no hay problema. Al llegar a THEJUMP, se hace una llamada al efecto correspondiente.

                LD      B,250           ; Pausa de 5 segundos, para que de tiempo a 
PPAUSE:         HALT                    ; ver las pantallas al usar emuladores con 
                DJNZ    PPAUSE          ; carga instantanea

Si preferimos usar el slideshow en una cinta real (o desactivar la carga rápida en el emulador que usemos), podemos bajar el valor de B, o incluso omitir totalmente esta parte.

                LD      A,(N_SCREEN)    ;Incrementar el contador de pantallas
                INC     A
                CP      N_S
 
                JR      Z,THEEND        ;Si se han visto todas ya, acabar
                LD      (N_SCREEN),A
 
                LD      A,(N_EFFECT)    ;Incrementar contador de efecto
                INC     A
                CP      N_E
                JR      Z,EXTBUC        ;Si se han usado todos, empezar de 0
                JR      INTBUC          ;Si no, usar el siguiente

Esta parte del código resulta bastante sencilla, no creo que deba explicar nada más.

Y llegamos al final:

THEEND:         XOR     A               ;Esperar pulsacion de tecla
WAITKEY:        IN      A,(254)
                CPL
                AND     31
                JR      Z,WAITKEY
                RET                     ;Volver al BASIC

Si no hay ninguna tecla pulsada, el resultado de la instrucción IN serán xxx11111, mientras que si hay pulsada alguna tecla aparecerá algún 0 en los últimos 5 bits. La rutina WAITKEY es por lo tanto una forma muy común de esperar a que se pulse una tecla.

                ;Variables del bucle principal del slideshow
 
N_EFFECT:       DEFB    0       
N_SCREEN:       DEFB    0
 
                ;Tabla de efectos
 
TABLE:          DEFW    EF1
                DEFW    EF2
                DEFW    EF3
                DEFW    EF4

En la tabla de efectos podemos ordenar los efectos como queramos e incluso poner alguno repetido para que aparezca con mas frecuencia que los demás, lo más importante es que el número de efectos que aparezcan en la tabla sea igual que el especificado en la constante N_E definida mas arriba, si fuera menor podría introducirse basura en la llamada al efecto con nefastas consecuencias.

Este efecto se basa en hacer un desplazamiento lateral de la pantalla, de forma que vaya entrando, pero dividiendo las líneas de forma que las pares entren por la parte de la izquierda de la pantalla, moviéndose a la derecha, y las impares entren por la derecha, avanzando hacia la izquierda. En el efecto se realizan dos bucles, el externo se repite 32 veces, una para cada carácter que avanzamos, y el interno 96 veces (192 / 2), uno por cada pareja de líneas que desplazamos.

        ; EF1: Efecto entrelazado de lineas, por Metalbrain
 
EF1_COUNT:      DEFB    0
 
Esta variable se utilizará de contador de líneas en el bucle interno del efecto.
 
EF1:            LD      A,56
                LD      HL,22528
                LD      DE,22529
                LD      (HL),A
                LD      BC,767
                LDIR                    ;Fijar los atributos

Con esto fijamos los atributos de la pantalla a ink=0, paper=7. Así podremos ver el desplazamiento de las líneas en todo momento.

A partir de ahora los propios comentarios del código son bastante abundantes, así que no tendré que detenerme demasiado.

                LD      BC,1            ;BC = numero de caracteres que entran
                                        ; de cada linea en cada iteracion.
                                        ; Varia de 1 a 32, con 32 acabamos
                                        ; de mostrar toda la pantalla y por lo
                                        ; tanto llegar a 33 significa que
                                        ; el efecto ha terminado

Este valor de BC va a tener varios usos. Uno de ellos es ajustar las direcciones de origen para líneas pares y de destino en las impares a su valor correcto en cada iteración. Otro es servir de contador en la instrucción LDIR que vamos a utilizar para mover los datos.

EF1_EXTBUC:     LD      HL,49152+32     ;HL = direccion de origen en los
                                        ; movimientos de bytes, por lo que
                                        ; se situa en la zona de memoria
                                        ; donde hemos cargado la pantalla
                                        ; que tenemos que presentar
 
                                        ;Las lineas pares entran hacia la
                                        ; derecha, por lo que HL al principio
                                        ; apunta 32 bytes mas alla del
                                        ; comienzo de la pantalla. Asi al
                                        ; restar BC, conseguiremos el comienzo
                                        ; del extremo derecho que tenemos que
                                        ; pintar de la linea.
 
                LD      DE,16384        ;DE = destino en los movimientos de
                                        ; bytes, por lo que se situa en la
                                        ; zona de memoria correspondiente a
                                        ; la pantalla que aparece en la TV
                                        ;Al principio apunta justo al comienzo
                                        ; de la pantalla, pues es donde va a
                                        ; aparecer la primera linea.
 
                LD      A,96            ;96 parejas de lineas quedan por
                                        ; mover
 
EF1_INTBUC:     LD      (EF1_COUNT),A   ;Guardar la variable
                PUSH    HL              ;Guardar HL y DE en la pila
                PUSH    DE              ; para recuperarlos tras hacer
                PUSH    HL              ; los LDIRs
                PUSH    DE
                OR      A               ;Poner el Carry Flag a 0 para hacer
                                        ; que el SBC sea como un SUB
                SBC     HL,BC           ;Origen de los bytes que vamos a mover
 
                LD    A,C        ;Preservar C
                LDIR                    ;Mover linea par, entrando por la
                                        ; izquierda hacia la derecha
        LD    C,A             ;Recuperar C

Sabemos que B=0, por lo que es mejor preservar el valor de BC de esta manera que usando las instrucciones PUSH y POP, que consumen 11 estados cada una en lugar de 4.

                POP     DE              ;Recuperar DE y HL
                POP     HL
                                        ;Las lineas impares entran por la
                                        ; derecha hacia la izquierda, de forma
                                        ; que tenemos que cambiar las
                                        ; posiciones relativas de DE y HL
                                        ; (aparte de hacer que bajen 1 linea
                                        ; y se coloquen en la impar)
 
                                        ;En este caso DE hay que colocarlo
                                        ; pasado el final de la linea y
                                        ; restarle BC para que obtenga su
                                        ; valor final
 
                EX      DE,HL           ;Intercambiamos DE y HL porque DE
                                        ; no permite realizar restas. En lugar
                                        ; de poner aqui esta instruccion, se
                                        ; podria haber puesto justo antes del
                                        ; SBC y operar en las siguientes
                                        ; 6 instrucciones con E y D. Lo mismo
                                        ; da una cosa que otra.
 
                LD      A,32            ;Sumamos 288 a HL, 32 para colocar
                ADD     A,L             ; al final de la linea y 256 para
                LD      L,A             ; bajar a la linea impar.
                LD      A,1
                ADC     A,H
                LD      H,A

La forma de realizar esta suma parece complicada, pero es la mejor manera de hacerla con los registros e instrucciones que el Z80 pone a nuestra disposición. En total usamos 8 bytes y 30 estados. Una suma de 16 bits ya consume de por si 2 bytes y 11 estados, a esto le sumamos otros 3 bytes y 10 estados para cargar el valor 288 en un registro, y otros 2 bytes y 22 estados para preservar y liberar un registro con las instrucciones de pila. En total ganaríamos 1 byte a costa de 13 estados.

                SBC     HL,BC           ;Restamos el numero de bytes a mover
                                        ; para acabar de ajustar la direccion
 
                EX      DE,HL           ;Y volvemos a dejar el resultado en DE
 
                LD    A,C                  ;Preservar valor de C
                  LD    C,224                  ;Sumamos 224 a HL, lo cual es equivalente a 
                  ADD    HL,BC                  ;restar los 32 que nos sobran y sumar 256 
                                            ;para bajar una linea
                LD    C,A                  ; Recuperar valor de C
                LDIR                    ;Mover la linea impar
                LD    C,A                  ;Recuperar numero de bytes de nuevo

En este caso el hecho de que 224 sea menor que 0, favorece el uso de BC para operar directamente con una operación de 16 bits, al contrario que en caso anterior.

                POP     DE              ;Recuperar posiciones originales
                POP     HL              ; en pantalla y en memoria
 
                DEC     HL              ;hacer que la linea de HL sea par
                CALL    NEXT2_EXDEHL    ;Bajar 2 lineas para DE
                CALL    NEXT2_EXDEHL    ;Y otras 2 para HL

No os preocupéis si no entendéis cómo la misma rutina puede operar sobre dos registros diferentes, lo veremos más adelante.

                INC     HL              ;recuperar el +32 que necesitamos
 
                LD      A,(EF1_COUNT)   ;Obtener cuenta de lineas
                DEC     A               ;Decrementar
                JR      NZ,EF1_INTBUC   ;Si no es cero, repetir el bucle
                                        ; interno para las 96 parejas de
                                        ; lineas
 
                HALT                    ;Pausa
                HALT                    
 
                INC     C               ;Incrementar numero de pixels a
                                        ; mostrar en la siguiente iteracion
 
                LD      A,C             ;Comparar con 33
                CP      33
 
                JR      NZ,EF1_EXTBUC   ;Continuar el bucle externo si es
                                        ; necesario
 
                LD      HL,49152+6144
                LD      DE,16384+6144
                LD      BC,768
                LDIR                    ;Mover los atributos
                RET                     ;Regresar a la rutina principal

Ahora vamos a ver la rutina que operaba sobre dos registros distintos con la misma llamada. Aquí tenemos su comienzo:

NEXT2_EXDEHL:   INC     D     ; Incrementa 2 lineas + intercambio de HL y DE
NEXT_EXDEHL:    EX      DE,HL ; Incrementa 1 linea + intercambio de HL y DE

Como podéis observar el secreto es muy sencillo: al comienzo de la rutina se intercambian los valores de HL y DE, de tal forma que la rutina actúa primero sobre HL con el valor de DE, y luego vuelve a actuar sobre HL pero esta vez sobre el valor que tenía originalmente, y el intercambio inicial además ha dejado en DE el dato procesado la primera vez. El incremento inicial también puede ponerse debajo de la orden de intercambio (pero en este caso habría que poner INC H, obviamente), pero está puesto así para que existan otros puntos de entrada en la rutina que puedan ser útiles. En un principio la rutina fue diseñada para que pudieran usarla distintos efectos en el slideshow FSC3, y colocada en una zona de rutinas comunes antes de los efectos, aunque al final tan sólo este efecto utiliza la rutina, y por eso lo he movido aquí esta vez. La instrucción de intercambio también podría haberse puesto fuera de la rutina, envolviendo una de las llamadas, pero en ese caso habría que repetirla, mientras que teniéndola dentro ahorramos un byte. Y mejor dejo ya de enrollarme y continuamos viendo la parte que realiza la bajada de línea:

NEXT_HL:        INC     H       ;Incrementa 1 linea, si pasamos de caracter
                LD      A,H     ; se queda a 0 y se produce un aumento de
                                ; tercio
                AND     7
                RET     NZ      ;Salir si no hemos pasado de caracter
                LD      A,L
                ADD     A,32    ;Pasar al siguiente caracter
                LD      L,A
                RET     C       ;Salir si se produjo un cambio de tercio
                LD      A,H
                SUB     8       ;Quitar el aumento de tercio que no se produjo
                LD      H,A
                RET

Para entender esta rutina, hay que recordar el formato de las direcciones de pantalla:

byte alto byte bajo
010 tt yyy YYY XXXXX

Donde:

tt tercio de la pantalla, de 00 a 10
(con 11 estamos con los atributos o fuera de la pantalla).
YYY fila dentro del tercio (en caracteres).
yyy fila dentro del carácter (en pixels).
XXXXX columna

Y tenemos que tener en cuenta 3 casos:

  • cuando estamos dentro de un carácter: para bajar al siguiente carácter basta con incrementar H, ya que con esto aumentamos el campo yyy, que es lo que se requiere.
  • cuando pasamos dentro de un carácter al siguiente dentro del mismo tercio, hay que pasar el campo yyy de 7 a 0, y debemos incrementar el campo YYY sumando 32 a L.
  • cuando pasamos de un tercio al siguiente, hay que pasar los campos yyy e YYY a 0, e incrementar tt.

Cuando la línea es par, siempre se va a dar el caso 1, y no es necesario hacer ninguna comprobación, por eso funciona el incremento del comienzo de forma que se bajan dos líneas.

Este efecto es algo más sencillo que el anterior. Básicamente, copiamos el gráfico en la pantalla con los atributos en negro, y vamos copiando los atributos siguiendo una espiral rectangular para ir desvelando el contenido de la pantalla.

Veamos la rutina:

        ; EF2: Efecto atributos espiral, por Metalbrain
 
EF2_COUNT:      DEFB    0
EF2:            LD      DE,16384+6144+1 ;Comienzo zona atributos + 1
                LD      HL,16384+6144   ;Comienzo zona atributos
                XOR     A               ;A = 0 (color negro)
                LD      (HL),A          ;Poner color negro en el primer
                                        ; caracter de la pantalla
                LD      BC,767          ;Numero de caracteres a borrar
                LDIR                    ;Poner pantalla en negro
 
                LD      DE,16384
                LD      HL,49152
                LD      BC,6144
                LDIR                    ;Copiar pixels
 
 
                LD      IX,1            ;Incremento horizontal
                LD      IY,32           ;Incremento vertical

Comenzamos por la esquina superior izquierda, por lo que el primer incremento horizontal es positivo (vamos a la derecha) y el vertical también (hacia abajo).

                LD      HL,16383+6144   ;Iniciar HL = Zona de atributos-1
                LD      A,32            ;Contador inicial
                LD      (EF2_COUNT),A   ;Iniciar variable
 
EF2_EXTBUC:     LD      A,(EF2_COUNT)   ;Contador
                LD      B,A             ;B = numero de caracteres a desvelar
 
                PUSH    IX
                POP     DE              ;DE = IX = incremento horizontal
 
                CALL    EF2_BUC         ;Mostrar una linea horizontal de
                                        ; atributos

La rutina EF2_BUC que veremos más adelante funciona especificando en DE el incremento para obtener el siguiente carácter.

                PUSH    DE
                POP     IX              ;IX = DE

Al volver, en DE aparece el valor negado del que entró, de modo que lo almacenamos para que la siguiente línea se recorra en sentido contrario.

                LD      A,(EF2_COUNT)
                DEC     A            
                LD      (EF2_COUNT),A   ;Decrementar cuenta de bytes

La espiral cada vez se hace más pequeña.

                SUB     8               ;Restar 8 para obtener el numero
                                        ; de caracteres verticales para el
                                        ; siguiente paso
 
                JR      Z,EF2_END       ;Si salen 0, hemos acabado

Aquí se detecta cuándo se produce el final de la rutina. La última línea que se recorre va a ser horizontal porque la pantalla es mas ancha que alta.

                LD      B,A             ;B = numero de caracteres a desvelar
 
                PUSH    IY
                POP     DE              ;DE = IY = incremento vertical
 
                CALL    EF2_BUC         ;Mostrar linea vertical
 
                PUSH    DE
                POP     IY              ;IY = DE
 
                JR      EF2_EXTBUC      ;Repetir hasta salir

Con esto concluye el bucle principal del efecto, veamos ahora la subrutina para copiar un número de caracteres especificado en B incrementando HL en DE bytes cada vez.

EF2_BUC:        ADD     HL,DE           ;Actualizar HL con el incremento
                                        ; que le corresponde
 
                SET     7,H             ;Hacer que HL apunte a la zona de
                                        ; memoria alta donde se almacena el
                                        ; grafico. Esto es posible hacerlo
                                        ; asi porque las direcciones de
                                        ; almacen y de pantalla solo se
                                        ; diferencian en un bit
 
                LD      A,(HL)          ;Tomar valor
 
                RES     7,H             ;Volver a apuntar a la pantalla
 
                LD      (HL),A          ;Escribir valor. Con esto desvelamos
                                        ; el caracter que habia oculto.
 
                LD      A,B             ;Hacer un HALT solo cada 8 caracteres
                AND     7               ; desvelados (hacerlo siempre
                JR      NZ,EF2_NOHALT   ; resultaria muy lento)
                HALT
 
EF2_NOHALT:     DJNZ    EF2_BUC         ;Mostrar todos los caracteres que se
                                        ; han pedido
 
                HALT                    ;Otro HALT
 
                LD      A,D             ;Negar el valor de DE, con lo cual
                CPL                     ; cambiamos el sentido la proxima
                LD      D,A             ; vez que avancemos 
                LD      A,E
                NEG
                LD      E,A

La parte alta de DE basta con complementarla, pero la baja hay que negarla.

EF2_END:        RET                     ;Volver de la rutina EF2_BUC, o bien
                                        ; regresar a la rutina principal al
                                        ; acabar el efecto

Y eso es todo lo que este efecto ha dado de sí. Ya os dije que era sencillo. Seguramente hay mejores formas de hacer esto en lugar de usar los registros IX e IY, pero tenía prisa y esto fue lo primero que se me ocurrió. Si el lector lo desea, puede intentar otras ideas mejores.

Este efecto es bastante espectacular: sin que se borre la pantalla anterior, los caracteres de la nueva van apareciendo de forma aparentemente aleatoria, hasta que sustituyen por completo la imagen anterior. Para lograr cubrir toda la pantalla, necesitaremos una tabla donde indicar qué caracteres han sido ya sustituidos y cuáles no. La tabla no ocupará los 768 bytes de la tabla de atributos, sino tan sólo 256 correspondientes a un tercio. En cada iteración de la rutina desvelaremos un carácter de cada tercio, con una separación entre las posiciones de forma que no quede cantoso. Para escoger qué carácter va a ser el siguiente, usaremos los bytes de la ROM como guía para saber la separación entre un carácter que aparezca y el siguiente.

Vamos al lío:

        ; EF3: Efecto caracteres desordenados, por Metalbrain
 
EF3_FILLTABLE   EQU     49152-256       ; Tabla para el efecto 3
EF3_POINTER:    DEFW    0
EF3_COUNTER:    DEFB    0
EF3:            LD      B,0             ;Contador de 0 a 0 -> 256 iteraciones
                LD      HL,EF3_FILLTABLE;Tabla de relleno, para indicar que
                                        ; caracteres hemos volcado ya y cuales
                                        ; no
                XOR     A               ;Valor inicial para rellenar la tabla
                LD      (EF3_COUNTER),A ;Iniciar contador
 
EF3_FILLIT:     LD      (HL),A          ;Llenar la tabla

Un 0 en la tabla indicará que el carácter está libre, o sea, que aún no ha sido sustituido.

                INC     L
                DJNZ    EF3_FILLIT
 
                LD      IX,(EF3_POINTER) ;IX apunta a la ROM, de donde se toman
                                         ; los valores de incremento que
                                         ; indican cuantas posiciones libres
                                         ; tenemos que saltar en la tabla antes
                                         ; de quedarnos con una
 
EF3_EXTBUC:     LD      A,(EF3_COUNTER) ;Contador en A
                LD      B,A             ;Y en B
 
                AND     A               ;Ver si es 0
 
                LD      A,(IX+0)        ;Cargar en valor de incremento
 
                JR      Z,EF3_NOADJUST  ;Si B vale 0, equivale a 256 y no hay
                                        ; que ajustar el valor de incremento
 
EF3_ADJUST:     SUB     B               ;Restar el contador al valor de
                JR      NC,EF3_ADJUST   ; incremento hasta que obtengamos un
                ADD     A,B             ; acarreo, y entonces lo volvemos a
                                        ; sumar. Con eso logramos que el
                                        ; valor de incremento sea menor que
                                        ; el contador de posiciones libres,
                                        ; para no estar dandole vueltas a la
                                        ; tabla tontamente. En otras palabras,
                                        ; hacemos A = (A MOD B), la operacion
                                        ; del modulo
 
EF3_NOADJUST:   LD      B,A             ;El valor de incremento ajustado lo
                                        ; ponemos en B, para el bucle con DJNZ
 
                XOR     A               ;En A ponemos el valor a buscar: 0
                                        ; (no copiado todavia)
EF3_PARSE:      INC     L               ;Buscamos en la tabla B valores libres
                CP      (HL)
                JR      NZ,EF3_PARSE
                DJNZ    EF3_PARSE
 
                INC     (HL)            ;Marcamos el valor en la tabla
 
                PUSH    HL              ;Guardamos HL para mas tarde
 
                LD      H,128+88        ;Apuntamos a la zona de atributos
                                        ; origen

Hay que tener en cuenta que el valor 88 en binario es 01011000, correspondiente a la zona de atributos.

                LD      B,3             ;3 caracteres, uno por cada tercio
 
                INC     IX              ;Incrementamos IX para apuntar a un
                                        ; valor de incremento diferente en la
                                        ; ROM. Este INC puede colocarse en
                                        ; cualquier otra parte del programa
 
EF3_OTHERTHIRD: PUSH    BC
                PUSH    HL              ;Preservar registros
 
                LD      E,L             ;DE=HL-32768, misma direccion pero
                LD      A,H             ; en pantalla
                SUB     128
                LD      D,A
 
                LD      A,(HL)          ;Copiar atributo
                LD      (DE),A
 
                                        ;Pasar de direccion de atributos a
                                        ; direccion de pixels en DE y HL

Para realizar la operación indicada en el comentario, tan sólo tenemos que modificar el valor alto del registro, ya que el valor bajo es idéntico (el campo yyy lo ponemos a 0):

atributo 010 11 0tt YYY XXXXX
gráfico 010 tt yyy YYY XXXXX

Veamos el código:

                                        ;       A   Carry
                LD      A,D             ;010110tt       ?
                RLCA                    ;10110tt0       0
                RLA                     ;0110tt00       1
                RLCA                    ;110tt000       0

Aquí lo más sencillo sería haber usado desplazamientos, ya que lo que queremos es desplazar a la izquierda a la vez que introducimos ceros por la derecha, pero por desgracia no existen instrucciones de 1 byte que permitan desplazar directamente en A de la misma forma que existen rotaciones, de forma que podemos aprovechar que conocemos el valor de los bits que van a salir para ir introduciendo esos ceros. Si os parece lioso, repasad las microfichas con las instrucciones de rotación y observad los valores.

                LD      H,A

Aprovechamos que ha salido el bit 7 a 1 para actualizar H.

                AND     88              ;010tt000
                LD      D,A

En lugar del valor 88 para el AND, nos vale cualquier valor de la forma 01x11xxx, por ejemplo 127, y en lugar del AND también podría haberse hecho SUB 128, o XOR 128, o ADD A,128. El caso es quitar el bit 7. RES 7,A también podría usarse, pero cuesta un estado más.

                LD      B,8             ;8 bytes por caracter
 
EF3_DOCHAR:     LD      A,(HL)          ;Copiar byte de pixels
                LD      (DE),A
 
                INC     H               ;Apuntar a la siguientes lineas
                INC     D
 
                DJNZ    EF3_DOCHAR

Aquí no nos tenemos que preocupar del cambio de carácter, con lo que el diseño de la pantalla del Spectrum se vuelve una ventaja.

                POP     HL              ;Recuperar HL
                POP     BC
 
                INC     H               ;Apuntar al siguiente tercio
 
                LD      A,49            ;Pequeno desplazamiento entre los
                ADD     A,L             ; caracteres que se muestran de cada
                LD      L,A             ; tercio, para que parezca mas
                                        ; aleatorio
 
                DJNZ    EF3_OTHERTHIRD  ;Repetir para los 3 tercios
 
                POP     HL              ;Recuperar direccion de la tabla
 
                LD      A,(EF3_COUNTER) ;Chequear la paridad del contador y
                AND     A               ; hacer un HALT solo cuando el
                JP      PE,EF3_NOHALT   ; contador tenga paridad impar. Por
                HALT                    ; termino medio, se hara una pausa
                                        ; cada 6 caracteres copiados
 
EF3_NOHALT:     DEC     A               ;Repetir hasta completar todos los
                LD      (EF3_COUNTER),A ; caracteres de la pantalla
                JR      NZ,EF3_EXTBUC
 
EF3_END:        LD      (EF3_POINTER),IX;Actualizar el puntero, asi si varias
                HALT                    ; pantallas usan este efecto, tendran
                RET                     ; patrones diferentes de aparicion
                                        ; de los caracteres

Con esto acaba el efecto, espero que os haya gustado tanto como a mí, porque estoy bastante orgulloso de cómo quedó y las ideas que utilicé.

Este efecto se basa en el que posiblemente sea el mas sencillo de los efectos de la demoscene, el primero que se enseña en los tutoriales: el fade-in/fade-out. Estos efectos consisten en variar la paleta de colores para que, sin tocar el gráfico que hay en pantalla, éste aparezca (fade-in) o se desvanezca (fade-out) poco a poco. En Spectrum no hay paleta, pero los atributos vienen a ser como una paleta de cada carácter.

Partiendo de unos atributos a 0, para hacer un fade-in basta con ir aumentando la tinta y papel de cada carácter hasta que se alcance el valor original, momento en el que no se aumenta más para ese carácter. Lo malo es que este proceso tan sólo tiene un máximo de 7 pasos, de forma que se me ocurrió otra variante dividida en dos mitades. La primera es un proceso de “calentamiento” (de ahí el “Heat up”), en la cual se hace un fade-in como el indicado para la tinta a la vez que se sube también el color del papel, pero para éste se sigue aumentando su valor hasta llegar al blanco, independientemente del valor original, y tras una pequeña pausa, comienza el proceso de “enfriamiento” (“cool down”) en el que se va decrementando tan sólo el papel hasta llegar a su valor original.

Tras la larga explicación, la rutina en sí es bastante sencilla:

EF4:            LD      DE,16384+6144
                LD      HL,49152+6144
                LD      BC,3            ;Numero de tercios, B=0 para dar
                                        ; 256 vueltas con DJNZ

En otras palabras, poniendo BC a 3 estamos metiendo un 768 en CB, que es lo que se usa haciendo un DJNZ seguido de DEC C/JR NZ.

EF4_FLABRI:     LD      A,(HL)          ;Coger atributo
                AND     192             ;Dejar los bits de flash y bright
                LD      (DE),A          ;Fijar flash y bright
                INC     HL
                INC     DE
                DJNZ    EF4_FLABRI      ;Procesar caracteres del tercio
                DEC     C
                JR      NZ,EF4_FLABRI   ;Repetir para los 3 tercios

Los atributos de flash y bright se copian al principio para no tener que tratarlos después en el proceso.

                LD      DE,16384
                LD      HL,49152
                LD      BC,6144
                LDIR                    ;Copiar pantalla menos atributos
                HALT
                LD      E,1             ;Heat actual en registro E
EF4_HEAT_EXT:   LD      HL,49152+6144   ;Apuntar al primer atributo
                LD      BC,3            ;C=3 tercios de B=0=256 caracteres
EF4_HEAT_INT:   LD      A,(HL)          ;Cargar atributo en A
                RES     7,H             ;Pasar a coordenada de pantalla
                AND     7               ;Aislar bits de tinta
                CP      E               ;Comparar con Heat actual
                CCF                     ;Invertir acarreo. Si CF=1, es que
EF4_NOINK:      LD      A,8             ; tenemos que incrementar la tinta
                ADC     A,(HL)          ; ademas del papel
                LD      (HL),A          ;
                SET     7,H             ;Recuperar posicion original
                INC     HL              ;Avanzar posicion
                DJNZ    EF4_HEAT_INT    ;Repetir tercio
                DEC     C
                JR      NZ,EF4_HEAT_INT ;Repetir los 3 tercios
                HALT
                HALT
                HALT
                HALT                    ;Pausa
                INC     E               ;Incrementar Heat
                LD      A,E
                CP      8
                JR      NZ,EF4_HEAT_EXT ;Si es menor que 8, seguir calentando
 
                LD      B,12            ;Pausa antes de pasar a enfriar el
EF4_PEAKPAUSE:  HALT                    ; el papel
                DJNZ    EF4_PEAKPAUSE
 
                LD      E,56            ;Cool a 7 (56 = 7*8)
EF4_COOL_EXT:   LD      HL,49152+6144   ;Apuntar al primer atributo
                LD      BC,3            ;como antes, CB = 768
EF4_COOL_INT:   LD      A,(HL)          ;Tomar atributo
                RES     7,H             ;Pasar a coordenada de pantalla
                AND     56              ;Aislar bits del papel
                CP      E               ;Comprobar si papel < Cool
                JR      NC,EF4_NOPAPER  ; si no lo es, no enfriamos
                LD      A,248           ;Sumar 248 es como restar 8
                ADD     A,(HL)          ;Enfriar papel
                LD      (HL),A
EF4_NOPAPER:    SET     7,H             ;Apuntar a la pantalla virtual
                INC     HL              ;Avanzar posicion
                DJNZ    EF4_COOL_INT
                DEC     C
                JR      NZ,EF4_COOL_INT ;Repetir bucle interno
                HALT
                HALT
                HALT
                HALT                    ;Pausa
                LD      A,E
                SUB     8               ;Bajar valor de Cool
                LD      E,A
                JR      NC,EF4_COOL_EXT ;Si no es 0, seguir enfriando
                RET                     ;Salir del efecto

Y eso es todo.

Espero que lo hayáis entendido todo y seáis capaces de hacer algún que otro efecto por vuestra cuenta, no olvidéis que lo importante es practicar. Y que os divirtáis modificando los míos o haciendo vuestros propios slideshows. Como de costumbre, si hay algo que no veis claro (incluso después de varias miradas), os ponéis en contacto conmigo, preferiblemente a través de la lista de correo [z80asm].

Hasta la próxima.

  • programacion/ensamblador/efectos-slideshow.txt
  • Última modificación: 31-03-2009 11:14
  • por sromero