Aula Macedonia


Curso de Programación Multimedia Bajo DOS


Artículo realizado por
José Antonio Suárez.





Capítulo 11.
Programación de la Sound Blaster (II).

3.3.- Programación de la Sound Blaster


Ya conocemos el hardware, ahora hay que reconocer sus posibles configuraciones y atenerse a ellas.

3.3.1.- Detección de la Sound Blaster

Ahora podemos analizar la parte del código que se encarga de la detección de la SB y de sus características, en el siguiente procedimiento que se encuentra en el módulo "VOC.C", pero antes hay que conocer ciertas constantes y variables incluidas en el módulo "VOC.H".

WORD longitud=0; WORD frecuencia=0; WORD alojado;

WORD BPORT=0x210; WORD XPORT=0x216; WORD WPORT=0x21c;

WORD RPORT=0x21a; WORD APORT=0x21e; BYTE READY=0xaa;

Y el procedimiento para la detección de la SB, que incluye la explicación de lo que hace, en el propio código, es:

// Si se encuentra la Sound Blaster, devuelve un 1. Un 0 si no se encuentra

BOOL DetectarSB(void)
{
    BYTE estado=0,cont;
 
    // Intentamos encontrar el puerto de conexión
    while ((estado!=READY)&&(BPORT<0x270))
    {
        EscribeEnPuerto(XPORT,1); // Reseteo de la Sound Blaster
        // Como mínimo hay que esperar 3,3 milisegundos.5 para asegurarnos
        delay(5);
        EscribeEnPuerto(XPORT,0); cont=0;
        while ((estado!=READY)&&(cont<100))
        { estado=LeeDePuerto(RPORT); cont++; }
        // Se buscan los puertos de la Sound Blaster
        if (estado!=READY)
        {
            BPORT+=0x10; XPORT+=0x10; WPORT+=0x10;
            PORT+=0x10; APORT+=0x10;
        }
    }
    if (BPORT!=0x270)
    {
        while ((LeeDePuerto(WPORT)&128)!=0);
        EscribeEnPuerto(WPORT,0xd1);
        // Activa la salida digital mediante el DAC
        SpeakerOn();
        return (1);
    }
    return (0);
}

Hasta ahora se ha presentado una visión introductoria de la SB, ya que en sí misma es secundaria. Pero lo que nos importa de verdad es lo que puede hacer por nosotros. Ya hemos detectado su presencia y reconocidos los valores de los puertos BASE y del DSP. Pasemos a lo que realmente es el motivo de su uso, aunque primero es imprescindible especificar el modo de funcionamiento que podemos darle a la SB. Se basan en:

  1. Temporizador del PC (Timer)
  2. Acceso directo a memoria (DMA)
3.3.2.- Generación de Sonido por el Temporizador
 
Todo ordenador dispone de un temporizador (timer), el cual está ubicado en una interrupción de usuario. Esta interrupción es llamada por el ordenador 18,2 veces por segundo y sirve para mantener la fecha, la hora del sistema y otros servicios internos.

Un aspecto importante de esta interrupción es que en todos los procesadores, ya sea un 8086 o un 80486, esta interrupción es llamada con la misma frecuencia, permitiendo así que la fecha o la hora del sistema, por ejemplo, se actualicen a la misma velocidad en todo tipo de procesadores. Si no fuese así, el tiempo "pasaría" más deprisa en un ordenador más potente que en un ordenador un poco más lento.

Las interrupciones, como se sabe, realizan diversas funciones. Para entenderlo claramente podemos decir que una interrupción es un servicio que nos permite simplificar una tarea determinada, tal como cambiar el modo gráfico (int 10h), o gestionar el ratón (int 33h).

Mediante el Ensamblador Inmerso usado conjuntamente con el C, el vector que apunta a una interrupción determinada puede ser cambiado, y hacer que apunte a una rutina de usuario.

Me explico: si conseguimos "colgar" una rutina de usuario en el vector de interrupción del teclado (9h), podremos hacer que nuestra rutina se ejecute cada vez que nosotros pulsamos una tecla... ¿por qué?. porque cada vez que se pulsa una tecla, el ordenador llama a la interrupción 9h para gestionar el evento ocurrido, a partir de esta llamada se llena el búfer del teclado, se realizan una serie de procesos y aparece el carácter deseado en pantalla (normalmente).

Pero si cambiamos esa interrupción y hacemos que en vez de apuntar a una posición de memoria determinada, apunte hacia nuestra rutina, conseguiremos que el ordenador llame a otra dirección de memoria, lo que provocará que se realice otro proceso distinto al original.

De la misma manera que podemos interceptar la interrupción 9h, también podemos interceptar el vector de interrupción del temporizador (8h). De esta manera nuestra rutina se ejecutaría 18.2 veces por segundo porque el ordenador llama él solito a la interrupción 8h 18.2 veces por segundo.

En definitiva, la generación de sonido vía temporizador se basa en los siguientes pasos:

  1. Crear una rutina que envíe un byte de un puntero al puerto de escritura de la SB, de esta manera cada vez que se llame al timer (18,2 veces por segundo) se enviará sólo un byte del puntero que contiene el sonido digitalizado (un número de 8 bits).
  2. Reprogramar el temporizador para que se ejecute más de 18,2 veces... 22000 veces, por ejemplo (algo totalmente posible). Este valor tomado como ejemplo, representa en definitiva el número de bytes que queremos mandar en un segundo. A este valor lo llamamos frecuencia de muestreo, concepto presentado anteriormente, expresado en Hertzios. Por lo tanto, si mandamos bytes a una frecuencia de 22000 bytes por segundo, nuestro sonido sonará a 22000Hz (22 Khz).
  3. "Colgar" nuestra rutina en la interrupción 8h, de esta manera se ejecutará la rutina 22000 veces.
