Determina tu umbral de audición

Vicente González Ruiz

June 24, 2014

Contents

1 La práctica
2 Configuración del audio
 2.1 Está ALSA corriendo?
 2.2 ALSA no está, cómo se instala?
3 Generando “pitos”
4 La modulación de señales
5 modulator
6 dc
7 La Transformada de Fourier Discreta
8 Relación entre la frecuencia de muestreo y la ventana de análisis
9 Minimizando la distorsión provocada por la duración limitada de la ventana de análisis
10 RTASA: un analizador de espectro
 10.1 Entradas
 10.2 Salidas
 10.3 Códigos fuente
11 Ejemplos de utilización
12 Determinación del umbral de audición

1 La práctica

Vamos a usar nuestra computadora y unos cascos de audio para determinar nuestro umbral de audición.1 El procedimiento va a ser el siguiente:

  1. Generar un tono puro, a una determinada frecuencia y a una baja intensidad.
  2. Comenzar a subir la amplitud del tono hasta que comience a oirse. En ese intante, anotar la intensidad del tono.
  3. Cambiar la frecuencia del tono e ir al paso 1 mientras se desee obtener más muestras el umbral de audición.

2 Configuración del audio

Existen diferentes drivers de audio para Linux y con distintas posibilidades. Uno de ellos es ALSA (Advanced Linux Sound Architecture) y es el que nosotros vamos a usar en esta práctica.

2.1 Está ALSA corriendo?

Ejecutar:

cat /dev/sndstat

si el fichero existe es que ALSA está ejecutándose y está bien configurado.

Dependiendo del hardware de sonido de nuestra computadora, es posible que el archivo anterior no exista o tenga otro nombre. También podemos comprobar si nos funciona ALSA ejecutando:

host$ arecord -f cd -t raw | aplay -f cd -t raw

Si nos escuchamos a nosotros mismos cuando hablamos por el micro, entonces todo está correcto.

2.2 ALSA no está, cómo se instala?

Como administrador instalamos los paquetes correspondientes:

# apt-get update               # Actualizamos la lista de paquetes  
# apt-get install alsa-base    # Instalamos el driver de ALSA  
# apt-get install alsa-utils   # Instalamos algunas utilidades

3 Generando “pitos”

Vamos a generar tonos puros (sonidos que tienen sólo una componente de frecuencia). Para ellos usaremos dos programas llamados modulator y dc. Un modulador es un elemento que realiza la modulación de una señal. El otro programa simplemente genera una señal constante (dc = direct current). Veámos cómo se relacionan ambos programas para generar un tono puro.

4 La modulación de señales

Un modulador posee dos entradas y una salida. Una de las entradas es la señal modulante (señal de datos) y la otra es un parámetro que controla la frecuencia de la señal portadora. A la salida encontramos la señal modulada:

Se\~nal modulante  +-----------+  Se\~nal modulada  
---------------->| Modulador |---------------->  
                 +-----------+  
                       ^  
                       |  
                  Frecuencia  
                   portadora

Una forma sencilla de modular una señal es desplazar (hacia frecuencias superiores) el espectro de frecuencias de dicha señal. Para desmodular simplemente movemos de nuevo el espectro de la señal de forma que quede en su posición inicial.

5 modulator

El programa modulator.c que puede descargarse de http://www.ace.ual.es/\~vruiz/imyso/modulator.tar.gz es un modulador de señales digitales y su código fuente es:

 
/* 
 * modulator.c 
 * 
 * Lee desde la entrada estndar una seal de audio (digital), a 16 bits 
 * por muestra, usando el endian de la mquina. Modula la seal a 
 * partir de una frecuencia especificada por uno o ms clientes que la 
 * especifican a travs de un socket TCP. Finalmente escribe la seal 
 * modulada en la salida estndard. 
 * 
 * gse. 2006. 
 * 
 */ 
 
/****************************************************************************** 
 * 
 * Ficheros cabecera. 
 * 
 *****************************************************************************/ 
 
/* Entrada y salida de streams buffereados. */ 
#include <stdio.h> 
 
/* Biblioteca de funciones estndar (exit(), EXIT_SUCCESS, 
   EXIT_FAILURE, ...) */ 
#include <stdlib.h> 
 
/* Manipulacin de cadenas y movimiento de memoria (memset(), ...). */ 
#include <string.h> 
 
/* Biblioteca estndar de funciones relacionadas con el sistema 
   operativo Unix (read(), write(), ...). */ 
#include <unistd.h> 
 
/* Biblioteca estndar de clculos matemticos en punto flotante 
   (sin(), rint(), ...).*/ 
#include <math.h> 
 
/* POSIX Threads (pthread_t, pthread_create(), ...). */ 
#include <pthread.h> 
 
/* Sockets. */ 
/* Ms info en: 
 * 
 * http://www.csce.uark.edu/~aapon/courses/os/examples/concurrentserver.c 
 * http://beej.us/guide/bgnet/ 
 */ 
 
/* Tipos de datos primitivos del sistema. */ 
#include <sys/types.h> 
 
/* Sockets (socket(), listen(), ...). */ 
#include <sys/socket.h> 
 
/* Direcciones IP, opciones y definiciones. */ 
#include <netinet/in.h> 
 
/* Servicio de resolucin de nombres. */ 
#include <netdb.h> 
 
/* Signals (interrupciones) (signal(), SIGINT). */ 
#include <signal.h>  /* signal(), SIGINT */ 
 
/* Manejo de parmetros desde la lnea de comandos. */ 
#include <getopt.h> 
 
/****************************************************************************** 
 * 
 * Definiciones. 
 * 
 *****************************************************************************/ 
 
/* Nmero de bytes del buffer de audio. */ 
#define BUFFER_SIZE 32 
 
/* Nmero de muestras por segundo (calidad CD). */ 
#define SAMPLES_PER_SECOND 44100 
 
/* Frecuencia por defecto en Hz. */ 
#define DEFAULT_FREQ 440 
 
/* Dos canales. */ 
#define NUMBER_OF_CHANNELS 2 
 
/* Puerto usado para atender a los clientes. */ 
#define LISTENING_PORT 6789 
 
/* Puerto destino de los paquetes UDP enviados a la direccin de broadcast. */ 
#define MULTICAST_PORT 6789 
 
/* Direccin multicast por defecto. Todos los clientes escuchan dicha 
   direccin multicast. */ 
#define MULTICAST_ADDR (char *)"224.0.0.1" 
 
/* Nmero mximo de clientes que esperan a que la conexin sea 
   establecida. */ 
#define QLEN 1 
 
/****************************************************************************** 
 * 
 * Variables globales. 
 * 
 *****************************************************************************/ 
 
/****************************************************************************** 
 * 
 * Funciones. 
 * 
 *****************************************************************************/ 
 
/* Cuerpo principal del programa. */ 
int main(int argc, char *argv[]) { 
  work(argc, argv); 
  return EXIT_SUCCESS; 
} 
 
