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:
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):
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();