14. Cómo reproducir sonidos. Séptimo juego: SimeonDice.

La mayoría de las versiones actuales de lenguajes como C y Pascal incluyen posibilidades básicas de creación de sonidos a través del altavoz del ordenador. En ciertos entornos como Windows será fácil reproducir sonidos digitalizados, porque el propio sistema nos da facilidades. Otras melodías complejas pueden requerir que sepamos exactamente de qué forma se almacenan las notas y los instrumentos musicales empleados, o bien que usemos bibliotecas de funciones que nos simplifiquen un poco esa tarea.

Vamos a comentar los principales tipos de sonidos por ordenador que nos pueden interesar para nuestros juegos y a ver cómo reproducirlos:

1) Sonidos simples mediante el altavoz. Muchos lenguajes permiten emitir un sonido de una cierta frecuencia durante un cierto tiempo.

Por ejemplo, con Turbo Pascal empezamos a emitir un sonido con la orden "sound(freq)" donde "freq" es la frecuencia del sonido (más adelante veremos alguna de las frecuencias que podemos usar). Paramos este sonido con "nosound". Para que el sonido dure un cierto tiempo, podemos usar "delay(ms)" para esperar una cantidad de milisegundos, o bien hacer otra cosa mientras se escucha el sonido y comprobar la hora continuamente.

Esta es la misma idea que sigue Free Pascal (aunque esta posibilidad puede no estar disponible en todas las plataformas). También existen estas órdenes para muchos compiladores de C como Turbo C y DJGPP (para DJGPP están declaradas en "pc.h"):

sound(freq);
...
nosound();

2) Sonidos digitalizados. También existe la posibilidad de capturar un sonido con la ayuda de un micrófono y reproducirlo posteriormente. El estándar en Windows es el formato WAV , que podremos reproducir fácilmente desde lenguajes diseñados para este sistema operativo. Otro formato conocido, y que se usaba bastante en los tiempo de MDos, es el VOC , de Creative Labs, la casa desarrolladora de las tarjetas de sonido SoundBlaster. Y un tercer formato, también usado actualmente es el AU , el estándar para  Java. No es difícil encontrar herramientas (incluso gratuitas) para convertir de un formato a otro nuestros sonidos, si nos interesa. Un cuarto formato que es imprescindible mencionar es el formato MP3 , que es un formato comprimido, lo que supone una cierta pérdida de calidad a cambio de ocupar menos espacio (habitualmente cerca de un 10% de lo que ocuparía el WAV correspondiente).

En nuestro caso, podríamos capturar un sonido con la Grabadora de Windows, guardarlo en formato WAV y reproducirlo con una secuencia de órdenes parecida a ésta: 

SAMPLE *sonido;
int pan = 128;
int pitch = 1000;

...

allegro_init();

...

install_timer();

...

sonido = load_sample(nombreFichero);

if (!sonido) {
      allegro_message("Error leyendo el fichero WAV '%s'\n", nombreFichero);
      return 1;
   }

play_sample(
    sonido,   // Sonido a reproducir
    255,      // Volumen: máximo
    0,        // Desplazamiento: ninguno
    1000,     // Frecuencia: original
    FALSE);   // Repetir: no
...
destroy_sample(sonido);

En la orden "play_sample" los parámetros que se indican son: el sonido a reproducir, el volumen   (0 a 255), el desplazamiento (posición a partir de la que reproducir, "pan", 0 a 255), la frecuencia (relativa: 1000 es la velocidad original, 2000 es el doble y así sucesivamente) y si se debe repetir (TRUE para si y FALSE para no repetir). Si un sonido se está repitiendo, para pararlo usaremos "stop_sample".

3) Melodías MIDI. Son melodías formadas por secuencias de notas. Suelen ser relativamente fáciles de crear con programas que nos muestran partituras en pantalla, o incluso conectando ciertos instrumentos musicales electrónicos (teclados, por ejemplo) a una conexión que muchos ordenadores incluyen (MIDI: Musical Instrument Device Interface). Entornos como Windows (e incluso muchos teléfonos móviles actuales) incluyen reproductores de sonido capaces de manejar ficheros MID . Nosotros podríamos escucharlos haciendo algo como

MIDI *miMusica;

...

allegro_init();

...

install_timer();

...

if (install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, argv[0]) != 0) {
    allegro_message("Error inicializando el sistema de sonido\n%s\n", allegro_error);
    return 1;
}

miMusica = load_midi(nombreFichero);

if (!miMusica) {
      allegro_message("Error leyendo el fichero MID '%s'\n", nombreFichero);
      return 1;
   }

