Manual
logo



Programación C++ con AGU desde 0

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.