Aula Macedonia


Curso de Programación Multimedia Bajo DOS


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





Capítulo 12.
Programación de la Sound Blaster (III).

3.4.- Ficheros de Sonido


Ya tenemos lo necesario para mandar la información a la tarjeta de sonido usando el DMA. Ahora veremos los dos tipos de ficheros que vamos a "interpretar" mediante la SB:

  1. Ficheros VOC o sonidos digitalizados (64Kb como máximo).
  2. Ficheros MOD o música compuesta por samples (31 samples como máximo).
3.4.1.- Ficheros VOC

Los ficheros VOC fueron creados por Creative Labs como un medio para almacenar los sonidos digitalizados y posteriormente poder interpretarlos cómodamente, independientemente del método escogido.

Los VOC los vamos a utilizar aquí para interpretar la voz humana con una frecuencia de 8khz, que abarca la totalidad de los sonidos que podemos transmitir con nuestra voz, y aunque se podrían interpretar a 20khz, la calidad sería la misma y el tamaño necesario para almacenarlo crecería demasiado. Veamos esto:

Supongamos que este es el sonido real de nuestra voz:

Y éste el sonido digitalizado:

La calidad de la digitalización dependerá del número de muestras por segundo (puntos del gráfico) que tomemos. A mayor calidad, mayor número de muestras, por lo tanto mayor número de puntos (valores) y espacio necesario para almacenarlo.

Los ficheros VOC se hallan divididos en 2 bloques principales: cabecera y datos.

El bloque de cabecera es pequeño, y su contenido es básicamente informativo.

El bloque de datos, por el contrario, incluye el sonido digitalizado y otros datos de interés. Veámoslo con detalle:

A partir de aquí, tenemos el bloque de datos, que a su vez se divide en múltiples subbloques. El primer byte de cada subbloque indica el tipo de datos contenido en dicho bloque, y los siguientes tres bytes nos informan de la longitud del subbloque actual.

¿Por qué existen varios subbloques? Porque en un mismo archivo de voz digitalizada pueden darse varios eventos: por ejemplo, puede haber un período de silencio en la digitalización, o un bucle de repetición (sobre todo en digitalizaciones de fragmentos musicales). Los posibles tipos de subbloques son 8, pero aquí los VOC se usarán exclusivamente para un único sonido digitalizado, sea voz humana o sonido musical, el único tipo implementado será el de Tipo 1.

Tipos de subbloques:

Tipo 0: Terminador. Indica que no existen más bloques después de él, y por tanto la reproducción del fichero acaba.

Tipo 1: Datos de voz. Contiene los datos de la digitalización en sí, e incorpora una pequeña cabecera que informa al driver de la frecuencia de muestreo y del método de compresión usado en este bloque (aquí se usan los que están sin comprimir).

Tipo 2: Continuación de voz. Continúa con los datos de voz del último subbloque.

Tipo 3: Silencio. Define un período de silencio en la digitalización. La longitud del período se incluye en los dos bytes siguientes a la longitud del bloque, y viene dada en unidades de ciclos de muestreo.

Tipo 4: Marca. Se trata de un bloque especial que define una marca en el fichero. Dicha marca puede ser usada para informar a nuestro programa de eventos especiales en el archivo de voz; los valores 0 y FFFFh están reservados para el driver de la SB.

Tipo 5: Texto ASCII. En este bloque se puede incluir cualquier texto que pueda ser de ayuda o información, como nombre del creador, o comentarios sobre el fichero.

Tipo 6: Bucle de repetición. Se informa al driver, del comienzo de un bucle que repite el sonido contenido entre el siguiente bloque de datos y el siguiente bloque de final de bucle.

Tipo 7: Fin de bucle de repetición. Indica el fin, del bucle de repetición, explicado en el tipo anterior.

Tipo 8: Bloque extendido. Incluye atributos del siguiente subbloque de datos, como frecuencia de muestreo, compresión, etc.

A continuación se presenta el procedimiento que lee un fichero VOC (comentado en el propio código) y lo almacena en memoria usando las rutinas que también se reflejan en el módulo "VOC.C", además de varias pequeñas rutinas que son necesarias para el funcionamiento:

// Carga el fichero VOC en la memoria reservada

void CargaVoc(char nombre[12])
{
    FILE *ficherovoc;
 
    ficherovoc=fopen(nombre,"rb");
    if (!ficherovoc)
    { printf("\nNo se encuentra el fichero %s",nombre); exit (0); }
    fseek(ficherovoc,0L,SEEK_END);
    // 32: 20 de cabecera global+12 de cabecera de bloque.
    // Suprimo 100 para evitar el chasquido del final de bloque de forma que al
    // interpretar otro VOC posteriormente, suene limpiamente sin necesidad de
    // resetear de nuevo la SB
    longitud=ftell(ficherovoc)-32-100; fseek(ficherovoc,30,0);
    frecuencia=getc(ficherovoc); frecuencia=1000000L/(256l-(frecuencia));
    fseek(ficherovoc,32L,SEEK_SET); fread(zona5,longitud,1,ficherovoc);
    fclose(ficherovoc);
}

Y ahora, por fin, el código que interpreta un bloque de datos digitalizados de hasta 64 Kbytes usando el acceso directo a memoria.

void VocOn(WORD voc)
{
    WORD dmaoffset,pagina,segmento,desplazamiento,tiempo;
    BYTE tamanoalta,tamanobaja,canaldma=1;
 
    segmento=FP_SEG(zona5);
    desplazamiento=FP_OFF(zona5);
    asm{
            // Transforma la dirección
            mov ax,word ptr segmento
            mov cl,4
            shl ax,cl
            add ax,word ptr desplazamiento
            mov word ptr dmaoffset,ax
            mov ax,word ptr desplazamiento
            mov cl,4
            shr ax,cl
            add ax,word ptr segmento
            mov cl,12
            shr ax,cl
            mov word ptr pagina,ax
            mov dx,0xa
            // Configura el DMA
            mov al,5
            out dx,al
            mov dx,0xc
            xor al,al
            out dx,al
            mov dx,0xb
            mov al,0x49
            out dx,al
            mov ax,word ptr dmaoffset
            mov dx,2
            out dx,al
            xchg ah,al
            out dx,al
            mov dx,0x83
            mov al,byte ptr pagina
            out dx,al
            mov dx,3
            mov ax,word ptr longitud
            // Se indica al DMA cuántos bytes debe transmitir
            dec ax
            out dx,al
            xchg ah,al
            out dx,al
            mov dx,0xa
            mov al,byte ptr canaldma
            out dx,al
            // Habilita el canal de DMA
            mov bx,word ptr longitud
            mov byte ptr tamanobaja,bl
            mov byte ptr tamanoalta,bh
        }
        if (voc==0) tiempo=256-(1000000/8000);
        else tiempo=256-(1000000/voc);
        // Espera para que la SB se reajuste a lo indicado en Ensamblador, arriba
        delay(100);
        // Establece la frecuencia del DSP
        EscribeEnDSP(0x40);
        EscribeEnDSP(tiempo);
        EscribeEnDSP(0x14);
        // Empieza la transferencia
        EscribeEnDSP(tamanobaja);
        EscribeEnDSP(tamanoalta);
}

Ya hemos visto qué son los ficheros VOC y cómo manejarlos. Ahora vamos a analizar otro tipo de ficheros de sonido pero decenas de veces más complejo.
 

3.4.2.- Ficheros MOD

Un fichero MOD es un conjunto de información que por sí sola y con el auxilio del código pertinente, puede generar música.

Todos sabemos que cualquier producto software que incorpore una música que vaya acorde con el tema que se esté tratando, no sólo ameniza la sesión, sino que invita a seguir con ella paliando un poco el aburrimiento.
Hay muchos tipos de formatos para el concepto de MOD. Esto es debido a que es un formato universal que nació con los revolucionarios ordenadores Commodore Amiga y que se ha trasladado al mundo del PC.

