Cronómetro Double-Dutch
Problema
De las cosas
que más me gusta del reloj, es que sigue funcionando
aún
cuando estemos utilizando otra aplicación... Claro, que si no
lo
ves convendría que avisará en los cambios. Bueno
leyendo
las instrucciones del reglamento, dice que se avisará a los 15
y
30 segundos, y en el cambio. ¿Cómo gestionar el audio?
Convendrá
aclarar que no soy un especialista en audio -lo cierto es que no lo soy
en nada, pero en audio mis limitaciones son mayores-. Buscando por
Internet encuentro que en C++, existen dos librerías:
gstreamermm y ccaudio2. Como ésta última se puede
instalar con Synaptic procedemos a trabajar con la librería
ccaudio2. La librería gstreamermm, no es oficial, aunque si lo
es la famosa gstreamer (para programación en C).
Para este
proyecto, he realizado una adaptación de
este código.
Vamos a necesitar tres archivos
de audio: cambio.wav, quince.wav y treinta.wav.
En
mi caso, he utilizado el programa audacity para editar los archivos de
audio, que como observas en la imagen son archivos muy cortos (menos de
medio segundo, en el ejemplo), lo cual es importante para el correcto
funcionamiento de nuestro programa. El cual queda así:
main.cc (sin cambios)
//crono.h
#ifndef _CRONO_COMBA_H
#define _CRONO_COMBA_H
#include <gtkmm.h>
#include <libglademm/xml.h>
#include
<cc++/audio2.h>
class crono:public Gtk::Window
{
public:
crono(BaseObjectType* www, const
Glib::RefPtr<Gnome::Glade::Xml>& refGlade);
virtual ~crono();
protected:
int segundos;
int veces;
Glib::RefPtr<Gnome::Glade::Xml> refXml;
Gtk::Button* boton;
Gtk::Label* etiqueta;
sigc::connection conexion;
bool
tiempo();
int play(const char* file);
// señales
virtual void pulsa_contador();
};
#endif
Recuerda
que la librería ccaudio2 (libccaudio2) debe añadirse
a
nuestro proyecto. Y previamente, cargada, con Synaptic a nuestro
sistema.
Básicamente añadimos una
función play(), que va a ser la encargada de hacer sonar el
archivo de sonido correspondiente, el cual se pasa como argumento. Esta
función no la he desarrollado en el archivo crono.cc, sino en
otro archivo crono2.cc:
//crono2.cc
#include "cron.h"
int crono::play(const char* file){
//declaración de datos
ost::AudioDevice *dev;//dispositivo de
sonido
ost::AudioStream archivo;//archivo de
sonido
ost::Audio::Linear buffer;//memoria para
almacenar el sonido en formato audio
ost::Audio::Info info;// datos para el
archivo
unsigned tamanobuffer, bloques;
// Tratando de seleccionar un
dispositivo de audio
dev = archivo.getDevice();
// comprobando la operación
if(!dev)
{
return
-1; //tenemos un problema con el dispositivo de
audio
//quizás lo esté
utilizando otro
programa.
}
// Tratando de abrir el archivo de audio
archivo.open(file,ost::Audio::modeRead,10);
if(!archivo.isOpen()||!archivo.isStreamable())
{
delete dev; // en esta
operación si
está creado dev, como nos vamos, hay que borrarlo
return
-2; //problemas con el archivo de audio,
puede ser que no
//esté en el directorio, o que
no tengamos
los codecs apropiados
}
archivo.getInfo(&info);//obtener
datos archivo
//configurar el dispositivo con las
características de nuestro archivo
// en bloques de 10 milisegundos
if(!dev->setAudio((ost::Audio::Rate)info.rate,
ost::Audio::isStereo(info.encoding), 10))
{
delete
dev;
return -3;
//El rate (frecuencia
muestreo) no es soportada por dispositivo sonido
}
// Calcular el tamaño del
buffer.
tamanobuffer = archivo.getCount();//
devuelve el número de bloques de audio
if(ost::Audio::isStereo(info.encoding))
std::cout<<"Play devuelve
"<<play("cambio.wav")<<std::endl;
buffer = new
ost::Audio::Sample[tamanobuffer *
2];//si el archivo es estéreo necesito el doble
else
buffer =
new ost::Audio::Sample[tamanobuffer];
for(;;)
{ // Convertir un bloque del archivo de audio en
una
muestra de audio legible por el dispositivo de audio
if(ost::Audio::isStereo(info.encoding))
bloques = archivo.getStereo(buffer, 1);
else
bloques = archivo.getMono(buffer, 1);
// Si no hay bloque, es que ya hemos terminado de leerlos todos.
if(!bloques)
break;
//
Colocar las muestras en el dispositivo de audio ("pá" que
suene)
dev->putSamples(buffer, tamanobuffer);
}
dev->sync();//sincronizar el
dispositivo de audio para nuevos usos
delete dev;// eliminar/borrar el objeto
dev
archivo.close(); //cerrar el fichero
abierto
return 0;
}
El archivo crono2.cc, debe
añadirse al proyecto, teniendo como objetivo el archivo
ejecutable.
El archivo está comentado, pero trataré de explicarlo
(tal y como yo lo entiendo):
Empecemos
diciendo que en el disco duro almacenamos información muy
diversa: audio, imágenes, texto,... Dentro de los archivos de
audio, existen numerosos formatos (wav, mp3, ogg...), cada uno de ellos
almacena la información de una manera diferente. Y dentro de
un
mismo formato, puede variar la información según la
calidad de la grabación: frecuencia de muestreo,
estéreo
o mono...
Esta información no es la misma que recibe la
tarjeta de sonido, la cual convierte la información digital
del
ordenador en señal analógica, que es lo que precisa
el
altavoz para funcionar. Es decir, que precisamos convertir la
señal digital de nuestro archivo audio, a una señal
digital legible por nuestro dispositivo de audio.
Por otra parte,
el archivo de audio, correctamente convertido no se transmite en su
totalidad, sino que se realiza en paquetes o muestras (samples)
enviadas cada x tiempo. Esto, denominado Stream, nos permite por
ejemplo escuchar o ver un video por Internet sin habernos descargado el
archivo en su totalidad.
La función selecciona un dispositivo
de audio (si por ejemplo otro programa está utilizando el
audio,
nos resulta imposible seleccionarlo).
Si disponemos de audio,
podemos pasar a abrir el archivo. Podría ocurrir que el
archivo
enviado a la función no fuera de audio, o no existiera, o
fuese
de un formato no compatible, así que tras abrirlo, comprobamos
que puede ser interpretado por la librería libccaudio2.
La
siguiente cuestión es obtener los datos del archivo
(estéreo o mono, la frecuencia de muestreo,...) para
preparar adecuadamente el dispositivo de sonido y calcular el
tamaño del buffer (memoria), que es el instrumento utilizado
para conectar el archivo y el dispositivo.
En
for(;;)
se van convirtiendo los distintos bloques del archivo en
información interpretable por la tarjeta de sonido,
almacenándolos en el buffer (getStereo o getMono); y se van
enviando del buffer a la tarjeta de sonido (putSamples). Como este for
no tiene condiciones
se ejecuta
indefinidamente, para salir del mismo es necesario un
break;,
lo cual se alcanza cuando bloques es igual a 0, bloques obtiene el
valor de la función getMono (o getStereo) que devuelven el
número de bloques leídos, cuando se alcanza el final
del
archivo, bloques=0, y se sale del bucle for(;;).
El archivo crono.cc:
//cron.cc
#include <gtkmm.h>
#include <iostream>
#include <stdlib.h>
#include <libglademm/xml.h>
#include "cron.h"
// DEFINICIÓN DE LOS MÉTODOS DE LA CLASE
// constructor de la primera clase
crono::crono(BaseObjectType* www, const
Glib::RefPtr<Gnome::Glade::Xml>&
refGlade):Gtk::Window(www),refXml(refGlade)
{
segundos=0;
veces=1;
refXml->get_widget("boton", boton);
boton->signal_clicked().connect(sigc::mem_fun(*this,&crono::pulsa_contador));
refXml->get_widget("etiqueta", etiqueta);
}
// destructor
crono::~crono(){std::cout<<"Eso es todo
amigo"<<std::endl; conexion.disconnect();}
// función para manipular la señal
void crono::pulsa_contador(){
sigc::connection con =
Glib::signal_timeout().connect((sigc::slot<bool>)sigc::mem_fun(*this,&crono::tiempo),1000);
conexion=con;
char h[40];
sprintf(h,"Sujeto 1-> 00:00
segundos");
etiqueta->set_text(h);
boton->set_sensitive(false);
}
bool crono::tiempo(){
char h[40];
if (veces<=4){
if
(segundos<44){
sprintf (h, "Sujeto %i-> %i:00
segundos",veces, ++segundos);
if(segundos==15)play("quince.wav");
if(segundos==30)play("treinta.wav");
}
else{
if(veces!=4){
sprintf(h,"CAMBIO");
std::cout<<"Play devuelve
"<<play("cambio.wav")<<std::endl;
}else sprintf(h,"Pulsa abajo para
comenzar la prueba");
segundos=0;
veces++;
}
etiqueta->set_text(h);
return
true;
}
else{
boton->set_sensitive();
veces=1;
return
false;
}
}
En primer lugar, observa que ahora en vez de contar para
atrás, cuenta para adelante 1->45.
En relación al sonido, observa que llamamos a la
función
play
con el archivo que nos interesa, en función del momento. En el
momento del cambio, también se ve como aprovecho la consola
para
obtener información de la función play, lo cual puede
ser
interesante para analizar el error cuando el programa no funcione
adecuadamente.
Por
razones que desconozco, el código de audio funciona
perfectamente aisladamente, pero al reproducir éste en un
"timeout", el sonido se escucha regular. ¡Ahhh! y si el archivo
de audio es muy largo, acontece que se requiere la utilización
del objeto (functor, que no función) cuando éste, no ha
sido liberado, dando problemas; los archivos de audio deben ser muy
cortos para que una vez ejecutado no se altere el audio... habrá
que probar con gstreamer.