play_midi(miMusica, TRUE);
...
destroy_midi(miMusica);

En la orden "play_midi"  los dos únicos recuerdan a los de "play_sample": la música a reproducir y si se debe repetir (TRUE para si y FALSE para no repetir). Si una música se está repitiendo, seguirá hasta que se indique una nueva o se use "stop_midi".

4) Melodías MOD y similares (S3M, XM, etc). Son formatos más complejos que el anterior, ya que junto con las notas a reproducir se detalla cómo "suena" cada uno de los instrumentos (normalmente usando una "muestra" digitalizada -en inglés: "sample"-). Allegro no incluye rutinas para reproducir ficheros de este tipo (al menos en la versión existente a fecha de escribir este texto).

14b. Séptimo juego: SimeonDice.

(Aun no disponible)

El "Simon" es un juego clásico de memoria. La versión más habitual es un tablero electrónico con 4 pulsadores, de 4 colores distintos, cada uno de los cuales emite un sonido distinto:

Simon Original

El juego consiste en repetir la secuencia de sonidos (y luces) que nos va proponiendo el ordenador: primero será una sola nota; si la recordamos, se añade un segunda nota; si recordamos estas dos se añade una tercera, después una cuarta y así sucesivamente hasta que cometamos un error.

De modo que la secuencia de nuestra versión del juego, ya en algo más cercano al lenguaje que el ordenador entiende sería:

secuenciaDeNotas = vacia
repetir
  generar nuevaNota al azar
  anadir nuevaNota a secuenciaDeNotas
  reproducir secuenciaDeNotas
  fallo = FALSO
  desde i = 1 hasta (i=numeroDeNotas) o hasta (fallo)
    esperar a que el usuario escoja una nota
    si notaEscogida != nota[i] entonces fallo=VERDADERO
  finDesde
hasta fallo
puntuacion = numeroNotasAcertadas * 10

Y nuestro tablero podría ser sencillamente así (debajo de cada color recordamos la tecla que se debería pulsar):

Simeon - pantalla de juego

Las notas que yo he reproducido en este ejemplo son en formato MIDI. Si quieres hacer lo mismo en tu ordenador necesitarás algún editor de melodías MIDI (no debería ser difícil encontrar alguno gratuito en Internet). Si no tienes acceso a ninguno y/o prefieres usar las mismas notas que he creado yo, aquí las tienes comprimidas en un fichero ZIP .

Yo he plasmado todo el juego así (nota: en mi ordenador funciona correctamente bajo Windows, pero si compilo para DOS no se oye el sonido, se supone que porque no he instalado el driver MIDI en MsDos para mi tarjeta de sonido):

 

/*------------------------------*/
/*  Intro a la programac de     */
/*  juegos, por Nacho Cabanes   */
/*                              */
/*    ipj14c.c                  */
/*                              */
/*  Decimocuarto ejemplo:       */
/*    "Simeon dice"             */
/*                              */
/*  Comprobado con:             */
/*  - MinGW 2.0.0-3 (gcc 3.2)   */
/*    y Allegro 4.02 - Win XP   */
/*  - DevC++ 4.9.9.2(gcc 3.4.2) */
/*    y Allegro 4.03 - Win XP   */
/*------------------------------*/
 
 
/* -------------------
 
Planteamiento del juego:
 
secuenciaDeNotas = vacia 
repetir 
  generar nuevaNota al azar 
  anadir nuevaNota a secuenciaDeNotas 
  reproducir secuenciaDeNotas 
  fallo = FALSO 
  desde i = 1 hasta (i=numeroDeNotas) o hasta (fallo) 
    esperar a que el usuario escoja una nota 
    si notaEscogida != nota[i] entonces fallo=VERDADERO 
  finDesde 
hasta fallo 
puntuacion = numeroNotasAcertadas * 10 
 
 
------------------- */
 
#include <stdlib.h>         // Para "rand"
#include <ctype.h>          // Para "tolower"
#include <allegro.h>
 
 
/* -------------- Constantes globales ------------- */
#define ANCHOPANTALLA 320
#define ALTOPANTALLA 200
#define MAXNOTAS 300
#define RETARDONOTA 200
#define RETARDOETAPA 1000
 
#define TECLA1 'w'
#define TECLA2 'e'
#define TECLA3 's'
#define TECLA4 'd'
 
#define FICHEROSONIDO1 "simon1.mid"
#define FICHEROSONIDO2 "simon2.mid"
#define FICHEROSONIDO3 "simon3.mid"
#define FICHEROSONIDO4 "simon4.mid"
 