Cada grupo de programación importante que lo ha manejado, ha ido aportando mejoras con respecto al original, de forma que determinar un estándar es algo bastante arriesgado. Por esto, se ha decidido implementar el tipo de MOD que es el básico en todo tipo de programas que soportan este formato y cuyas características son:
 

Canales independientes
4
Samples Distintos
31
Longitud Máxima por Sample
64 Kb
Unidad de Información
8 bits
Patrones Distintos
64
Máximo de Patrones Iguales o Distintos
128
Frecuencia Máxima por Sample
44 Khz
 

Este formato MOD es conocido como el de ProTracker, que es una utilidad para su interpretación y cuyos siguientes 12 campos de cabecera (dentro de un mismo archivo MOD) pueden observarse en la siguiente tabla.

Los campos 2 al 7 se encuentran presentes en todos los samples, mientras que el resto se refiere a todo el archivo MOD y sólo aparecen una vez.

Esta es la cabecera de un MOD:
 
 
Campo
Tamaño
Tipo
Descripción
1
20 bytes 
Global
Nombre de la canción en formato ASCII, rellenándose los bytes no usados con ceros.
2
22 bytes
Sample
Nombre del Sample en ASCII, rellenándose con ceros los bytes no usados.
3
2 bytes
Sample
Tamaño del Sample en Words. Multiplicando el número por 2 se obtiene el tamaño. Cuando vale 0 o 1, está vacío.
4
1 byte
Sample
Finetune. Nibble (4 bits) con signo (-8 a +7 en decimal). Cada incremento cambia la nota un octavo de semitono.
5
1 byte
Sample
Valor del volumen de cada Sample (0 a 64 en decimal).
6
2 bytes
Sample
Offset de repetición. Cuando se quiere que un Sample se repita varias veces. Indica a partir de qué posición, en palabras, comenzará el Loop. Si vale 0 es nulo.
7
2 bytes
Sample
Longitud de repetición. Si es mayor que 1 indica repetición, y debe repetirse desde la posición indicada en el campo anterior (Campo 6), un nº de palabras igual a este valor.
8
1 byte
Global
Número de patrones. (1 a 128). Indica el número de patrones distintos que existen en el MOD.
9
1 byte
Global
Valor constante = 127.
10
128 bytes
Global
Tabla de patrones. Aquí se guardan los patrones que se van a tocar en cada posición (0 a 63). Rellenando con ceros el espacio sobrante.
11
4 bytes
Global
Letras M.K. En honor al que descubrió la forma de pasar de 15 samples a 31 sin casi ningún cambio en los MOD.
12
1024bytes
Global
Información de los patrones.
 

Ahora es necesario dar un concepto más entendible de lo que es un archivo MOD.

Éstos se dividen en patrones (el mismo concepto que el usado en el Editor, donde cada uno contenía 20 órdenes como máximo), que son como tablas en las que podemos almacenar hasta 64 notas musicales distintas para cada uno de los cuatro canales. Así, un patrón se puede ver como la siguiente tabla:
 
 

Canal 1
Valor
Canal 2
Valor
Canal 3
Valor
Canal 4
Valor
sample 6
31
sample 5
32
sample 9
14
sample 1
45
...
...
...
...
...
...
...
...
sample 6
20
sample 2
2
sample 7
3
sample 1
31
 

Cada Canal lleva un número de sample que se interpretará por él, así como el valor de la nota para ese sample. Se puede pensar en un sample como en un archivo .VOC que se va interpretando a mayor o menor frecuencia (velocidad), tono...

Como la SB sólo es capaz de producir un sonido a la vez por su única salida (y queremos 4), es necesario hacer una media aritmética entre los sonidos y crear un sonido que sea la combinación de todos ellos con sus características para cada una de los 64 celdas de cada patrón.

Ahora vamos a ver en mayor profundidad cómo son estos patrones:

Los cuatro bytes son en realidad grupos de bits, cada uno de los cuales tiene su significado dentro del grupo (los cuatro bytes).

Como vemos, gracias a estos grupos de bits, designamos el sample, su periodo y sus efectos asociados.