Todo esto es muy bonito y en realidad funciona tal y como se pretende. Sería la forma más cómoda y fácil de generar el sonido digitalizado. Pero a pesar de esto, tiene mucho que desear.

Si colgamos una rutina del temporizador, cada tantas veces por segundo, el temporizador interrumpe a la CPU para realizar una operación extremadamente lenta como es enviar un byte al puerto de la SB.

Este proyecto intenta ser lo más rápido posible para conseguir hacer algo que realmente pueda calificarse como "multimedia", es decir, utilizar los recursos del ordenador de forma conjunta para un determinado objetivo. Así, si estamos interpretando un fichero de animación como los FLI (utilizables gracias a este curso), que requieren un gran número de operaciones y deseamos a la vez reproducir un sample utilizando la técnica anteriormente expuesta, dependiendo del ordenador, la animación se ralentizará demasiado (intolerable en este curso), ya que el proceso de cálculo se verá interrumpido numerosas veces por segundo, con lo que si queremos una buena frecuencia de muestreo, 18000 por ejemplo, la animación se moverá a duras penas, dando tirones y lástima.

3.3.3.- Generación de Sonido a través del DMA

Como se ha visto, la técnica anterior es válida si, por ejemplo, tenemos una pantalla fija y queremos que a la vez suene un sample. Pero en el momento en que se quieran hacer dos cosas que necesiten al procesador al mismo tiempo, la técnica del timer no vale, sencillamente porque no es óptima y no se puede combinar con la lectura de ficheros FLI, uno de los objetivos de este Proyecto.

Sencillamente por esto, necesitamos un sistema por el cual pudiéramos liberar a la CPU de todo el trabajo, permitiendo una generación de sonido totalmente independiente del ordenador. Pero estando en el mundo del PC, pensar en que el ordenador en su conjunto haga algo sin la interacción de la CPU, motor de todo, es casi impensable.

A su vez, pensando en cómo hacer algo independiente de la CPU, teniendo en cuenta que la SB incorpora un chip DSP que trabaja por sí solo... una transmisión de datos sin que medie la CPU... una transmisión directa desde la memoria a la SB que tiene sus propios registros (o sea, memoria)... una transmisión directa de memoria a memoria... ¡pero si para eso está el DMA!

Las transferencias por DMA (Direct Memory Access) permiten liberar a la CPU de todo el trabajo, evitando así los problemas antes mencionados. Pero toda transferencia por DMA requiere unos protocolos, unas leyes, unas normas a seguir, y que se deben seguir estrictamente si no queremos bloquear nuestro ordenador, o queremos obtener una calidad de reproducción óptima a la vez que el fichero FLI se interpreta por parte de la CPU.

Analicemos cómo usar el DMA.

Lo primero que hay que tener en cuenta es el nuevo modelo de representación de posiciones de memoria que se usa, ya que estamos hasta ahora acostumbrados al típico esquema de:

Segmento:Desplazamiento

Pues bien, si queremos transmitir un bloque de memoria de una dirección a otra mediante DMA, no debemos usar este tipo de expresión: debemos decirle al DMA la "página" de memoria y el "desplazamiento" de memoria donde se encuentra el bloque a mover. Ahora,

Segmento:DesplazamientoS

apunta al bloque en cuestión. El proceso de transformación de la dirección normal a la nueva es el siguiente.

DesplazamientoS = Segmento shl 4 + Desplazamiento

Página = (Segmento + Desplazamiento shr 4) shr 12

A partir de aquí, deberemos decir al DMA que envíe bytes de la posición Página:DesplazamientoS a la SB, y deberemos configurar el DSP para que simplemente vaya leyendo los bytes que le llegan por DMA y los vaya interpretando por su DAC. De esta manera oiremos el sonido.

Pero antes de hacer esto, deberemos decirle al DSP la frecuencia de muestreo a la que debe reproducir el sonido. Aquí hay que tener cuidado; no basta con decirle la frecuencia en Hertzios, sino que debemos dársela de forma que él lo entienda (al igual que la conversión a Página:Desplazamiento). El proceso para convertir de Hertzios a la frecuencia del DSP es la siguiente:

FrecuenciaDSP = 256- (1000000-FrecuenciaHertzios)

Luego, deberemos enviarle este valor resultante al DSP... pero ¿como escribimos valores en el DSP? Es muy sencillo (claro, una vez que has estado dos días intentando comprender por qué se bloquea el ordenador y de repente piensas: ¿y si pongo "mov al, byte ptr valor" en lugar de "mov ax, word prt valor"?)

Para escribir cualquier tipo de valor en el DSP deberemos usar los puertos de la SB antes mencionados. El siguiente procedimiento que también se encuentra en el módulo "VOC.C" se encarga de esto, y en él podrá apreciarse (de nuevo) el uso del Ensamblador Inmerso.

void EscribeEnDSP(BYTE valor)
{
    test1:
    asm{
            mov dx,WPORT
            in al,dx
            and al,0x80
            cmp al,0
            jnz test1
            mov al,byte ptr valor
            out dx,al
    }
}

Según los valores que escribamos en el DSP, la tarjeta de sonido realizará diversas funciones, tales como activar el DAC mediante la función:

void SpeakerOn(void);

O desactivarlo mediante

void SpeakerOff(void);

Y otras más que posteriormente veremos.




AULA MACEDONIA
a
MACEDONIA Magazine