/* -------------- Variables globales -------------- */
int 
   notaActual = 0,   // Nmero de nota actual
   notas[MAXNOTAS],  // Secuencia de notas
   acertado;         // Si se ha acertado o no
 
MIDI *sonido1, *sonido2, *sonido3, *sonido4; 
 
/* -------------- Rutina de inicializacin -------- */
int inicializa()
{
    allegro_init();        // Inicializamos Allegro
    install_keyboard();
    install_timer();
 
                           // Intentamos entrar a modo grafico
    if (set_gfx_mode(GFX_SAFE, ANCHOPANTALLA, ALTOPANTALLA, 0, 0) != 0) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message(
            "Incapaz de entrar a modo grafico\n%s\n",
            allegro_error);
        return 1;
    }
 
                            // e intentamos usar midi
    if (install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, "") != 0) { 
        allegro_message("Error inicializando el sistema de sonido\n%s\n", allegro_error); 
        return 2; 
    }     
 
    sonido1 = load_midi(FICHEROSONIDO1); 
    if (!sonido1) { 
        allegro_message("Error leyendo el fichero MID '%s'\n", FICHEROSONIDO1); 
        return 3; 
    } 
 
    sonido2 = load_midi(FICHEROSONIDO2); 
    if (!sonido2) { 
        allegro_message("Error leyendo el fichero MID '%s'\n", FICHEROSONIDO2); 
        return 3; 
    }
 
    sonido3 = load_midi(FICHEROSONIDO3); 
    if (!sonido3) { 
        allegro_message("Error leyendo el fichero MID '%s'\n", FICHEROSONIDO3); 
        return 3; 
    }
 
    sonido4 = load_midi(FICHEROSONIDO4); 
    if (!sonido4) { 
        allegro_message("Error leyendo el fichero MID '%s'\n", FICHEROSONIDO4); 
        return 3; 
    }   
 
    // Preparo nmeros aleatorios
    srand(time(0));
 
    // Volumen al mximo, por si acaso
    set_volume(255,255);
 
    // Y termino indicando que no ha habido errores
    return 0;
}
 
 
 
/* -------------- Rutina de dibujar pantalla ------ */
void dibujaPantalla()
{
 
    // Borro pantalla
    clear_bitmap(screen);
 
    // Primer sector: SupIzq -> verde    
    rectfill(screen, 10,10,
        ANCHOPANTALLA/2-10,ALTOPANTALLA/2-30,
        makecol(0, 150, 0));
    textprintf(screen, font, ANCHOPANTALLA/4, ALTOPANTALLA/2-28, 
        makecol(0, 150, 0), "%c",
        TECLA1);
 
    // Segundo sector: SupDcha -> rojo
    rectfill(screen, ANCHOPANTALLA/2+10,10,
        ANCHOPANTALLA-10,ALTOPANTALLA/2-30,
        makecol(150, 0, 0));
    textprintf(screen, font, ANCHOPANTALLA/4*3, ALTOPANTALLA/2-28,
        makecol(150, 0, 0), "%c",
        TECLA2);
 
    // Tercer sector: InfIzq -> amarillo
    rectfill(screen, 10,ALTOPANTALLA/2-10,
        ANCHOPANTALLA/2-10,ALTOPANTALLA-50,
        makecol(200, 200, 0));
    textprintf(screen, font, ANCHOPANTALLA/4, ALTOPANTALLA-48, 
        makecol(200, 200, 0), "%c",
        TECLA3);
 
    // Cuarto sector: InfDcha -> azul
    rectfill(screen, ANCHOPANTALLA/2+10,ALTOPANTALLA/2-10,
        ANCHOPANTALLA-10,ALTOPANTALLA-50,
        makecol(0, 0, 150));
    textprintf(screen, font, ANCHOPANTALLA/4*3, ALTOPANTALLA-48, 
        makecol(0, 0, 150), "%c",
        TECLA4);
 
 
    textprintf(screen, font, 4,ALTOPANTALLA-20, palette_color[13], 
        "Puntos: %d", notaActual*10);   // Puntuacin
}
 
 
/* -------------- Rutina de reproducir notas ------ */
void reproduceNotas()
{
    int i;
 
    for (i=0; i<=notaActual; i++) {
 
        if (notas[i] == 0) {
            play_midi(sonido1, FALSE);
            rectfill(screen, 10,10,
                ANCHOPANTALLA/2-10,ALTOPANTALLA/2-30,
                makecol(255, 255, 255));
        }
 
        if (notas[i] == 1) {
            play_midi(sonido2, FALSE);
            rectfill(screen, ANCHOPANTALLA/2+10,10,
                ANCHOPANTALLA-10,ALTOPANTALLA/2-30,
                makecol(255, 255, 255));
            }
 
        if (notas[i] == 2) {
            play_midi(sonido3, FALSE);
            rectfill(screen, 10,ALTOPANTALLA/2-10,
                ANCHOPANTALLA/2-10,ALTOPANTALLA-50,
                makecol(255, 255, 255));
        }
 
        if (notas[i] == 3) {
            play_midi(sonido4, FALSE); 
            rectfill(screen, ANCHOPANTALLA/2+10,ALTOPANTALLA/2-10,
                ANCHOPANTALLA-10,ALTOPANTALLA-50,
                makecol(255, 255, 255));
        }
        rest(RETARDONOTA);
        dibujaPantalla();        
    }
}
 
 
/* -------------- Rutina de comparar notas ------ */
int comparaNota(char tecla, int notaActual)
{
    int i;
 
    // Presupongo que no ha acertado y comparare 1 x 1
    int seHaAcertado = 0;
 
    if ( (tecla == TECLA1) && (notas[notaActual] == 0) ){
        play_midi(sonido1, FALSE);
        rectfill(screen, 10,10,
            ANCHOPANTALLA/2-10,ALTOPANTALLA/2-30,
            makecol(255, 255, 255));
        seHaAcertado = 1;
    }
 
    if ( (tecla == TECLA2) && (notas[notaActual] == 1) ){
        play_midi(sonido2, FALSE);
        rectfill(screen, ANCHOPANTALLA/2+10,10,
            ANCHOPANTALLA-10,ALTOPANTALLA/2-30,
            makecol(150, 0, 0));
        seHaAcertado = 1;
        }
 
    if ( (tecla == TECLA3) && (notas[notaActual] == 2) ){
        play_midi(sonido3, FALSE);
        rectfill(screen, 10,ALTOPANTALLA/2-10,
            ANCHOPANTALLA/2-10,ALTOPANTALLA-50,
            makecol(200, 200, 0));
        seHaAcertado = 1;
    }
 
    if ( (tecla == TECLA4) && (notas[notaActual] == 3) ){
        play_midi(sonido4, FALSE); 
        rectfill(screen, ANCHOPANTALLA/2+10,ALTOPANTALLA/2-10,
            ANCHOPANTALLA-10,ALTOPANTALLA-50,
            makecol(255, 255, 255));
        seHaAcertado = 1;
    }
 
    return seHaAcertado;
}
 
 
 