/* Funcin que realiza todo el trabajo. */ 
work(int argc, char *argv[]) { 
 
  /* Si pulsamos CRTL+C, el programa acaba ejecutando la funcin 
     end(). */ 
  void end() { 
    fprintf(stderr,"%s:CTRL+Cdetected.Exiting...",argv[0]); 
    fprintf(stderr,"done\n"); 
    exit(EXIT_SUCCESS); 
  } 
  signal(SIGINT, end); 
 
  /* Un semforo para acceder "en exclusin mutua" a una zona de 
     cdigo. */ 
  pthread_mutex_t mut; 
 
  /* Creamos el semforo. */ 
  pthread_mutex_init(&mut, NULL); 
 
  /* Frecuencia de la seal sinusoidal en Hz. */ 
  int freq = DEFAULT_FREQ; 
 
  /* Puerto de escucha de los clientes. */ 
  short listening_port = LISTENING_PORT; 
 
  /* Puerto destino de los datagramas multicast. */ 
  short multicast_port = MULTICAST_PORT; 
 
  /* Canal multicast destino. */ 
  char *multicast_addr = MULTICAST_ADDR; 
 
  /* Manipulacin de los parmetros de entrada. */ { 
    int c; 
    while(1) { 
 
      static struct option long_options[] = { 
        {"frequency", required_argument, 0, f}, 
        {"listening_port", required_argument, 0, l}, 
        {"multicast_addr", required_argument, 0, a}, 
        {"multicast_port", required_argument, 0, m}, 
        {"help", no_argument, 0, ?}, 
        {0, 0, 0, 0} 
      }; 
 
      int option_index = 0; 
 
      c = getopt_long(argc, argv, "f:l:a:m:?", long_options, &option_index); 
      if(c==-1) { 
        /* No more options. */ 
        break; 
      } 
 
      switch (c) { 
      case 0: 
        /* If this option set a flag, do nothing else now. */ 
        if (long_options[option_index].flag != 0) 
          break; 
        fprintf(stderr, "option%s", long_options[option_index].name); 
        if (optarg) 
          fprintf(stderr, "witharg%s", optarg); 
        fprintf(stderr, "\n"); 
        break; 
 
      case f: 
        freq = atoi(optarg); 
        break; 
 
      case l: 
        listening_port = atoi(optarg); 
        break; 
 
      case a: 
        multicast_addr = optarg; 
        break; 
 
      case m: 
        multicast_port = atoi(optarg); 
        break; 
 
      case ?: 
        fprintf(stdout, 
                "-[-f]requency=frequency(inHz)ofthemodulatingsignal(%d)\n", 
                freq); 
        fprintf(stdout, 
                "-[-l]isteningport=porttoservetheModulatorControlclients(%d)\n", 
                listening_port); 
        fprintf(stdout, 
                "-[-]multicast_[a]ddress=multicastaddresstocomunicate"); 
        fprintf(stdout, 
                "totheclientsachangeinthevalueofthefrequency(%s)\n", 
                multicast_addr); 
        fprintf(stdout, 
                "-[-]multicast_[p]ort=porttowhichthemulticast"); 
        fprintf(stdout, 
                "packetsaresent(%d)\n", 
                multicast_port); 
        exit(1); 
 
      default: 
        fprintf(stderr, "%s:Unrecognizedargument.Aborting...\n", argv[0]); 
        abort(); 
      } 
    } 
  } 
 
  fprintf(stderr, "%s:frequency=%dHz\n" ,argv[0], freq); 
  fprintf(stderr, "%s:listeningport=%d\n", argv[0], listening_port); 
  fprintf(stderr, "%s:multicastaddress=%s\n", argv[0], multicast_addr); 
  fprintf(stderr, "%s:multicastport=%d\n", argv[0], multicast_port); 
 
  /* Hilo que realiza la sntesis de la sinusoide. */ 
  void *modu(void *arg) { 
 
    /* Angulo de la sinusoide, en radianes. */ 
    double angle_in_radians = 0; 
 
    /* La sinusoide ser generada hasta pulsar CTRL+C. */ 
    for(;;) { 
      int i; 
 
      /* El buffer de audio. */ 
      short buffer[BUFFER_SIZE]; 
 
      /* Leemos la entrada estndar. */ 
      int bytes_read; 
      bytes_read = read(0/* stdin */, buffer, BUFFER_SIZE*sizeof(buffer[0])); 
 
      /* Nmero de muestras ledas (sumando ambos canales). */ 
      int samples = bytes_read / sizeof(buffer[0]); 
 
      //fprintf(stderr,"%d\n",samples); 
 
      /* Media del canal izquierdo y derecho. */ 
      double ave_l = 0.0, ave_r = 0.0; 
      for(i=0; i<samples; i+=2) { 
        ave_l += buffer[i]; 
        ave_r += buffer[i+1]; 
      } 
      ave_l /= samples; 
      ave_r /= samples; 
 
      //fprintf(stderr,"*%f %f\n",ave_l, ave_r); 
 
      /* Eliminamos la componente continua en cada canal. */ 
      for(i=0; i<samples; i+=2) { 
        buffer[i] -= ave_l; 
        buffer[i+1] -= ave_r; 
      } 
 
      ave_l = 0; ave_r = 0; 
      for(i=0; i<samples; i+=2) { 
        ave_l += buffer[i]; 
        ave_r += buffer[i+1]; 
      } 
      ave_l /= samples; 
      ave_r /= samples; 
 
      //fprintf(stderr,"%f %f\n",ave_l, ave_r); 
 
      /* Modulamos. */ 
      for(i=0; i<samples; i+=2) { 
 
        /* Calculamos la portadora. */ 
        double portadora = sin(angle_in_radians); 
 
        /* Multiplicamos el valor de cada canal por el valor de la 
           portadora. */ 
        buffer[i] *= portadora; 
        buffer[i+1] *= portadora; 
 
        /* Angulo de la siguiente muestra de la portadora. */ 
        angle_in_radians += 2*M_PI*freq/SAMPLES_PER_SECOND; 
      } 
 
      /* Escribimos buffer en la salida estndar. */ 
      write(1/* stdout */, (void *)buffer, bytes_read); 
    } 
 
    /* Finalizamos el thread. */ 
    pthread_exit(0); 
 
  } 
 
  /* Lanzamos el hilo anterior. */ 
  pthread_t modu_tid; 
  if(pthread_create(&modu_tid, NULL, modu, NULL) == -1) { 
    perror("pthread_create"); 
    exit(EXIT_FAILURE); 
  } 
 
  /* Creamos el socket TCP de escucha. */ 
  int listen_sd; { 
 
    /* Obtenemos el nmero del protocolo. */ 
    struct protoent *ptrp; /* Pointer to a protocol table entry. */ 
    ptrp = getprotobyname("tcp"); 
    if((long)(ptrp) == 0) { 
      perror("getprotobyname"); 
      exit(EXIT_FAILURE); 
    } 
 
    /* Creamos el socket. */ 
    listen_sd = socket (PF_INET, SOCK_STREAM, ptrp->p_proto); 
    if(listen_sd < 0) { 
      perror("socket"); 
      exit(EXIT_FAILURE); 
    } 
 
    /* Usaremos el puerto de servicio sin esperar a que ste est 
       libre. */ 
    int yes = 1; 
    if(setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) { 
      perror("setsockopt"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  /* Asignamos una direccin (dir IP, puerto) al socket de escucha. */ { 
    struct sockaddr_in sad;              /* Direccin del servidor. */ 
    memset((char  *)&sad,0,sizeof(sad)); /* Borramos la estructura. */ 
    sad.sin_family = AF_INET;            /* Usaremos Internet. */ 
    sad.sin_addr.s_addr = INADDR_ANY;    /* Cualquiera de las IP 
                                            asignadas al host vale. */ 
    sad.sin_port = 
      htons((u_short)LISTENING_PORT);    /* Asignamos el puerto de escucha. */ 
    if (bind(listen_sd, (struct sockaddr *)&sad, sizeof (sad)) < 0) { 
      perror("bind"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  /* Comenzamos a escuchar. */ 
  if (listen(listen_sd, QLEN) < 0) { 
    perror("listen"); 
    exit(EXIT_FAILURE); 
  } 
 
  /* Creamos el socket UDP. */ 
  int info_sd; 
  if((info_sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 
    perror("socket"); 
    exit(EXIT_FAILURE); 
  } 
 
  /* Dir IP y puerto del socket. */ 
  struct sockaddr_in info_addr; { 
    /* Usaremos Internet. */ 
    info_addr.sin_family = AF_INET; 
    /* IP multicast destino de los datagramas. */ 
    info_addr.sin_addr.s_addr = inet_addr(multicast_addr); 
    /* Puerto destino de los datagramas. */ 
    info_addr.sin_port = htons(multicast_port); 
    memset(&(info_addr.sin_zero), \0, 8); 
  } 
 
  //fprintf(stderr,"%s: Frequency value = \"%d\"\n",  argv[0], freq); 
 
  /* Lazo del servidor. */ 
  while(1) { 
 
    fprintf(stderr,"%s:waitingforaconnection...\n", argv[0]); 
 
    /* Socket para "servir" a los clientes. */ 
    long serve_sd; 
    /* Esperamos a que un cliente se conecte. */ { 
      struct sockaddr_in cad;        /* Clients address. */ 
      socklen_t alen = sizeof(cad);  /* Tamao de la direccin. */ 
      serve_sd = accept(listen_sd, (struct sockaddr *)&cad, &alen); 
      if(serve_sd<0) { 
        perror("accept"); 
        exit (EXIT_FAILURE); 
      } 
    } 
 
    /* Hilo que controla a un cliente. */ 
    void *service(void *arg) { 
 
      /* El socket de conmunicacin con el cliente. Ntese que cada 
         cliente posee una variable "sd" diferente. */ 
      long sd = (long)arg; 
 
      /* Mientras la frecuencia recibida sea >= 0. */ 
      for(;;) { 
        int new_freq; 
        receive_from_client(sd, &new_freq); 
        if(new_freq<0) break; 
 
        /* Seccin crtica. */ 
        pthread_mutex_lock(&mut); 
        freq = new_freq; 
        fprintf(stderr,"%s:Frequencyvalue=\"%d\"\n",  argv[0], freq); 
        pthread_mutex_unlock(&mut); 
        /* Fin de la seccin crtica. */ 
 
        inform_to_client(info_sd, info_addr, freq); 
      } 
 
      /* Cerramos la conexin con el cliente. */ 
      close(sd); 
 
      /* Finalizamos el thread. */ 
      pthread_exit(0); 
    } 
 
    /* Lanzamos el hilo. */ 
    pthread_t service_tid; /* Thread identificator. */ 
    if(pthread_create(&service_tid, NULL, service, (void *)serve_sd) == -1) { 
      perror("pthread_create"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  /* Cerramos el socket de escucha (cuando pulsamos CTRL+C). */ 
  close(listen_sd); 
} 
 
/* Enva un paquete UDP con la frecuencia. */ 
inform_to_client(int sd, struct sockaddr_in addr, int freq) { 
  unsigned char message[2]; 
  //fprintf(stderr,"--> %d\n",freq); 
  message[0] = freq/256;//((freq & 0x000F) >>  0); 
  message[1] = freq%256;//((freq & 0x00F0) >>  8); 
  //message[2] = 3;//((freq & 0x0F00) >> 16); 
  //message[3] = 4;//((freq & 0xF000) >> 24); 
  sendto(sd, message, 2, 0,(struct sockaddr *) &addr, sizeof(addr)); 
} 
 
/* Recibe del cliente la nueva frecuencia. */ 
receive_from_client(int sd, int *freq) { 
  char message[256]; 
  read_text_line(sd, message); 
  sscanf(message,"%d",freq); 
} 
 
/* Lee una lnea de texto ASCII. */ 
read_text_line(int sd, char *str) { 
  int n; 
  do { 
    n = recv(sd, str, 1, 0); 
  } while (n>0 && *str++ != \n); 
}

Como se puede apreciar en el código, modulator utiliza por defecto una portadora de 440 Hz. Para cambiar dicha frecuencia en tiempo de ejecución vamos a utilizar el programa ModulatorControl.java cuyo código fuente es:

 
/* 
 * Crea una ventana redimensionable en la que aparece un JSlider y un 
 * JFormatterdTextField. El slider se utiliza para indicar la 
 * frecuencia usando el raton. El textField para indicarla 
 * numericamente. Ambos objetos estan sincronizados, es decir, si 
 * modificamos el estado de uno automaticamente se modifica el estado 
 * del otro. 
 * 
 * Esta clase contiene ademas un thread que escucha un canal multicast 
 * para conocer la frecuencia establecida en el servidor por otro 
 * objeto FreqControl. Cuando se recibe una frecuencia distinta de la 
 * actual, el slider y el textField son actualizados. 
 * 
 * La comunicacion con el servidor se realiza mediante las clases 
 * externas OutToServer y InFromServer. 
 * 
 * gse. 2006. 
 */ 
 
/* Evento de manipulacion de excepciones de entrada/salida. */ 
import java.io.IOException; 
 
/* En Java, un JFrame es una ventana independiente. */ 
import javax.swing.JFrame; 
 
/* Organiza objetos graficos en una ventana. */ 
import javax.swing.JPanel; 
 
/* Un campo con texto que cuando se modifica genera un evento. */ 
import javax.swing.JFormattedTextField; 
 
/* Permite construir "potenciometros" lineales. */ 
import javax.swing.JSlider; 
 
/* Etiquetas de texto */ 
import javax.swing.JLabel; 
 
/* Permite organizar unos objetos graficos con respecto a otros. */ 
import javax.swing.BoxLayout; 
 
/* Genera un evento cuando pulsamos una tecla. */ 
import javax.swing.KeyStroke; 
 
/* Una clase abstracta para los objetos que tratan con acciones. Las 
 * clases abstractas no pueden ser instanciadas, solo pueden ser 
 * heredadas. */ 
import javax.swing.AbstractAction; 
 
/* Da un formato concreto a un numero. Por ejemplo, esta clase se 
 * puede utilizar para hacer que aparezca el punto del millar ... */ 
import javax.swing.text.NumberFormatter; 
 
/* Establece un borde alrededor de un objeto grafico. */ 
import javax.swing.BorderFactory; 
 
/* Define un objeto que escucha los eventos de cambio. */ 
import javax.swing.event.ChangeListener; 
 
/* Se utiliza para avisar a otros objetos interesados en que ha 
 * cambiado el estado en una fuente de eventos. */ 
import javax.swing.event.ChangeEvent; 
 
/* Espeficica que se ha ocurrido un evento en un componente. */ 
import java.awt.event.ActionEvent; 
 
/* Escucha a los eventos de ventana. */ 
import java.awt.event.WindowListener; 
 
/* Es una clase que contiene una serie de metodos independientes de la 
 * plataforma (por ejemplo, el que genera un "beep"). */ 
import java.awt.Toolkit; 
 
/* Interface (una clase abstracta pura, sin metodos definidos) usado 
 * para escribir manejadores de escucha de eventos. */ 
import java.beans.PropertyChangeListener; 
 
/* Objeto lanzado cuando se produce un cambio en las propiedades de un 
 * objeto. */ 
import java.beans.PropertyChangeEvent; 
 
/* Un componente en Java es un objeto grafico que puede interactuar 
 * con el usuario. */ 
import java.awt.Component; 
 
/* El evento de ventana. */ 
import java.awt.event.WindowEvent; 
 
/* El evento de teclado. */ 
import java.awt.event.KeyEvent; 
 
/* Interface usado cuando deseamos escuchar los cambios de estado que 
 * se producen en los componentes. */ 
import java.awt.event.ComponentListener; 
 
/* El evento de cambio del estado de una componente. */ 
import java.awt.event.ComponentEvent; 
 
public class ModulatorControl 
    extends JPanel 
    implements Runnable, 
               WindowListener, 
               ChangeListener, 
               PropertyChangeListener, 
               ComponentListener { 
 
    /* Rango de frecuencias posible. */ 
    static final int MIN_FREQ = 0; 
    static final int MAX_FREQ = 22050; 
 
    /* Dimensiones iniciales de la ventana. */ 
    static final int WINDOW_WIDTH = 512; 
    static final int WINDOW_HEIGHT = 128; 
 
    JFormattedTextField textField; 
    JSlider slider; 
 
    /* La frecuencia leida del servidor. */ 
    int freq; 
 
    /* El canal TCP hacia el servidor. */ 
    OutToServer outToServer; 
 
    /* El canal multicast UDP desde el servidor. */ 
    InFromServer inFromServer; 
 
    /** 
     * Constructor. Crea la ventana con todos los componentes y 
     * establece los cockes de entrada y salida. 
     */ 
    public ModulatorControl(String serverName, 
                            int serverPort, 
                            String multicastAddr, 
                            int multicastPort) 
        throws IOException { 
 
        /* Colocaremos el textField y el slider uno encima del otro. */ 
        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); 
 
        /* Creamos el label del textField. */ 
        JLabel textFieldLabel = new JLabel("Frecuencia(Hz):", JLabel.CENTER); 
        textFieldLabel.setAlignmentX(Component.CENTER_ALIGNMENT); 
 
        /* Creamos el textField con su formato. */ 
        java.text.NumberFormat numberFormat 
            = java.text.NumberFormat.getIntegerInstance(); 
        NumberFormatter formatter = new NumberFormatter(numberFormat); 
        formatter.setMinimum(new Double((double)MIN_FREQ)); 
        formatter.setMaximum(new Double((double)MAX_FREQ)); 
        textField = new JFormattedTextField(formatter); 
        textField.setValue(new Integer(MIN_FREQ)); 
        textField.setColumns(4); 
        textField.addPropertyChangeListener(this); 
 
        /* El textField comprobara el valor introducido por teclado 
         * una vez que hallamos pulsado la techa <Enter>. */ 
        textField.getInputMap(). 
            put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),"check"); 
        textField.getActionMap().put("check", new AbstractAction() { 
            public void actionPerformed(ActionEvent e) { 
                if (!textField.isEditValid()) { // Texto invalido. 
                    Toolkit.getDefaultToolkit().beep(); 
                    textField.selectAll(); 
                } else try {                    // Texto valido, 
                    textField.commitEdit();     // usalo. 
                } catch (java.text.ParseException exc) { } 
            } 
        }); 
 
        /* Creamos el slider y definimos su apariencia. */ 
        slider = new JSlider(JSlider.HORIZONTAL, MIN_FREQ, MAX_FREQ, MIN_FREQ); 
        slider.addChangeListener(this); 
        slider.setMajorTickSpacing(1000); 
        slider.setMinorTickSpacing(1000); 
        slider.setPaintTicks(true); 
        slider.setPaintLabels(true); 
        //slider.setSnapToTicks(true); 
        slider.setBorder(BorderFactory.createEmptyBorder(0,0,10,0)); 
 
        /* Redefinimos las etiquetas del slider. */ 
        java.util.Dictionary labelTable = slider.getLabelTable(); 
        JLabel[] l = new JLabel[(MAX_FREQ-MIN_FREQ)/1000+1]; { 
            int i; 
            for(i=MIN_FREQ; i<MAX_FREQ; i+= 1000) { 
                l[i/1000] = new JLabel(i/1000 + ""); 
                labelTable.put(new Integer(i), l[i/1000]); 
            } 
        } 
        slider.setLabelTable(labelTable); 
        /*java.util.Hashtable labelTable = new java.util.Hashtable(); 
        JLabel[] l = new JLabel[(MAX_FREQ-MIN_FREQ)/1000+1]; { 
            int i; 
            for(i=MIN_FREQ; i<MAX_FREQ; i+= 1000) { 
                l[i/1000] = new JLabel(i/1000 + ""); 
                labelTable.put(new Integer(i), l[i/1000]); 
            } 
        } 
        slider.setLabelTable(labelTable);*/ 
        //slider.setPaintLabels(true); 
 
        /* Creamos una estructura subpanel para el textField y su 
         * etiqueta. */ 
        JPanel labelAndTextField = new JPanel(); 
        labelAndTextField.add(textFieldLabel); 
        labelAndTextField.add(textField); 
 
        /* Insertamos en la ventana "ModulatorControl" el anterior JPanel y 
         * el slider. */ 
        add(labelAndTextField); 
        add(slider); 
 
        /* Distancia del contenido de la ventana a sus bordes. */ 
        setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); 
 
        /* Queremos una ventana "bonita" con la decoracion y 
         * comportamiento por defecto en Java. */ 
        JFrame.setDefaultLookAndFeelDecorated(true); 
 
        /* Creamos la ventana y establecemos su tama~no. */ 
        JFrame frame = new JFrame("ModulatorControl"); 
 
        /* Le indicamos el tama~no. */ 
        frame.setPreferredSize(new java.awt.Dimension(WINDOW_WIDTH, WINDOW_HEIGHT)); 
        /* Espeficicamos lo que hace cuando lo cerramos: finalizar. */ 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
 
        /* Insertamos el "pane" en la ventana actual. Un pane es la 
         * estructura de objetos creada cuando hemos ido a~nadiendo los 
         * diferentes objetos usando el metodo "add()". */ 
        //this.setOpaque(true); //content panes must be opaque 
        frame.setContentPane(this); 
 
        /* Hace que la ventana tenga el tama~no especificado por el 
         * metodo "setPreferredSize()". */ 
        frame.pack(); 
 
        /* Mostramos la ventana. */ 
        frame.setVisible(true); 
 
        /* Nos conectamos al servidor */ 
        outToServer = new OutToServer(serverName, serverPort); 
        inFromServer = new InFromServer(multicastAddr, multicastPort); 
 
        /* Hacemos que la ventana escuche a los eventos de ventana. */ 
        frame.addWindowListener(this); 
 
        /* Hacemos que la ventana escuche a los eventos provocamos por 
         * sus componentes. */ 
        frame.addComponentListener(this); 
 
        /* Creamos y lanzamos el hilo que esta pendiente de las 
         * frecuencias especificadas por los demas clientes. */ 
        new Thread(this).start(); 
   } 
 
    /** 
     * Este metodo es el que ejecuta el hilo que escucha las 
     * frecuencias de los demas clientes. Basicamente es un lazo sin 
     * fin que espera a recibir un paquete del canal multicast. Cuando 
     * lo recibe extrae de el la frecuencia y actualiza la variable 
     * global del metodo "freq". 
     */ 
    public void run() { 
        for(;;) { 
            try { 
                //freq = inFromServer.receive(); 
                byte[] data = inFromServer.receive(); 
                freq = ((int)data[0] & 0xFF) * 256 + ((int)data[1] & 0xFF); 
            } catch (IOException e) { 
                System.out.println("ModulatorControl:run:unabletoreceivedata"); 
            } 
            //System.out.println("freq = " + freq); 
 
            /* Cuando recibimos una frecuencia actualizamos el campo 
             * textField. */ 
            textField.setValue(new Integer(freq)); 
        } 
    } 
 
    /** 
     * Metodo que se invoca cuando la ventana se abre. 
     */ 
    public void windowOpened(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando la ventana se iconiza. 
     */ 
    public void windowIconified(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando la ventana se des-iconiza. 
     */ 
    public void windowDeiconified(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando la ventana se activa (cuando se 
     * selecciona). 
     */ 
    public void windowActivated(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando la ventana se des-activa. 
     */ 
    public void windowDeactivated(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando la ventana se va cerrar. 
     */ 
    public void windowClosing(WindowEvent e) { 
        try { 
            /* Enviamos una frecuencia negativa indicando al servidor 
             * que desemoa cerrar la conexion TCP. */ 
            outToServer.sendString("-1\n"); 
        } catch (IOException ioe) { 
            System.out.println("ModulatorControl::windowClosing:unabletosenddata"); 
        } 
        try { 
            /* Abandonamos el grupo multicast. */ 
            inFromServer.leaveGroup(); 
        } catch (IOException ioe) { 
            System.out.println("ModulatorControl::windowClosing:unabletoleavethemulticastgroup"); 
        } 
        /* Cerramos el socket de recepcion (canal multicast). */ 
        inFromServer.close(); 
    } 
 
    /** 
     * Metodo que se invoca cuando la ventana ya se ha cerrado. 
     */ 
    public void windowClosed(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando el slider cambia de estado. 
     */ 
    public void stateChanged(ChangeEvent e) { 
        JSlider source = (JSlider)e.getSource(); 
        int local_freq = (int)source.getValue(); 
        if (!source.getValueIsAdjusting()) { // done adjusting 
            textField.setValue(new Integer(local_freq)); 
        } else { //value is adjusting; just set the text 
            textField.setText(String.valueOf(local_freq)); 
        } 
        /* Si la frecuencia leida a traves del canal multicast es 
         * diferente de la que indica el slider, es que estamos 
         * interactuando con el slider. Por tanto enviamos la que 
         * espeficica este hacia al servidor. */ 
        if(freq!=local_freq) { 
            try { 
                //System.out.println(freq + " " + local_freq); 
                outToServer.sendString(String.valueOf(local_freq) + \n); 
            } catch (IOException ioe) { 
                System.out.println("ModulatorControl::stateChanged:unabletosenddata"); 
            } 
        } 
    } 
 
    /** 
     * Escucha al textField. Este metodo detecta cuando el valor del 
     * texField cambia (por el motivo que sea). 
     */ 
    public void propertyChange(PropertyChangeEvent e) { 
        if ("value".equals(e.getPropertyName())) { 
            Number value = (Number)e.getNewValue(); 
            if (slider != null && value != null) { 
                slider.setValue(value.intValue()); 
            } 
        } 
    } 
 
    /** 
     * Metodo invocado si los componentes de la ventana son escondidos. 
     */ 
    public void componentHidden(ComponentEvent e) {} 
 
    /** 
     * Metodo que se invoca si los componentes de la ventana son 
     * movidos. 
     */ 
    public void componentMoved(ComponentEvent e) {} 
 
    /** 
     * Metodo invocado cuando los componentes cambian de tama~no. 
     */ 
    public void componentResized(ComponentEvent e) {} 
 
    /** 
     * Metodo invocado cuando los componentes son mostrados. 
     */ 
    public void componentShown(ComponentEvent e) {} 
 
    /** 
     * El metodo main() es el primero que se ejecuta cuando se lanza 
     * un objeto de la clase ModulatorControl. Su funcion principal es la 
     * de tratar los argumentos proporcionados desde el shell a traves 
     * de la linea de comandos y de crear el objeto en cuestion. 
     */ 
    public static void main(String args[]) throws Exception { 
        if(args.length <1) { 
            System.out.print("Uso:javaModulatorControlserverserverPort"); 
            System.out.println("multicastAddrmulticastPort"); 
        } else { 
            String server = args[0]; 
            int serverPort = Integer.parseInt(args[1]); 
            String multicastAddr = args[2]; 
            int multicastPort = Integer.parseInt(args[3]); 
            System.out.println("ModulatorControl.class:server=" + server); 
            System.out.println("ModulatorControl.class:serverPort=" + 
                               serverPort); 
            System.out.println("ModulatorControl.class:multicastaddress=" + 
                               multicastAddr); 
            System.out.println("ModulatorControl.class:multicastport=" + 
                               multicastPort); 
            new ModulatorControl(server, serverPort, multicastAddr, 
                                 multicastPort); 
        } 
    } 
}

ModulatorControl utiliza dos clases externas llamadas InFromServer y OutToServer cuyos códigos fuente son:

 
/* Manipula los paquetes UDP. */ 
import java.net.DatagramPacket; 
 
/* Socket Multicast. */ 
import java.net.MulticastSocket; 
 
/* Evento de manipulacion de excepciones de entrada/salida. */ 
import java.io.IOException; 
 
/* Manipula las direcciones IP. */ 
import java.net.InetAddress; 
 
public class InFromServer { 
 
    /* Puerto de entrada de los paquetes. */ 
    //static final int PORT = 6789; 
 
    /* Socket Multicast para leer del servidor. */ 
    MulticastSocket inFromServer; 
 
    /* Paquete UDP leido del servidor. */ 
    DatagramPacket receivedPacket; 
 
    /* Dir IP del grupo multicast. */ 
    InetAddress mCastIA; 
 
    /* Constructor. */ 
    public InFromServer(String multicastAddr, int port) throws IOException { 
 
        /* Crea un socket para recibir paquetes UDP. */ 
        inFromServer = new MulticastSocket(/*PORT*/port); 
 
        /* Obtenemos la dir IP del grupo multicast. */ 
        mCastIA = InetAddress.getByName(multicastAddr); 
 
        /* Nos unimos al grupo multicast. */ 
        inFromServer.joinGroup(mCastIA); 
 
        /* Creamos el paquete UDP que recibiremos del servidor. */ 
        receivedPacket = new DatagramPacket(new byte[2], 2); 
    } 
 
    /* Leemos el canal multicast. */ 
    public byte[] receive() throws IOException { 
 
        try { 
            inFromServer.receive(receivedPacket); 
        } catch (IOException e) { 
            System.out.println("InFromServer:receive:unabletoreceivedata"); 
            throw new IOException(); 
        } 
 
        /*int freq = ((int)receivedPacket.getData()[0] & 0xFF) * 256 + 
            ((int)receivedPacket.getData()[1] & 0xFF); 
 
            return freq;*/ 
        return receivedPacket.getData(); 
    } 
 
    /* Dejamos el grupo multicast. */ 
    public void leaveGroup() throws IOException { 
        try { 
            inFromServer.leaveGroup(mCastIA); 
        } catch (IOException ioe) { 
            System.out.println("InFromServer:leaveGroup:unabletoleavethemulticastgroup"); 
            throw new IOException(); 
        } 
    } 
 
    /* Cerramos el socket. */ 
    public void close() { 
        inFromServer.close(); 
    } 
 
 }

y

 
/* 
 * Envia la frecuencia al servidor a traves de un socket TCP. 
 */ 
 
/* Permite escribir tipos de datos primitivos (byte, int, float, 
 * ...). */ 
import java.io.DataOutputStream; 
 
/* Puerto de comunicacion entre procesos. */ 
import java.net.Socket; 
 
/* Evento de manipulacion de excepciones de entrada/salida. */ 
import java.io.IOException; 
 
/* Evento lanzado cuando nos conectamos a un host inexistente. */ 
import java.net.UnknownHostException; 
 
public class OutToServer { 
 
    /* Flujo hacia el servidor. */ 
    DataOutputStream outToServer; 
 
    /* Puerto de escucha del servidor. */ 
    //static final int PORT = 5678; 
 
    /* Constructor. */ 
    public OutToServer(String serverName, int port) 
        throws 
            UnknownHostException, 
    IOException { 
 
        Socket clientSocket = null; 
 
        /* Consulta al DNS y establece una conexion TCP. */ 
        try { 
            clientSocket = new Socket(serverName, port); 
        } catch (UnknownHostException e) { 
            System.out.println("OutToServer:unknownhost" + 
                               "\"" + serverName + "\""); 
            throw new UnknownHostException(); 
        } 
 
        /* Conectamos clientSocket con outToServer. */ 
        try { 
            outToServer = new DataOutputStream(clientSocket.getOutputStream()); 
        } catch (IOException e) { 
            System.out.println("OutToServer:unabletoconnectto" + 
                               "\"" + serverName + "\""); 
            throw new IOException(); 
        } 
    } 
 
    /* Envia una cadena al servidor. */ 
    public void sendString(String s) throws IOException { 
        try { 
            outToServer.writeBytes(s); 
        } catch (IOException ioe) { 
            System.out.println("OutToServer:unabletosendString" + 
                               "\"" +  s + "\""); 
            throw new IOException(); 
        } 
    } 
}

Para manegar con mayor comodidad los parámetros de ModulatorControl.java se adjunta un script escrito en Python:

 
#!/usr/bin/python 
# -*- coding: iso-8859-15 -*- 
 
import sys 
import getopt 
import os 
 
server = "localhost" 
server_port = 6789 
multicast_addr = "224.0.0.1" 
multicast_port = 6789 
 
def usage(): 
    sys.stderr.write 
    ("ModulatorControl[-sserver][-pport][-cmulticast_channel]\n") 
    sys.stderr.write("server=servername(\"%s\")\n" % server) 
    sys.stderr.write("port=serverport(%d)\n" % port) 
    sys.stderr.write("multicastchannel=multicastaddress(\"%s\")\n" 
                     % multicast_addr) 
    sys.stderr.write("multicastport=multicastport(\"%d\")\n" 
                     % multicast_port) 
 
opts = "" 
 
try: 
    opts, extraparams = getopt.getopt(sys.argv[1:],"s:p:c:m:h", 
                                      ["server=", 
                                       "server_port=", 
                                       "multicast_addr=", 
                                       "multicast_port=", 
                                       "help" 
                                       ]) 
except getopt.GetoptError, exc: 
    sys.stderr.write(sys.argv[0] + ":" + exc.msg + "\n") 
    sys.exit(2) 
 
for o, a in opts: 
    if o in ("-s", "--server"): 
        server = a 
    if o in ("-p", "--server_port"): 
        server_port = int(a) 
    if o in ("-c", "--multicast_addr"): 
        multicast_addr = a 
    if o in ("-m", "--multicast_port"): 
        multicast_port = int(a) 
    if o in ("-h", "--help"): 
        usage() 
        sys.exit() 
 
sys.stderr.write(sys.argv[0] + ":server=" + server + \n) 
sys.stderr.write(sys.argv[0] + ":server_port=" + str(server_port) + \n) 
sys.stderr.write(sys.argv[0] + ":multicast_addr=" + multicast_addr + \n) 
sys.stderr.write(sys.argv[0] + ":multicast_port=" + str(multicast_port) + \n) 
 
os.system("javaModulatorControl" + 
          "" + server + 
          "" + str(server_port) + 
          "" + multicast_addr + 
          "" + str(multicast_port))

Finalmente, para facilitar la tarea de la compilación de todos estos programas vamos a utilizar el programa make. Este programa utiliza como configuración un fichero Makefile, que no es otra cosa que una colección de reglas con la estructura:

objetivo: dependencias del objetivo  
          comandos necesarios para constuir el objetivo

donde un “objetivo” puede ser tanto un fichero a generar como un objetivo de objetivos.

El fichero Makefile para el proyecto modulator es:

 
%.class: %.java 
        javac -classpath ../Server $*.java 
 
%: %.c 
        gcc $< -o $@ -lm -lpthread 
 
%: %.py 
        cp $*.py $@ 
 
EXE =  modulator \ 
        ModulatorControl.class \ 
        ModulatorControl \ 
        ../Server/InFromServer.class \ 
        ../Server/OutToServer.class 
 
all:  $(EXE) 
 
clean: 
        rm -f $(EXE) 
 
install: 
        cp $(EXE) *.class ~/bin 
 
publish: 
        rm -rf /tmp/modulator 
        svn export . /tmp/modulator 
        tar --create --file=/tmp/modulator.tar -C /tmp modulator 
        gzip -9 /tmp/modulator.tar 
        scp /tmp/modulator.tar.gz www.ace.ual.es:~/public_html/imyso 
        rm /tmp/modulator.tar.gz

Finalmente, en el directorio modulator ejectutar:

make

Si todo ha ido bien, debería poder listar los ficheros modulator, ModulatorControl.class, InFromServer.class y OutToServer.class.

Como para realizar esta práctica vamos a invocar a estos programas (y otros más) desde un directorio diferente, nos va a ser útil especificar el camino a cada uno de ellos en la variable de entorno que contiene todos los directorios donde el shell busca un comando que se escribe sin especificar su camino completo. Para ello sitúese en el directorio modulator y ejecute:

export PATH=$PATH:‘pwd‘  
export CLASSPATH=$CLASSPATH:‘pwd‘

6 dc

En esta práctica vamos a modular a diferentes frecuencias una señal constante.2 Dicha señal puede generarse con el programa dc.c (http://www.ace.ual.es/\~vruiz/imyso/dc.tar.gz):

 
/* 
 * dc.c -- Un generador de seal constante (Direct Current). 
 * 
 * gse. 2005. 
 * 
 */ 
 
/****************************************************************************** 
 * 
 * Ficheros cabecera. 
 * 
 *****************************************************************************/ 
 
/* Entrada y salida de streams buffereados. */ 
#include <stdio.h> 
 
/* Biblioteca de funciones estndar (exit(), EXIT_SUCCESS, 
   EXIT_FAILURE, ...) */ 
#include <stdlib.h> 
 
/* Manipulacin de cadenas y movimiento de memoria (memset(), ...). */ 
#include <string.h> 
 
/* Biblioteca estndar de funciones relacionadas con el sistema 
   operativo Unix (read(), write(), getopt(), ...). */ 
#include <unistd.h> 
 
/* Signals (interrupciones) (signal(), SIGINT). */ 
#include <signal.h> 
 
/* POSIX Threads (pthread_t, pthread_create(), ...). */ 
#include <pthread.h> 
 
/* Sockets. */ 
/* Ms info en: 
 * 
 * http://www.csce.uark.edu/~aapon/courses/os/examples/concurrentserver.c 
 * http://beej.us/guide/bgnet/ 
 */ 
 
/* Tipos de datos primitivos del sistema. */ 
#include <sys/types.h> 
 
/* Sockets (socket(), listen(), ...). */ 
#include <sys/socket.h> 
 
/* Direcciones IP, opciones y definiciones. */ 
#include <netinet/in.h> 
 
/* Servicio de resolucin de nombres. */ 
#include <netdb.h> 
 
/* Signals (interrupciones) (signal(), SIGINT). */ 
#include <signal.h>  /* signal(), SIGINT */ 
 
#include <getopt.h> 
 
/****************************************************************************** 
 * 
 * Definiciones. 
 * 
 *****************************************************************************/ 
 
/* Tamao del buffer de datos. */ 
#define BUFFER_SIZE 512 
 
/* Valor por defecto de la seal. */ 
#define DEFAULT_DC 1000 
 
/* Puerto de comunicacin con los controladores de parmetros. */ 
#define MULTICAST_PORT 5678 
 
/* Puerto usado para atender a los clientes. */ 
#define LISTENING_PORT 5678 
 
/* Direccin multicast por defecto. Todos los clientes escuchan dicha 
   direccin multicast. */ 
#define MULTICAST_ADDR (char *)"224.0.0.1" 
 
/* Nmero mximo de clientes que esperan a que la conexin sea 
   establecida. */ 
#define QLEN 1 
 
/****************************************************************************** 
 * 
 * Variables Globales. 
 * 
 *****************************************************************************/ 
 
/****************************************************************************** 
 * 
 * Funciones. 
 * 
 *****************************************************************************/ 
 
/* Cuerpo principal del programa. */ 
int main(int argc, char *argv[]) { 
  work(argc, argv); 
  return EXIT_SUCCESS; 
} 
 
work(int argc, char *argv[]) { 
 
  /* Si pulsamos CRTL+C, el programa acaba ejecutando la funcin 
     end(). */ 
  void end() { 
    fprintf(stderr,"%s:CTRL+Cdetected.Exiting...",argv[0]); 
    fprintf(stderr,"done\n"); 
    exit(EXIT_SUCCESS); 
  } 
  signal(SIGINT, end);  //activando la senal SIGINT 
 
  /* Valor de la seal DC. */ 
  int dc_val = DEFAULT_DC; 
 
  /* Puerto de escucha de los clientes. */ 
  short listening_port = LISTENING_PORT; 
 
  /* Puerto destino de los datagramas multicast. */ 
  short multicast_port = MULTICAST_PORT; 
 
  /* Canal multicast destino. */ 
  char *multicast_addr = MULTICAST_ADDR; 
 
  /* Manipulacin de los parmetros de entrada. */ { 
    int c; 
    while(1) { 
 
      static struct option long_options[] = { 
        {"dc_val", required_argument, 0, d}, 
        {"listening_port", required_argument, 0, l}, 
        {"multicast_addr", required_argument, 0, a}, 
        {"multicast_port", required_argument, 0, m}, 
        {"help", no_argument, 0, ?}, 
        {0, 0, 0, 0} 
      }; 
 
      int option_index = 0; 
 
      c = getopt_long(argc, argv, "d:l:a:m:?", long_options, &option_index); 
      if(c==-1) { 
        /* No more options. */ 
        break; 
      } 
 
      switch (c) { 
      case 0: 
        /* If this option set a flag, do nothing else now. */ 
        if (long_options[option_index].flag != 0) 
          break; 
        fprintf(stderr, "option%s", long_options[option_index].name); 
        if (optarg) 
          fprintf(stderr, "witharg%s", optarg); 
        fprintf(stderr, "\n"); 
        break; 
 
      case d: 
        dc_val= atoi(optarg); 
        break; 
 
      case l: 
        listening_port = atoi(optarg); 
        break; 
 
      case a: 
        multicast_addr = optarg; 
        break; 
 
      case m: 
        multicast_port = atoi(optarg); 
        break; 
 
      case ?: 
        fprintf(stdout, 
                "-[-a]mplitude=frequency(inHz)ofthemodulatingsignal(%d)\n", 
                dc_val); 
        fprintf(stdout, 
                "-[-J]PG=porttoservetheModulatorControlclients(%d)\n", 
                listening_port); 
        fprintf(stdout, 
                "-[-]multicast_[a]ddress=multicastaddresstocomunicate"); 
        fprintf(stdout, 
                "totheclientsachangeinthevalueofthefrequency(%s)\n", 
                multicast_addr); 
        fprintf(stdout, 
                "-[-]multicast_[p]ort=porttowhichthemulticast"); 
        fprintf(stdout, 
                "packetsaresent(%d)\n", 
                multicast_port); 
        exit(1); 
 
      default: 
        fprintf(stderr, "%s:Unrecognizedargument.Aborting...\n", argv[0]); 
        abort(); 
      } 
    } 
  } 
 
  fprintf(stderr, "%s:DCvalue=%dHz\n" ,argv[0], dc_val); 
  fprintf(stderr, "%s:listeningport=%d\n", argv[0], listening_port); 
  fprintf(stderr, "%s:multicastaddress=%s\n", argv[0], multicast_addr); 
  fprintf(stderr, "%s:multicastport=%d\n", argv[0], multicast_port); 
 
 
  /* Un semforo para acceder "en exclusin mutua" a una zona de 
     cdigo. */ 
  pthread_mutex_t mut; 
 
  /* Creamos el semforo. */ 
  pthread_mutex_init(&mut, NULL); 
 
  /* Hilo que genera la seal constante. */ 
  void *generator(void *arg) { 
 
    for(;;) { 
      short buffer[BUFFER_SIZE/sizeof(short)]; 
      int i; 
      for(i=0; i<BUFFER_SIZE/sizeof(short); i++) { 
        buffer[i] = dc_val; 
      } 
      write(1, (void *)buffer, BUFFER_SIZE); 
    } 
 
    /* Finalizamos el thread. */ 
    pthread_exit(0); 
 
  } 
 
    /* Lanzamos el hilo anterior. */ 
  pthread_t generator_tid; 
  if(pthread_create(&generator_tid, NULL, generator, NULL) == -1) { 
    perror("pthread_create"); 
    exit(EXIT_FAILURE); 
  } 
 
  /* Creamos el socket TCP de escucha. */ 
  int listen_sd; { 
 
    /* Obtenemos el nmero del protocolo. */ 
    struct protoent *ptrp; /* Pointer to a protocol table entry. */ 
    ptrp = getprotobyname("tcp"); 
    if((long)(ptrp) == 0) { 
      perror("getprotobyname"); 
      exit(EXIT_FAILURE); 
    } 
 
    /* Creamos el socket. */ 
    listen_sd = socket (PF_INET, SOCK_STREAM, ptrp->p_proto); 
    if(listen_sd < 0) { 
      perror("socket"); 
      exit(EXIT_FAILURE); 
    } 
 
    /* Usaremos el puerto de servicio sin esperar a que ste est 
       libre. */ 
    int yes = 1; 
    if(setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) { 
      perror("setsockopt"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  /* Asignamos una direccin (dir IP, puerto) al socket de escucha. */ { 
    struct sockaddr_in sad;              /* Direccin del servidor. */ 
    memset((char  *)&sad,0,sizeof(sad)); /* Borramos la estructura. */ 
    sad.sin_family = AF_INET;            /* Usaremos Internet. */ 
    sad.sin_addr.s_addr = INADDR_ANY;    /* Cualquiera de las IP 
                                            asignadas al host vale. */ 
    sad.sin_port = 
      htons((u_short)listening_port);    /* Asignamos el puerto de escucha. */ 
    if (bind(listen_sd, (struct sockaddr *)&sad, sizeof (sad)) < 0) { 
      perror("bind"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  /* Comenzamos a escuchar. */ 
  if (listen(listen_sd, QLEN) < 0) { 
    perror("listen"); 
    exit(EXIT_FAILURE); 
  } 
 
  /* Creamos el socket UDP. */ 
  int info_sd; 
  if((info_sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 
    perror("socket"); 
    exit(EXIT_FAILURE); 
  } 
 
  /* Dir IP y puerto del socket. */ 
  struct sockaddr_in info_addr; { 
    /* Usaremos Internet. */ 
    info_addr.sin_family = AF_INET; 
    /* IP multicast destino de los datagramas. */ 
    info_addr.sin_addr.s_addr = inet_addr(multicast_addr); 
    /* Puerto destino de los datagramas. */ 
    info_addr.sin_port = htons(multicast_port); 
    memset(&(info_addr.sin_zero), \0, 8); 
  } 
 
  fprintf(stderr,"%s:DCvalue=\"%d\"\n",  argv[0],dc_val); 
 
  /* Lazo del servidor. */ 
  while(1) { 
 
    fprintf(stderr,"dc:waitingforconnection...\n"); 
 
    /* Socket para "servir" a los clientes. */ 
    long serve_sd; 
 
    /* Esperamos a que un cliente se conecte. */ { 
      struct sockaddr_in cad;        /* Clients address. */ 
      socklen_t alen = sizeof(cad);  /* Tamao de la direccin. */ 
      serve_sd = accept(listen_sd, (struct sockaddr *)&cad, &alen); 
      if(serve_sd<0) { 
        perror("accept"); 
        exit (EXIT_FAILURE); 
      } 
    } 
 
    /* Hilo que controla a un cliente. */ 
    void *service(void *arg) { 
 
      /* El socket de conmunicacin con el cliente. Ntese que cada 
         cliente posee una variable "sd" diferente. */ 
      long sd = (long)arg; 
 
      /* Mientras la amplitud recibida sea >= 0. OJO */ 
      for(;;) { 
        int new_dc_val; 
        receive_from_client(sd, &new_dc_val); 
        if(new_dc_val<0) break; 
 
        /* Seccin crtica. */ 
        pthread_mutex_lock(&mut); 
        dc_val = new_dc_val; 
        fprintf(stderr,"%s:DCvalue=\"%d\"\n", argv[0], dc_val); 
        pthread_mutex_unlock(&mut); 
        /* Fin de la seccin crtica. */ 
 
        inform_to_client(info_sd, info_addr, dc_val); 
      } 
 
      /* Cerramos la conexin con el cliente. */ 
      close(sd); 
 
      /* Finalizamos el thread. */ 
      pthread_exit(0); 
    } 
 
    /* Lanzamos el hilo. */ 
    pthread_t service_tid; /* Thread identificator. */ 
    if(pthread_create(&service_tid, NULL, service, (void *)serve_sd) == -1) { 
      perror("pthread_create"); 
      exit(EXIT_FAILURE); 
    } 
 
  } 
 
  /* Cerramos el socket de escucha (cuando pulsamos CTRL+C). */ 
  close(listen_sd); 
} 
 
/* Enva un paquete UDP con la amplitud de la seal. */ 
inform_to_client(int sd, struct sockaddr_in addr, int amplitude) { 
  unsigned char message[2]; 
  message[0] = amplitude/256; 
  message[1] = amplitude%256; 
  sendto(sd, message, 2, 0,(struct sockaddr *) &addr, sizeof(addr)); 
} 
 
/* Recibe del cliente la nueva frecuencia. */ 
receive_from_client(int sd, int *dc_val) { 
  char message[256]; 
  read_text_line(sd, message); 
  sscanf(message,"%d",dc_val); 
} 
 
/* Lee una lnea de texto ASCII. */ 
read_text_line(int sd, char *str) { 
  int n; 
  do { 
    n = recv(sd, str, 1, 0); 
  } while (n>0 && *str++ != \n); 
} 
 
#ifdef _1_ 
  int c; 
  while ((c = getopt (argc, argv, "v:")) != -1) { 
    switch (c) { 
    case v: 
      dc_val = atoi(optarg); 
      break; 
    case ?: 
      if (isprint (optopt)) 
        fprintf (stderr, "Unknownoption‘-%c’.\n", optopt); 
      else 
        fprintf (stderr, 
                 "Unknownoptioncharacter‘\\x%x’.\n", 
                 optopt); 
      return 1; 
    default: 
      abort (); 
    } 
  } 
 
  fprintf(stderr,"%s:DCvalue=\"%d\"\n", 
          argv[0],dc_val); 
 
 
 
} 
#endif

El programa DcControl.java sirve para controlar a dc.c. Su código fuente es:

 
/* 
 * Crea una ventana redimensionable en la que aparece un JSlider y un 
 * JFormatterdTextField. El slider se utiliza para indicar la 
 * amplitud usando el raton. El textField para indicarla 
 * numericamente. Ambos objetos estan sincronizados, es decir, si 
 * modificamos el estado de uno automaticamente se modifica el estado 
 * del otro. 
 * 
 * Esta clase contiene ademas un thread que escucha un canal multicast 
 * para conocer la amplitud establecida en el servidor por otro 
 * objeto FreqControl. Cuando se recibe una amplitud distinta de la 
 * actual, el slider y el textField son actualizados. 
 * 
 * La comunicacion con el servidor se realiza mediante las clases 
 * externas OutToServer y InFromServer. 
 * 
 * gse. 2006. 
 */ 
 
/* Evento de manipulacion de excepciones de entrada/salida. */ 
import java.io.IOException; 
 
/* En Java, un JFrame es una ventana independiente. */ 
import javax.swing.JFrame; 
 
/* Organiza objetos graficos en una ventana. */ 
import javax.swing.JPanel; 
 
/* Un campo con texto que cuando se modifica genera un evento. */ 
import javax.swing.JFormattedTextField; 
 
/* Permite construir "potenciometros" lineales. */ 
import javax.swing.JSlider; 
 
/* Etiquetas de texto */ 
import javax.swing.JLabel; 
 
/* Permite organizar unos objetos graficos con respecto a otros. */ 
import javax.swing.BoxLayout; 
 
/* Genera un evento cuando pulsamos una tecla. */ 
import javax.swing.KeyStroke; 
 
/* Una clase abstracta para los objetos que tratan con acciones. Las 
 * clases abstractas no pueden ser instanciadas, solo pueden ser 
 * heredadas. */ 
import javax.swing.AbstractAction; 
 
/* Da un formato concreto a un numero. Por ejemplo, esta clase se 
 * puede utilizar para hacer que aparezca el punto del millar ... */ 
import javax.swing.text.NumberFormatter; 
 
/* Establece un borde alrededor de un objeto grafico. */ 
import javax.swing.BorderFactory; 
 
/* Define un objeto que escucha los eventos de cambio. */ 
import javax.swing.event.ChangeListener; 
 
/* Se utiliza para avisar a otros objetos interesados en que ha 
 * cambiado el estado en una fuente de eventos. */ 
import javax.swing.event.ChangeEvent; 
 
/* Espeficica que se ha ocurrido un evento en un componente. */ 
import java.awt.event.ActionEvent; 
 
/* Escucha a los eventos de ventana. */ 
import java.awt.event.WindowListener; 
 
/* Es una clase que contiene una serie de metodos independientes de la 
 * plataforma (por ejemplo, el que genera un "beep"). */ 
import java.awt.Toolkit; 
 
/* Interface (una clase abstracta pura, sin metodos definidos) usado 
 * para escribir manejadores de escucha de eventos. */ 
import java.beans.PropertyChangeListener; 
 
/* Objeto lanzado cuando se produce un cambio en las propiedades de un 
 * objeto. */ 
import java.beans.PropertyChangeEvent; 
 
/* Un componente en Java es un objeto grafico que puede interactuar 
 * con el usuario. */ 
import java.awt.Component; 
 
/* El evento de ventana. */ 
import java.awt.event.WindowEvent; 
 
/* El evento de teclado. */ 
import java.awt.event.KeyEvent; 
 
/* Interface usado cuando deseamos escuchar los cambios de estado que 
 * se producen en los componentes. */ 
import java.awt.event.ComponentListener; 
 
/* El evento de cambio del estado de una componente. */ 
import java.awt.event.ComponentEvent; 
 
public class DcControl 
    extends JPanel 
    implements Runnable, 
               WindowListener, 
               ChangeListener, 
               PropertyChangeListener, 
               ComponentListener { 
 
    /* Rango de amplitudes posible. */ 
    static final int MIN_AMPLITUDE = 0; 
    static final int MAX_AMPLITUDE = 32767; 
 
    /* Dimensiones iniciales de la ventana. */ 
    static final int WINDOW_WIDTH = 512; 
    static final int WINDOW_HEIGHT = 128; 
 
    JFormattedTextField textField; 
    JSlider slider; 
 
    /* La amplitud leida del servidor. */ 
    int amplitude; 
 
    /* El canal TCP hacia el servidor. */ 
    OutToServer outToServer; 
 
    /* El canal multicast UDP desde el servidor. */ 
    InFromServer inFromServer; 
 
    /** 
     * Constructor. Crea la ventana con todos los componentes y 
     * establece los cockes de entrada y salida. 
     */ 
    public DcControl(String serverName, 
                     int serverPort, 
                     String multicastAddr, 
                     int multicastPort) 
        throws IOException { 
 
        /* Colocaremos el textField y el slider uno encima del otro. */ 
        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); 
 
        /* Creamos el label del textField. */ 
        JLabel textFieldLabel = new JLabel("Amplitud:", JLabel.CENTER); 
        textFieldLabel.setAlignmentX(Component.CENTER_ALIGNMENT); 
 
        /* Creamos el textField con su formato. */ 
        java.text.NumberFormat numberFormat 
            = java.text.NumberFormat.getIntegerInstance(); 
        NumberFormatter formatter = new NumberFormatter(numberFormat); 
        formatter.setMinimum(new Double((double)MIN_AMPLITUDE)); 
        formatter.setMaximum(new Double((double)MAX_AMPLITUDE)); 
        textField = new JFormattedTextField(formatter); 
        textField.setValue(new Integer(MIN_AMPLITUDE)); 
        textField.setColumns(4); 
        textField.addPropertyChangeListener(this); 
 
        /* El textField comprobara el valor introducido por teclado 
         * una vez que hallamos pulsado la techa <Enter>. */ 
        textField.getInputMap(). 
            put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),"check"); 
        textField.getActionMap().put("check", new AbstractAction() { 
            public void actionPerformed(ActionEvent e) { 
                if (!textField.isEditValid()) { // Texto invalido. 
                    Toolkit.getDefaultToolkit().beep(); 
                    textField.selectAll(); 
                } else try {                    // Texto valido, 
                    textField.commitEdit();     // usalo. 
                } catch (java.text.ParseException exc) { } 
            } 
        }); 
 
        /* Creamos el slider y definimos su apariencia. */ 
        slider = new JSlider(JSlider.HORIZONTAL, MIN_AMPLITUDE, MAX_AMPLITUDE, MIN_AMPLITUDE); 
        slider.addChangeListener(this); 
        slider.setMajorTickSpacing(10000); 
        slider.setMinorTickSpacing(10000); 
        slider.setPaintTicks(true); 
        slider.setPaintLabels(true); 
        //slider.setSnapToTicks(true); 
        slider.setBorder(BorderFactory.createEmptyBorder(0,0,10,0)); 
 
        /* Redefinimos las etiquetas del slider. */ 
        /*java.util.Dictionary labelTable = slider.getLabelTable(); 
        JLabel[] l = new JLabel[(MAX_AMPLITUDE-MIN_AMPLITUDE)/512+1]; { 
            int i; 
            for(i=MIN_AMPLITUDE; i<MAX_AMPLITUDE; i+= 512) { 
                l[i/512] = new JLabel(i/512 + ""); 
                labelTable.put(new Integer(i), l[i/512]); 
            } 
        } 
        slider.setLabelTable(labelTable);*/ 
        /*java.util.Hashtable labelTable = new java.util.Hashtable(); 
        JLabel[] l = new JLabel[(MAX_AMPLITUDE-MIN_AMPLITUDE)/1000+1]; { 
            int i; 
            for(i=MIN_AMPLITUDE; i<MAX_AMPLITUDE; i+= 1000) { 
                l[i/1000] = new JLabel(i/1000 + ""); 
                labelTable.put(new Integer(i), l[i/1000]); 
            } 
        } 
        slider.setLabelTable(labelTable);*/ 
        //slider.setPaintLabels(true); 
 
        /* Creamos una estructura subpanel para el textField y su 
         * etiqueta. */ 
        JPanel labelAndTextField = new JPanel(); 
        labelAndTextField.add(textFieldLabel); 
        labelAndTextField.add(textField); 
 
        /* Insertamos en la ventana "DcControl" el anterior JPanel y 
         * el slider. */ 
        add(labelAndTextField); 
        add(slider); 
 
        /* Distancia del contenido de la ventana a sus bordes. */ 
        setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); 
 
        /* Queremos una ventana "bonita" con la decoracion y 
         * comportamiento por defecto en Java. */ 
        JFrame.setDefaultLookAndFeelDecorated(true); 
 
        /* Creamos la ventana y establecemos su tama~no. */ 
        JFrame frame = new JFrame("DcControl"); 
 
        /* Le indicamos el tama~no. */ 
        frame.setPreferredSize(new java.awt.Dimension(WINDOW_WIDTH, WINDOW_HEIGHT)); 
        /* Espeficicamos lo que hace cuando lo cerramos: finalizar. */ 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
 
        /* Insertamos el "pane" en la ventana actual. Un pane es la 
         * estructura de objetos creada cuando hemos ido a~nadiendo los 
         * diferentes objetos usando el metodo "add()". */ 
        //this.setOpaque(true); //content panes must be opaque 
        frame.setContentPane(this); 
 
        /* Hace que la ventana tenga el tama~no especificado por el 
         * metodo "setPreferredSize()". */ 
        frame.pack(); 
 
        /* Mostramos la ventana. */ 
        frame.setVisible(true); 
 
        /* Nos conectamos al servidor */ 
        outToServer = new OutToServer(serverName, serverPort); 
        inFromServer = new InFromServer(multicastAddr, multicastPort); 
 
        /* Hacemos que la ventana escuche a los eventos de ventana. */ 
        frame.addWindowListener(this); 
 
        /* Hacemos que la ventana escuche a los eventos provocamos por 
         * sus componentes. */ 
        frame.addComponentListener(this); 
 
        /* Creamos y lanzamos el hilo que esta pendiente de las 
         * amplitudes especificadas por los demas clientes. */ 
        new Thread(this).start(); 
   } 
 
    /** 
     * Este metodo es el que ejecuta el hilo que escucha las 
     * amplitudes de los demas clientes. Basicamente es un lazo sin 
     * fin que espera a recibir un paquete del canal multicast. Cuando 
     * lo recibe extrae de el la amplitud y actualiza la variable 
     * global del metodo "amplitude". 
     */ 
    public void run() { 
        for(;;) { 
            try { 
                //amplitude = inFromServer.receive(); 
                byte[] data = inFromServer.receive(); 
                amplitude = ((int)data[0] & 0xFF) * 256 + ((int)data[1] & 0xFF); 
            } catch (IOException e) { 
                System.out.println("DcControl:run:unabletoreceivedata"); 
            } 
            //System.out.println("amplitude = " + amplitude); 
 
            /* Cuando recibimos una amplitud actualizamos el campo 
             * textField. */ 
            textField.setValue(new Integer(amplitude)); 
        } 
    } 
 
    /** 
     * Metodo que se invoca cuando la ventana se abre. 
     */ 
    public void windowOpened(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando la ventana se iconiza. 
     */ 
    public void windowIconified(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando la ventana se des-iconiza. 
     */ 
    public void windowDeiconified(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando la ventana se activa (cuando se 
     * selecciona). 
     */ 
    public void windowActivated(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando la ventana se des-activa. 
     */ 
    public void windowDeactivated(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando la ventana se va cerrar. 
     */ 
    public void windowClosing(WindowEvent e) { 
        try { 
            /* Enviamos una amplitud negativa indicando al servidor 
             * que desemoa cerrar la conexion TCP. */ 
            outToServer.sendString("-1\n"); 
        } catch (IOException ioe) { 
            System.out.println("DcControl::windowClosing:unabletosenddata"); 
        } 
        try { 
            /* Abandonamos el grupo multicast. */ 
            inFromServer.leaveGroup(); 
        } catch (IOException ioe) { 
            System.out.println("DcControl::windowClosing:unabletoleavethemulticastgroup"); 
        } 
        /* Cerramos el socket de recepcion (canal multicast). */ 
        inFromServer.close(); 
    } 
 
    /** 
     * Metodo que se invoca cuando la ventana ya se ha cerrado. 
     */ 
    public void windowClosed(WindowEvent e) {} 
 
    /** 
     * Metodo que se invoca cuando el slider cambia de estado. 
     */ 
    public void stateChanged(ChangeEvent e) { 
        JSlider source = (JSlider)e.getSource(); 
        int local_amplitude = (int)source.getValue(); 
        if (!source.getValueIsAdjusting()) { // done adjusting 
            textField.setValue(new Integer(local_amplitude)); 
        } else { //value is adjusting; just set the text 
            textField.setText(String.valueOf(local_amplitude)); 
        } 
        /* Si la amplitud leida a traves del canal multicast es 
         * diferente de la que indica el slider, es que estamos 
         * interactuando con el slider. Por tanto enviamos la que 
         * espeficica este hacia al servidor. */ 
        if(amplitude!=local_amplitude) { 
            try { 
                //System.out.println(amplitude + " " + local_amplitude); 
                outToServer.sendString(String.valueOf(local_amplitude) + \n); 
            } catch (IOException ioe) { 
                System.out.println("DcControl::stateChanged:unabletosenddata"); 
            } 
        } 
    } 
 
    /** 
     * Escucha al textField. Este metodo detecta cuando el valor del 
     * texField cambia (por el motivo que sea). 
     */ 
    public void propertyChange(PropertyChangeEvent e) { 
        if ("value".equals(e.getPropertyName())) { 
            Number value = (Number)e.getNewValue(); 
            if (slider != null && value != null) { 
                slider.setValue(value.intValue()); 
            } 
        } 
    } 
 
    /** 
     * Metodo invocado si los componentes de la ventana son escondidos. 
     */ 
    public void componentHidden(ComponentEvent e) {} 
 
    /** 
     * Metodo que se invoca si los componentes de la ventana son 
     * movidos. 
     */ 
    public void componentMoved(ComponentEvent e) {} 
 
    /** 
     * Metodo invocado cuando los componentes cambian de tama~no. 
     */ 
    public void componentResized(ComponentEvent e) {} 
 
    /** 
     * Metodo invocado cuando los componentes son mostrados. 
     */ 
    public void componentShown(ComponentEvent e) {} 
 
    /** 
     * El metodo main() es el primero que se ejecuta cuando se lanza 
     * un objeto de la clase DcControl. Su funcion principal es la 
     * de tratar los argumentos proporcionados desde el shell a traves 
     * de la linea de comandos y de crear el objeto en cuestion. 
     */ 
    public static void main(String args[]) throws Exception { 
        if(args.length <1) { 
            System.out.print("Uso:javaDcControlserverserverPort"); 
            System.out.println("multicastAddrmulticastPort"); 
        } else { 
            String server = args[0]; 
            int serverPort = Integer.parseInt(args[1]); 
            String multicastAddr = args[2]; 
            int multicastPort = Integer.parseInt(args[3]); 
            System.out.println("DcControl.class:server=" + server); 
            System.out.println("DcControl.class:serverPort=" + 
                               serverPort); 
            System.out.println("DcControl.class:multicastaddress=" + 
                               multicastAddr); 
            System.out.println("DcControl.class:multicastport=" + 
                               multicastPort); 
            new DcControl(server, serverPort, multicastAddr, multicastPort); 
        } 
    } 
}

Para manegar con mayor comodidad los parámetros de DcControl.java se adjunta un script escrito en Phython:

 
#!/usr/bin/python 
# -*- coding: iso-8859-15 -*- 
 
import sys 
import getopt 
import os 
 
server = "localhost" 
server_port = 5678 
multicast_addr = "224.0.0.1" 
multicast_port = 5678 
 
def usage(): 
    sys.stderr.write 
    ("DcControl[-sserver][-pport][-amulticast_addr][-mmulticast_port]\n") 
    sys.stderr.write("server=servername(\"%s\")\n" % server) 
    sys.stderr.write("server_port=serverport(%d)\n" % server_port) 
    sys.stderr.write("multicastaddr=multicastaddress(\"%s\")\n" 
                     % multicast_addr) 
    sys.stderr.write("multicastport=multicastport(\"%d\")\n" 
                     % multicast_port) 
opts = "" 
 
try: 
    ropts, extraparams = getopt.getopt(sys.argv[1:],"s:p:c:m:h", 
                                      ["server=", 
                                       "server_port=", 
                                       "multicast_addr=", 
                                       "multicast_port=", 
                                       "help" 
                                       ]) 
except getopt.GetoptError, exc: 
    sys.stderr.write(sys.argv[0] + ":" + exc.msg + "\n") 
    sys.exit(2) 
 
for o, a in opts: 
    if o in ("-s", "--server"): 
        server = a 
    if o in ("-p", "--server_port"): 
        server_port = int(a) 
    if o in ("-a", "--multicast_addr"): 
        multicast_addr = a 
    if o in ("-m", "--multicast_port"): 
        multicast_port = int(a) 
    if o in ("-h", "--help"): 
        usage() 
        sys.exit() 
 
sys.stderr.write(sys.argv[0] + ":server=" + server + \n) 
sys.stderr.write(sys.argv[0] + ":server_port=" + str(server_port) + \n) 
sys.stderr.write(sys.argv[0] + ":multicast_addr=" + multicast_addr + \n) 
sys.stderr.write(sys.argv[0] + ":multicast_port=" + str(multicast_port) + \n) 
 
os.system("javaDcControl" + 
          "" + server + 
          "" + str(server_port) + 
          "" + multicast_addr + 
          "" + str(multicast_port))

El Makefile:

 
%.class: %.java 
        javac -classpath ../Server $*.java 
 
%: %.c 
        gcc $< -o $@ -lpthread 
 
%: %.py 
        cp $*.py $@ 
 
EXE =  dc \ 
        DcControl.class \ 
        DcControl \ 
        ../Server/InFromServer.class \ 
        ../Server/OutToServer.class 
 
all:  $(EXE) 
 
clean: 
        rm -f $(EXE) 
 
install: 
        cp $(EXE) *.class ~/bin 
 
publish: 
        rm -rf /tmp/dc 
        svn export . /tmp/dc 
        tar --create --file=/tmp/dc.tar -C /tmp dc 
        gzip -9 /tmp/dc.tar 
        scp /tmp/dc.tar.gz www.ace.ual.es:~/public_html/imyso 
        rm /tmp/dc.tar.gz

Finalmente en dc ejecute:

make

para obtener el ejecutable dc y el fichero DcControl.class. Nótese que al igual que ModulatorControl, DcControl utiliza las clases InFromServer y OutToServer. Estas clases son accesibles en tiempo de ejecución por DcControl al estar incluida en la variable de entorno CLASSPATH el camino completo al directorio modulator. Sin embargo, todavía es necesario escribir:

export PATH=‘pwd‘:$PATH  
export CLASSPATH=$CLASSPATH:‘pwd‘

para que podamos invocar al programa DcControl desde cualquier directorio.

7 La Transformada de Fourier Discreta

La DFT (Discrete Fourier Transform) o Transformada Discreta de Fourier es la herramienta matemática que permite representar un sonido digitalizado en función de sus componentes de frecuencia. Algorítmicamente hablando, la rutina DFT calcula a partir de un conjunto de muestras de audio digitalizadas otro conjunto de coeficientes de Fourier. Por definición, cada coeficiente de Fourier es un número complejo. La forma natural de representación de estos números es mediante su notación fase-magnitud, donde la magnitud expresa la amplitud del sonido que da lugar a dicha componente de frecuencia y la fase, su fase. Sin embargo, es común representar dichos números complejos también como un número real y otro imaginario.

Generalmente, la rutina DFT necesita que tanto el conjunto de entrada como el de salida de muestras complejas se almacenen en un array. En el array de entrada la posición dentro del array depende del instante de tiempo en el que la muestra allí almacenada se generó. En el array de salida la posición dentro del array indica la banda de frecuencia a la que corresponde dicho coeficiente de Fourier.

En esta práctica vamos a utilizar un algoritmo de cálculo rápido de la DFT conocido como FFT (Fast Fourier Transform). El número de operaciones de la DFT es proporcional a N2 donde N es el número de muestras procesadas. Por el contrario, la FFT tiene una complejidad de N log 2(N).

8 Relación entre la frecuencia de muestreo y la ventana de análisis

Ya que vamos a trabajar con sonidos, el tamaño del array (el número de muestras del array) junto con la frecuencia de muestreo nos va a determinar el tamaño temporal de la ventana de análisis que estamos utilizando en nuestro analizador de espectro de audio. Por ejemplo, si la frecuencia de muestreo utilizada es 8.000 Hz y el tamaño de la ventana medido en muestras es de 1.000 muestras, entonces cada uno de los 500 coeficientes de Fourier calculados por la rutina DFT hablaría de un ancho de banda igual a

4.000 Hz = 8 Hz∕coef.
500 coefs

El primer coeficiente (primer número complejo) se refiere a la banda [0,8) Hz, el segundo a la banda [8,16) y así sucesivamente. Nótese que el Teorema del Muestreo Uniforme indica que si f es la frecuencia de muestreo utilizada, entonces f∕2 es la máxima componente de frecuencia registrada. Por esta razón, aunque pasamos 1.000 muestras a la rutina DFT, obtenemos sólo 500 coeficientes.

Por tanto, el tamaño de la ventana de análisis controla la resolución del espectro en el dominio de la frecuencia y la frecuencia de muestreo el rango de frecuencias capturado.

9 Minimizando la distorsión provocada por la duración limitada de la ventana de análisis

Antes de calcular la FFT de la ventana de análisis las muestras son multiplicadas por una función que minimiza la distorsión espectral. Dicha distorsión está provocada por la duración no-infinita (por motivos prácticos) de la ventana de análisis.

Si analizamos desde el punto de vista de la frecuencia qué estamos haciendo cuando calculamos la FFT de un cojunto finito de muestras de una señal, veremos que en realidad estamos calculando el espectro de la señal resultante de multiplicar las muestras por una función cuadrada (la ventana de análisis) que vale distinto de 0 (en concreto, 1) justamente en el intervalo de tiempo que se toman las muestras.

Si aplicamos el Teorema de Convolución, el espectro resultante de la anterior operación es la convolución en el dominio de la frecuencia de los espectros de la señal de audio y de la función cuadrada. Como el espectro de la función cuadrada es una Sinc (la Función Muestreo) y no una función impulso (Delta de Dirac), el espectro de la señal de audio se presenta distorsionado por dicha Sinc.

Para minimizar esta distorsión podemos hacer dos cosas:

  1. Hacer la ventana de análisis lo más grande posible, provocando así que la Sinc se asemeje a un impulso (la función ideal para convolucionar si no queremos modificar la forma de onda de la señal convolucionada).
  2. Usar una función que disperse menos energía (espectralmente hablando) que una función cuadrada. En esta práctica se utiliza la Ventana de Hamming.

10 RTASA: un analizador de espectro

RTASA (Real Time Audio Spectrum Analyzer) es una aplicación escrita en Java que permite calcular en tiempo real el espectro de frecuencias de Fourier de una señal de audio que entra a través de la entrada de datos estándar.

Por definición, el espectro de frecuencias de Fourier de una señal es el módulo de los coeficientes complejos de Fourier de dicha señal. Los sonidos naturales están constituidos por una sumatoria de sonidos puros o sinusoidales con diferentes fases, amplitudes y frecuencias. Los seres humanos somos especialmente sensibles a la frecuencia de los sonidos cuando vamos a reconocerlos. Por lo tanto, una forma conveniente de caracterizar los sonidos es describir las amplitudes de las componentes puras (frecencias) de los mismos.

Un analizador de espectro en tiempo real es un sistema que es capaz de visualizar y dar información acerca del espectro de una señal, con poco retardo. Para el caso de una señal unidimensional (como es el caso de una señal de audio), el espectro es una gráfica cartesiana donde en el eje X se indica la frecuencia (generalmente en Hz) y en el eje Y la amplitud de dicha frecuencia (generalmente sin dimensiones).

10.1 Entradas

RTASA acepta una secuencia RAW (sin cabecera) de muestras a través de entrada estándar. Debe tratarse de una secuencia de muestras de 16 bits, con signo, en formato little endian (el usado en las máquinas Intel). Se esperan dos canales.

RTASA acepta parámetros iniciales desde la línea de comandos e interactivamente (durante la ejecución), a través de diferentes elementos de entrada.

Los parámetros iniciales son:

  1. El número de bandas de frecuencia analizadas. Por requerimientos de la FFT debe ser una potencia de 2.
  2. La frecuencia de muestreo (en muestras por segundo). Dicha frecuencia debería coincidir con la frecuencia de muestreo de la secuencia de entrada.

Los otros controles interactivos son:

  1. Y-Scale Control: Controla la escala del eje Y del espectro mostrado en la ventana Spectrum. El control se realiza con la rueda del ratón. El paso del incremento o del decremento se controla con los botones.
  2. Elasticity Control: Controla la elasticidad de la gráfica que muestra el historial reciente del espectro mostrado en la ventana Spectrum. Dicha elasticidad se puede indicar con el ratón, arrastrando el slider o introduciéndo un valor en el campo de texto.

10.2 Salidas

La única salida del programa es una copia de los datos de audio de entrada, sin procesar. Por supuesto, se muestra la gráfica del espectro (ventana Spectrum). En la parte superior aparece el espectro del canal izquierdo y abajo, el del canal derecho. La ventana es redimensionable.

10.3 Códigos fuente

El programa RTASA.java (descargue el fichero http://www.ace.ual.es/\~vruiz/imyso/RTASA.tar.gz) tiene como código fuente:

 
/* Real Time Audio Spectrum Analyzer. */ 
 
/* 
 * Version inicial creada por Vicente Gonzalez Ruiz <vruiz@ual.es>. 
 * 
 * Mejoras introducidas por Manuel Marin <niram@eresmas.net> 
 *   referentes al calculo de la frecuencia con el puntero del raton. 
 * 
 * gse. 2006. 
 */ 
 
import java.awt.*; 
import javax.swing.*; 
import java.awt.event.*; 
import java.util.*; 
 
//import java.net.DatagramSocket; 
//import java.net.InetAddress; 
//import java.net.DatagramPacket; 
import java.io.IOException; 
 
public class RTASA 
    extends JComponent 
    implements Runnable, 
               ComponentListener,MouseMotionListener { 
 
    // Variables y metodos relacionados con el raton. 
 
    //Variables para almacenar la posicion actual del raton 
    Dimension d; 
    int posX = 0, posY=0; // Posicion del raton 
    int visualiza_frec; 
 
    // Inicializacion de la funcion de rastreo del raton. 
    public void initMouse() { 
        d = this.getSize(); 
        addMouseMotionListener(this); 
    } 
 
    // Metodo de realizacion de acciones en el caso de arrastrar y 
    // soltar. 
    public void mouseDragged(MouseEvent me) {} 
 
    // Metodo de realizacion de acciones en el caso de movimiento 
    // del raton dentro de la pantalla grafica. Dichas acciones 
    // consisten en calcular el valor de la frecuencia sobre el que 
    // esta situado el puntero del raton. Para realizar el calculo, 
    // se multiplica la variable bandWidth por la coordenada X de la 
    // posicion actual del raton en ese momento, restandole a dicho 
    // resultado el desplazamiento de 20 pixeles existente, ya que la 
    // abcisa empieza en el pixel 20. Para el calculo del valor de 
    // este desplazamiento se multiplica la variable bandWidth por 
    // 20. El valor final de toda esta operacion lo recoge la 
    // variable visualiza_frec. 
    public void mouseMoved(MouseEvent me) { 
        // Si el raton esta situado a la derecha de los 20 pixeles de 
        // xoffset. 
        if (me.getX()>=20){ 
            // Obtiene la frecuencia con la coordenada x. 
            visualiza_frec = (int)(bandWidth*me.getX()-(bandWidth*20)); 
            // Escribe en la consola de texto el valor en Hz. Se puede 
            // prescindir de esta linea ya que dicho valor ya aparece 
            // en la pantalla grafica. 
            //System.err.println("Frecuencia: " + visualiza_frec +" Hz"); 
        } 
        // Si se sale del margen izquierdo, para evitar escribir 
        // valores negativos. 
        else { 
            visualiza_frec = 0; 
            System.err.println("Frecuencia:" + visualiza_frec +"Hz"); 
        } 
 
        // Se almacenan las coordenadas actuales "x" e "y" del raton. 
        posX = me.getX(); 
        posY = me.getY(); 
 
        //Actualizamos la pantalla grafica. 
        this.repaint(); 
    } 
 
    /* Fin de las variables y metodos relacionados con el raton. 
 
    /* Numero de bandas (por defecto). */ 
    static int NUMBER_OF_BANDS = 512; 
 
    /* Numero de muestras por segundo (por defecto). */ 
    static int SAMPLE_RATE = 44100; 
 
    /* A FFT will be performed each STEP samples. */ 
    static int STEP = 512; 
 
    /* Altura inicial de la ventana. */ 
    static int WINDOW_HEIGHT = 512; 
 
    /* Dimensiones de la ventana. */ 
    int windowHeight, windowWidth; 
 
    JFrame frame; 
    Thread thread; /* Este hilo */ 
 
    /* Numero de bytes en cada muestra de audio. */ 
    int bytesPerSample = 2; 
 
    /* Numero de canales. */ 
    int channels = 2; 
 
    /* Numero de muestras por segundo (frecuencia de muestreo). */ 
    float sampleRate; 
 
    /* Ancho de banda mostrado. */ 
    float totalBandWidth; 
 
    /* Anchura de cada banda. */ 
    float bandWidth; 
 
    YScale yScale; 
    ElasticityControl ec; 
    double elasticity; 
 
    /* Factor de escala en el eje Y. */ 
    //float scale; 
 
    /* Buffer de audio. */ 
    byte[] audioBuffer; 
 
    /* Tama~no del buffer de audio en bytes. */ 
    int audioBufferSize; 
 
    double[] leftSpectrum; 
    double[] rightSpectrum; 
    double[][] leftBuffer; 
    double[][] rightBuffer; 
    double[] window; 
 
    /* Arena encima del espectro */ 
    int[] leftSand, rightSand; 
 
    /* Gravedad aplicada a la arena (0 -> ausencia de gravedad, 1 -> 
     * gravedad infinita). */ 
    double sandGravity=0.1; 
 
    /* Tama~no del grano de arena */ 
    int sandSize = 10; 
 
    /* Numero de bandas. */ 
    int numberOfBands; 
 
    int step_bytes; 
 
    /* Numero de muestras de audio. */ 
    int numberOfSamples; 
 
    /* Number of samples avaiable from the last read. */ 
    int availableSamples; 
 
    //int numberOfSpectrumsPerPacket; 
 
    //static final int PACKET_SIZE = 1024; 
    //static final int SIZEOF_SAMPLE = 2; 
    //static final int BUF_SIZE = 4096; 
 
    //static final int PORT = 6789; 
    //DatagramSocket socket; 
    //DatagramPacket sendPacket; 
    //DatagramPacket receivePacket; 
 
    /* Un color. */ 
    Color rojoOscuro; 
 
    public RTASA(int numberOfBands, float sampleRate, int step) throws IOException { 
        System.err.println("+-----------------------------------+"); 
        System.err.println("|RealTimeAudioSpectrumAnalizer|"); 
        System.err.println("+-----------------------------------+"); 
        this.sampleRate = sampleRate; 
        System.err.println("SampleRate=" + sampleRate + "samples/second"); 
        this.numberOfBands = numberOfBands; 
        step_bytes = step * 4; // 2 bytes/sample, 2 channels 
 
        /* Comprobamos que el numero de bandas sea una potencia de dos. */ { 
            int tmp = numberOfBands; 
            boolean numberOfBandsIsAPowerOfTwo = true; 
            int i = 0; 
            while(tmp>1) { 
                if((tmp%2)==1) { 
                    numberOfBandsIsAPowerOfTwo = false; 
                    break; 
                } 
                tmp >>= 1; 
                i++; 
            } 
            if(numberOfBandsIsAPowerOfTwo==false) { 
                System.err.println("Ineedanumberofbandspoweroftwo..."); 
                numberOfSamples = (numberOfSamples>>i); 
            } 
        } 
 
        System.err.println("Numberofbands=" + numberOfBands); 
        numberOfSamples = numberOfBands*2; 
        System.err.println("Numberofsamples=" + numberOfSamples); 
 
        audioBufferSize = channels * numberOfSamples * bytesPerSample; 
        audioBuffer = new byte[audioBufferSize]; 
        //numberOfSpectrumsPerPacket = BUF_SIZE/numberOfSamples/2; 
        leftBuffer = new double[numberOfSamples][2]; 
        rightBuffer = new double[numberOfSamples][2]; 
        leftSpectrum = new double[numberOfSamples]; 
        rightSpectrum = new double[numberOfSamples]; 
        leftSand = new int[numberOfSamples]; 
        rightSand = new int[numberOfSamples]; 
 
        /* Calculamos los coeficientes de la ventana temporal. */ 
        //computeWindow(1); 
        window = new double[numberOfSamples]; 
        Window.computeCoefficients(1/* Ventana tipo 1*/, window); 
 
        totalBandWidth = computeTotalBandWidth(sampleRate); 
        System.err.println("TotalBandWidth=" + totalBandWidth + "Hz"); 
        bandWidth = computeBandWidth(sampleRate, numberOfBands); 
        System.err.println("BandWidth=" + bandWidth + "Hz"); 
        //setDoubleBuffered(true); 
 
        // Create and set up the RTASA window 
        frame = new JFrame("RTASA-Spectrum"); 
 
        //scale = numberOfSamples; 
        // Controlador de la scala en el eje Y 
        yScale = new YScale(numberOfSamples, numberOfSamples/10); 
 
        frame.getContentPane().add(this); 
        windowWidth = numberOfBands + 10; 
        windowHeight = WINDOW_HEIGHT; 
        frame.setSize(windowWidth, windowHeight); 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        frame.addComponentListener(this); 
        Container content = frame.getContentPane(); 
        content.setBackground(Color.black); 
        frame.setVisible(true)/*show()*/; 
 
        // Controlador de la elasticidad del espectro azul. */ 
        ec = new  ElasticityControl(); 
 
        rojoOscuro = new Color(100, 0, 0); 
 
        //Capturar movimiento del raton// 
        initMouse(); 
    } 
 
    /* Lanza el hilo */ 
    public void start() { 
        thread = new Thread(this); 
        thread.setName("RealTimeAudioSpectrumAnalizer"); 
        thread.start(); 
        System.err.println("Yopuedoseguirhaciendocosasaqui!"); 
        // for(;;) { 
        //     int total = 0; 
        //     while(total<audioBufferSize) { 
        //     try { 
        //         System.out.write(audioBuffer,total,256); 
        //         total += System.in.read(audioBuffer,total,/*audioBufferSize-total*/256); 
        //         //System.err.print(total + " "); 
        //         System.err.print("."); 
        //         //Thread.sleep(10); 
        //     } catch (IOException e) { 
        //         System.err.println("Error in the pipe."); 
        //     } /*catch (InterruptedException e) { 
        //         System.err.println("InterruptedException."); 
        //         }*/ 
        //     availableSamples = total/4; /* 2 bytes/sample, 2 channels */ 
        //     //ystem.out.write(audioBuffer,total,audioBufferSize-total); 
        //     } 
        //     //System.out.write(audioBuffer,0,audioBufferSize); 
        // } 
    } 
 
    /* Detiene el hilo */ 
    public void stop() { 
        thread = null; 
    } 
 
    public float computeTotalBandWidth(float sampleRate) { 
        return sampleRate/2; 
    } 
 
    public float computeBandWidth(float sampleRate, int numberOfBands) { 
        return (sampleRate/2)/numberOfBands; 
    } 
 
    public void setGravity(float gravity) { 
        this.sandGravity = gravity; 
        System.out.println(gravity); 
    } 
 
    public void run() { 
        for(;;) { 
 
            int total = 0; 
            while(total<audioBufferSize) { 
                try { 
                    if(step_bytes < (audioBufferSize-total)) { 
                        System.out.write(audioBuffer,total,step_bytes); 
                        total += System.in.read(audioBuffer,total,step_bytes); 
                    } else { 
                        System.out.write(audioBuffer,total,audioBufferSize-total); 
                        total += System.in.read(audioBuffer,total,audioBufferSize-total); 
                    } 
                    //System.err.print(total + " "); 
                    //System.err.print("."); 
                    //Thread.sleep(10); 
                } catch (IOException e) { 
                    System.err.println("Errorinthepipe."); 
                } /*catch (InterruptedException e) { 
                    System.err.println("InterruptedException."); 
                    }*/ 
                availableSamples = total/4; /* 2 bytes/sample, 2 channels */ 
 
                /* We have availableSamples from the last read. These 
                 * samples are going to be placed at the end of the arrays 
                 * while the rest of data remain of previous reads. */ 
                for(int i=availableSamples; i<numberOfSamples; i++) { 
                    leftBuffer[i-availableSamples][0] 
                        = (double)(audioBuffer[4*i+1]*256 + 
                                   audioBuffer[4*i]); 
                    /* Parte imaginaria, muestra izquierda. */ 
                    leftBuffer[i-availableSamples][1] = 0.0; 
                    /* Parte real, muestra derecha. */ 
                    rightBuffer[i-availableSamples][0] 
                        = (double)(audioBuffer[4*i+3]*256 + 
                                   audioBuffer[4*i+2]); 
                    /* Parte imaginaria, muestra derecha. */ 
                    rightBuffer[i-availableSamples][1] = 0; 
                } 
 
                /* Now, we concatenate the new samples. */ 
                for(int i=0; i<availableSamples; i++) { 
                    /* Parte real, muestra izquierda. */ 
                    leftBuffer[i+numberOfSamples-availableSamples][0] 
                        = (double)(audioBuffer[4*i+1]*256 + 
                                   audioBuffer[4*i]); 
                    /* Parte imaginaria, muestra izquierda. */ 
                    leftBuffer[i+numberOfSamples-availableSamples][1] = 0.0; 
                    /* Parte real, muestra derecha. */ 
                    rightBuffer[i+numberOfSamples-availableSamples][0] 
                        = (double)(audioBuffer[4*i+3]*256 + 
                                   audioBuffer[4*i+2]); 
                    /* Parte imaginaria, muestra derecha. */ 
                    rightBuffer[i+numberOfSamples-availableSamples][1] = 0; 
                } 
 
                /* Multiplicamos cada muestra con el correspondiente 
                 * coeficiente de la ventana temporal. */ 
                for(int i=0; i<numberOfSamples; i++) { 
                    leftBuffer[i][0] *= window[i]; 
                    rightBuffer[i][0] *= window[i]; 
                } 
 
                /* Transformada de Fourier del canal izquierdo. */ 
                FFT.direct(leftBuffer); 
                /* Transformada de Fourier del canal derecho. */ 
                FFT.direct(rightBuffer); 
 
                /* Obtenemos la elasticitad de la arena. */ 
                elasticity = ec.getElasticity(); 
                //System.err.println(elasticity); 
 
 
                /* Calculamos el espectro (modulo). */ 
                for(int i=0; i<numberOfSamples; i++) { 
                    leftSpectrum[i] 
                        = Math.sqrt(leftBuffer[i][0]*leftBuffer[i][0] + 
                                    leftBuffer[i][1]*leftBuffer[i][1]); 
                    rightSpectrum[i] 
                        = Math.sqrt(rightBuffer[i][0]*rightBuffer[i][0] + 
                                    rightBuffer[i][1]*rightBuffer[i][1]); 
                } 
 
                /* Calculamos la arena. */ 
                for(int i=0; i<numberOfSamples; i++) { 
                    leftSand[i] 
                        = (int)((1-elasticity)*leftSand[i] + 
                                elasticity*leftSpectrum[i]); 
                    rightSand[i] 
                        = (int)((1-elasticity)*rightSand[i] + 
                                elasticity*rightSpectrum[i]); 
                } 
 
                /* Pintamos. */ 
                repaint(); 
            } 
 
        } 
    } 
 
    // Este metodo no deberia estar aqui. Si creamos una clase para 
    // controlar el tama~no de la ventana deberia de llamarse desde 
    // alli. Lo mismo deberia de ocurrir si creamos una clase para 
    // controlar la frecuencia de muestreo.  Esto se debe de hacer asi 
    // porque solo cuando estas clases estan trabajando es cuando debe 
    // de cambiar la escala y no deberia de pintarse siempre que se 
    // presenta el espectro. 
    void drawHz(Graphics g) { 
        Color color = Color.black; 
        g.setColor(color); 
        for(int i=10; i<numberOfSamples/2; i+=50) { 
            g.drawString(i*bandWidth + "", i, 10); 
        } 
    } 
 
    /* Pinta la ventana */ 
    public void paintComponent(Graphics g) { 
        int xOffset = 20; 
        int yOffset = 25; 
        Color color = Color.red; 
        //g.setColor(color); 
 
        /* Pintamos el espectro del canal izquiero arriba. */ 
        //color = new Color(255,0,0); 
        g.setXORMode(new Color(255,0,0)); 
        for(int i=0; i<numberOfSamples/2/*256*/; i++) { 
            //Double y = new Double(spectrum[i]/numberOfSamples); 
            //int x = y.intValue(); 
            int x = (int)(leftSpectrum[i]/yScale.getScale()); 
            //System.err.print(spectrum[i] + " " + x + " "); 
            g.drawLine(i+xOffset, /*460*/yOffset, i+xOffset, /*460-x*/x+yOffset); 
            //paintLine(i,yOffset,i,x+yOffset,g); 
            //int val_i = (data[2*i]*256+data[2*i+1])/256; 
            //int val_i1 = (data[2*(i+1)]*256+data[2*(i+1)+1])/256; 
            //g.drawLine(i,val_i+128,i+1,val_i1+128); 
        } 
 
        /* Pintamos el espectro del canal derecho abajo. */ 
        g.setXORMode(/*Color.green*/new Color(0,255,0)); 
        for(int i=0; i<numberOfSamples/2/*256*/; i++) { 
            //Double y = new Double(spectrum[i]/numberOfSamples); 
            //int x = y.intValue(); 
            int x = (int)(rightSpectrum[i]/yScale.getScale()); 
            //System.err.print(rightSpectrum[i] + " " + x + " "); 
            g.drawLine(i+xOffset, /*460*/yOffset+windowHeight-85, i+xOffset, /*460-x*/windowHeight-85+yOffset-x); 
            //paintLine(i,yOffset,i,x+yOffset,g); 
            //int val_i = (data[2*i]*256+data[2*i+1])/256; 
            //int val_i1 = (data[2*(i+1)]*256+data[2*(i+1)+1])/256; 
            //g.drawLine(i,val_i+128,i+1,val_i1+128); 
        } 
 
        //color = Color.blue; 
        //g.setColor(color); 
        g.setXORMode(/*Color.blue*/new Color(0,0,255)); 
        for(int i=0; i<numberOfSamples/2; i++) { 
            int x = (int)(leftSand[i]/yScale.getScale()); 
            g.drawLine(i+xOffset, /*460-x*/x+yOffset, i+xOffset, /*450-x*/x+sandSize+yOffset); 
        } 
        //color = Color.green; 
        //g.setColor(color); 
 
        //color = Color.cyan; 
        //g.setColor(color); 
        //g.setXORMode(Color.cyan); 
        //g.setColor(Color.cyan); 
        //color = new Color(10,10,200); 
        //g.setXORMode(color); 
        for(int i=0; i<numberOfSamples/2; i++) { 
            int x = (int)(rightSand[i]/yScale.getScale()); 
            g.drawLine(i+xOffset, /*460-x*/yOffset+windowHeight-85-x, i+xOffset, /*450-x*/yOffset+windowHeight-85-sandSize-x); 
        } 
        //if(dakl) drawHz(g); 
        g.setXORMode(Color.white); 
        for(int i=0; i<numberOfBands; i+= 50) { 
            g.drawString("" + (int)(i*bandWidth), i+xOffset, 15); 
            g.drawString("" + (int)(i*bandWidth), i+xOffset, /*505*/windowHeight-40); 
        } 
 
        //g.setColor(Color.red); 
        for(int i=0; i<numberOfBands; i+= 50) { 
            g.drawLine(i+xOffset,18,i+xOffset,21); 
            g.drawLine(i+xOffset,windowHeight-84+yOffset+3,i+xOffset,windowHeight-84+yOffset+6); 
        } 
 
        //Mostrar la frecuencia siguiendo al puntero del raton// 
        if (posX>=xOffset && posX<=windowWidth){ //para no salirse de los margenes establecidos// 
          g.drawString("" +visualiza_frec + "Hz", posX, posY); 
          //Mostrar linea vertical cuyas coordenadas de inicio y fin son los siguientes dos puntos: 
          //  origen(posX, 0) 
          //  fin(posX, windowHeight) 
          g.drawLine(posX,0,posX,windowHeight); 
        } 
    } 
 
    public void componentHidden(ComponentEvent e) { 
    } 
 
    public void componentMoved(ComponentEvent e) { 
    } 
 
    public void componentResized(ComponentEvent e) { 
        Component c = e.getComponent(); 
        windowHeight = c.getSize().height; 
        //System.err.println(c.getSize().width + " " + c.getSize().height); 
    } 
 
    public void componentShown(ComponentEvent e) { 
    } 
 
    public static void main(String[] args) throws Exception { 
        int numberOfBands = NUMBER_OF_BANDS; 
        float sampleRate = SAMPLE_RATE; 
        int step = STEP; 
        if(args.length>=1) { 
            try { 
                numberOfBands = new Integer(args[0]).intValue(); 
            }  catch (NumberFormatException e) { 
                System.err.println("Errorparsing\"" + args[1] + "\""); 
            } 
        } 
        if(args.length>=2) { 
            try { 
                sampleRate = new Float(args[1]).floatValue(); 
            }  catch (NumberFormatException e) { 
                System.err.println("Errorparsing\"" + args[2] + "\""); 
            } 
        } 
        if(args.length>=3) { 
            try { 
                step = new Float(args[2]).intValue(); 
            }  catch (NumberFormatException e) { 
                System.err.println("Errorparsing\"" + args[3] + "\""); 
            } 
        } 
        RTASA analizador = new RTASA(numberOfBands, sampleRate, step); 
        analizador.start(); 
    } 
}

Esta clase necesita además de:

11 Ejemplos de utilización

Para ejecutar correctamente esta aplicacion debemos lanzar los programas modulator y dc en el host que posee la tarjeta de sonido y por lo tanto, donde se va a realizar la audición. El/los cliente/s que controla/n la frecuencia de la portandora puede/n ejecutarse también es ese host o en cualquier otro host de Internet (siempre que el tráfico multicast entre los clientes y el servidor esté permitido). En la práctica, sólo en la red local el tráfico multicast suele estar permitido.

Como se puede ver en el código de modulator, el socket que hemos creado para controlar la frecuencia acepta una cadena de caracteres que representan un número entero.3 Puesto que dicha cadena está en formato ASCII, podemos usar el programa estándar telnet para controlar la frecuencia de la portadora. Para hacer esto escribiremos:

telnet host_que_ejecuta_modulator PORT

donde PORT es el puerto de escucha, definido en el fichero modulator.c. Tras establecer la conexión podremos cambiar la frecuencia tecleando una nueva (y pulsando la tecla ¡Enter¿). La otra forma de controlar la frecuencia consiste en usar ModulatorControl. Para ejecutar este programa escribieremos:

java ModulatorControl host_que_ejecuta_modulator

en el directorio que contiene las clases compiladas (los ficheros .class).

Por supuesto, es necesariauna máquina virtual de Java instalada en cada host cliente. Estos son algunos ejemplos interesantes:

  1. Visualizando el espectro de lo que entra por el micro:
    host$ arecord -f cd -t raw | java RTASA > /dev/null

  2. Visualizando el espectro de un sonido almacenado en un fichero MP3 y escuchando dicho sonido:
    host$ wget http://www.hpca.ual.es/~vruiz/docencia\  
    /imagen_y_sonido/practicas/audio_files/THX.mp3  
    host$ wget http://www.hpca.ual.es/~vruiz/docencia\  
    /imagen_y_sonido/practicas/audio_files/Javier_Paxinho__Temura.mp3  
    host$ mpg321 THX.mp3 -s | java RTASA | aplay -f cd -t raw  
    host$ mpg321 Javier_Paxinho__Temura.mp3 -s | java RTASA | aplay -f cd -t raw

  3. Igual que antes, pero sincronizado el RTASA con el audio:
    host$ wget http://www.ace.ual.es/~vruiz/imyso/delay.tar.gz  
    host$ tar xzvf delay.tar.gz  
    host$ cd delay  
    host$ make all  
    host$ PATH=$PATH:‘pwd‘  
    host$ mknod p pipe  
    host$ mpg321 /tmp/Javier_Paxinho__Temura.mp3 -s | tee p | aplay -f cd  
    -t raw  
    host$ delay -d 800 < p | java RTASA 4096 44100 > /dev/null

  4. Modulando y escuchando la señal que entra por el micro:
    host$ arecord -f cd -t raw | modulator | aplay -f cd -t raw &  
    host$ java ModulatorControl localhost

  5. Igual, pero usando dos hosts:
    host1$ arecord -f cd -t raw | modulator | aplay -f cd -t raw  
    host2$ java ModulatorControl host1

  6. Igual, pero usando tres hosts:
    host1$ arecord -f cd -t raw | modulator | aplay -f cd -t raw  
    host2$ java ModulatorControl host1  
    host3$ java ModulatorControl host1

  7. Qué ocurre al modular en el dominio de la frecuencia?:
    host$ arecord -f cd -t raw | java RTASA | modulator | \  
          java RTASA > /dev/null &  
    host$ java ModulatorControl localhost

  8. Escuchamos una sinusoide y controlamos su frecuencia con telnet:
    host$ dc | modulator | aplay -f cd -t raw  
    host$ telnet host 6789 # Ejecutar en otra consola

  9. Escuchamos una sinusoide y controlamos su frecuencia con ModulatorControl:
    host$ dc | modulator | aplay -f cd -t raw &  
    host$ java ModulatorControl localhost

  10. Escuchamos una sinusoide, controlando su frecuencia con ModulatorControl y visualizando el espectro:
    host$ dc | modulator | java -jar RTASA | aplay -f cd -t raw &  
    host$ ModulatorControl

12 Determinación del umbral de audición

Finalmente, ya poseemos todos los comocimientos y herramientas necesarias para llevar a cabo nuestro experimento. Ejecutar4 :

host$ dc | modulator | java -jar RTASA | aplay -f cd -t raw &  
host$ ModulatorControl &  
host$ DcControl &

Por comodidad, se ha creado un tar.gz con todo el código necesario en http://www.ace.ual.es/\~vruiz/imyso/umbral.tar.gz).

Y para diferentes frecuencias (tantas como sea posible) comience a incrementar el valor de la señal producida por dc. En cuanto perciba el tono de la sinusoide, anote el valor que indica dc o DcControl. Este valor será su umbral de audición para la correspondiente frecuencia.