Los efectos son cambios sobre el sonido normal del sample. Por ejemplo: Trémolo, vibrato, glissando, slide, saltos, subidas/bajadas de volumen, etc., los cuales tienden a añadir brillantez (y bastante complejidad al programarlos) y profesionalidad a las melodías que vamos a ser capaz de interpretar. Estos efectos son funciones de dominio público, pero no nos equivoquemos, no quiero decir que sean implementados por códigos creados por otros autores y donados como "dominio público", sino que me refiero al hecho de cómo se implementan. Un ejemplo tonto para aclararlo:

Cuando se descubrió la forma de sumar mediante un ordenador, el autor difundió la idea de cómo hacerlo, es decir, el método, pero no el código, que dependía de la máquina. Pues igualmente, el que creó el efecto "vibrato" en la música clásica, difundió la idea de qué era y cómo realizarlo. Por esto, sólo hay una forma de crear un "vibrato": particionar la onda de sonido en varios valores y solapar las zonas contiguas. Con lo que el efecto es: de dominio público.

El código que lo implementa y está incluido en el fichero "CARGAMOD.ASM", es:

Vibrato: mov dh,dl
and dl,0Fh
shr dh,4
shl dh,2
add [di+VibPos],dh
mov dh,[di+VibPos]
mov bl,dh
shr bl,2
and bx,1Fh
mov al,[SinTable+bx]
mul dl
rol ax,1
xchg al,ah
and ah,1
test dh,dh
jns VibUp
neg ax
 
Inserto

Los códigos fuente que se encargan de este tipo de ficheros están realizados en Ensamblador puro. Ahora es necesario aclarar el porqué en el cambio de la filosofía de programación utilizada hasta este momento (Ensamblador Inmerso y C puro).

Casi toda la parte que se encarga de ejecutar las órdenes están en C o en Ensamblador Inmerso. Originalmente las rutinas para interpretar los archivos MOD fueron creadas en C, pero eran demasiado lentas y el resultado era decepcionante para el esfuerzo invertido en comprender este tipo de archivo tan complejo y extremadamente lioso.

Posteriormente fue traducido al Ensamblador Inmerso, en busca de la velocidad imprescindible, pero aunque los resultados fueron los esperados, apareció un nuevo problema: algunas instrucciones de Ensamblador no estaban implementadas, tales como la creación de una pila y la utilización de diversos operandos y direccionamientos de memoria, con lo que hubo que suplirlos con técnicas que no eran las más adecuadas, pero que constituían la única solución.

Esto dio como resultado un código que verdaderamente interpretaba de forma correcta los MOD, pero con el inconveniente insalvable de que al conjuntarlo con el resto del código del Proyecto, no funcionaba de ninguna manera. Esto era debido a las características del extremadamente rígido modelo de compilación "Large".

Concretando, para hacer que el lector de MOD funcionase conjuntamente con el resto del código, debía de compilarse todo en el modo Compact, pero de esta forma algunos procedimientos no funcionaban por su necesidad de acceder a punteros lejanos forzosamente, y a que la conversión directa del C (far *) no funciona correctamente en esta versión del compilador (tal y como se refleja en los FAQ "Frecuently Asqued Questions" de Borland).

Tras esto, la única esperanza fue hacer el último esfuerzo antes de tirar el código a la basura y olvidarse de esta parte tan importante, por lo que finalmente traduje todo a Ensamblador puro y lo compilé con el Turbo Assembler 4.0, creando los OBJ pertinentes y "linkándolos" con el resto del código desde el compilador Borland C++ 3.1.

Esto dio como resultado tres módulos distintos: Sb.ASM, CargaMod.ASM y TocaMOD.ASM, que por su aislamiento con respecto al resto del código, tienen que incluir rutinas ya implementadas en C y Ensamblador Inmerso como las de reserva de memoria mediante la función 48h, el acceso a DMA, la inicialización y detección de la SB, etc.

Pero Borland volvió a hacer de las suyas. Ahora indicaba que se había excedido su capacidad de compilación debido a: Group "Dgroup" execeds 64kb.




AULA MACEDONIA
a
MACEDONIA Magazine