/* ------------------------------------------------ */
/*                                                  */
/* -------------- Cuerpo del programa ------------- */
 
int main()
{
 
    int i;
    char tecla;
 
    // Intento inicializar
    if (inicializa() != 0)
        exit(1);
 
    dibujaPantalla();
    textprintf(screen, font, ANCHOPANTALLA/4, ALTOPANTALLA/2 - 40, 
        makecol(255, 255, 255), " S I M E O N     D I C E ");
    textprintf(screen, font, ANCHOPANTALLA/4, ALTOPANTALLA/2, 
        makecol(255, 255, 255), "Pulsa una tecla para jugar");
    readkey();
 
    do {  // Parte que se repite hasta que falle
 
        dibujaPantalla();  // Dibujo la pantalla de juego
 
        // Genero nueva nota y reproduzco todas
        notas[notaActual] = rand() % 4;
        reproduceNotas();
 
        acertado = 1;       // Presupongo que acertar
 
        // Ahora el jugador intenta repetir
        i = 0;
        do {
            tecla = tolower(readkey());  // Leo la tecla
            if (tecla == 27) break;      // Salgo si es ESC
            acertado = comparaNota(tecla, i); // Comparo
            i ++;                        // Y paso a la siguiente
        } while ((i<=notaActual) && (acertado == 1));
 
        // Una nota ms
        if (acertado == 1) {
            textprintf(screen, font,
                ANCHOPANTALLA/4, ALTOPANTALLA/2, 
                makecol(255, 255, 255), "Correcto!");
            notaActual ++;
        } 
        else {
            textprintf(screen, font,
                ANCHOPANTALLA/4, ALTOPANTALLA/2, 
                makecol(255, 255, 255), "Fallaste!");
        }
 
        rest (RETARDOETAPA);
 
    } while ((acertado == 1) && (notaActual < MAXNOTAS));
 
 
    textprintf(screen, font, 
        ANCHOPANTALLA/4, ALTOPANTALLA/2 + 20, palette_color[15], 
        "Partida terminada"); 
    readkey();
    return 0;
 
}
 
            /* Termino con la "macro" que me pide Allegro */
END_OF_MAIN();