/*
 * SRCFILE: rap-isd.c 
 * NAME: RAP-ISD (Rec And Play ISD1420 Memory)
 * VERSION: 0.2
 * DESC: Graba y reproduce mensages en una memoria de audio ISD1420 a traves
 *       del puerto paralelo de la PC.
 * OS: Linux 2.x.x
 *
 * Autor: Estudiez, Boris
 * Nick : slice
 * Fecha: 28/09/2003
 * Update: xx/xx/xx
 * Web  : http://stk.freeshell.org
 * Mails: slicetek@hotpop.com, 43824@electronica.frc.utn.edu.ar
 *
 * Compilacion: gcc -O rap-isd.c -o rap
 * Ejecucion: ./rap [OPCION] [ARGUMENTOS]
 *
 * Nota(0): Este programa es funcional pero esta en estado de desarrollo.
 * Los errores por su mal uso no estan contemplados. whocare? Enjoyit!
 *
 * Nota(1): Usted debe ser root para ejecutar este programa. Si quiere
 * ejecutarlo como usuario, como root teclee: chmod +s rap-isd
 * y luego puede usar el programa como usuario.
 *
 * Nota(2): El programa no verifica si los mensages a grabar exeden
 * el espacio en la memoria ISD1420.
 *
 * Nota(3): Lea el fichero README.TXT que se adjunta para un corrrecto
 * uso del programa, se recomienda LEER la hoja de datos de la memoria
 * ISD1420 (www.isd.com) antes de usar el programa!. 
 * La interfaz de hardware tambien se adjunta para un correcto conexionado
 * del puerto paralelo a la memoria ISD1420.
 *
 */
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <asm/io.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/* Modificar si es necesario: lp0 = 0x378, lp1 = 0x278 y lp2 = 0x3BC */

#define PORT 0x378  /* Direccion del puerto paralelo a usar lp0 (LPT1) */


/* Definiciones usadas por el programa */
#define VERSION "0.2"
#define NAMEPRG "rap-isd"
#define FALSE   0
#define TRUE    1
#define ERROR   0
#define OK      1
#define ERRPAR  "Error, Parametros incorrectos o insuficientes."

/* 
 Retardos introducidos por el ISD1420 - Expresados en microsegundos. 
 Para mayor referencia lea la hoja tecnica del ISD1420. TYP=TYPICAL.
 Valores probados en una PIII @ 500 MHz. Si su PC es mas rapida
 y el programa no funciona correctamente al grabar, incremente el
 valor de TRPUD.
 */

#define TSET  1     /* ADDRESS SETUP TIME - TYP = 300 ns */
#define TRPUD 32000 /* RECORD POWERUP DELAY TIME - TYP = 32 ms */
#define TRPDD 32500 /* RECORD POWERDOWN DELAY TIME - TYP = 32 ms */
#define TPPUD 32000 /* PLAYBACK POWERUP DELAY TIME - TYP = 32 ms */
#define TPPDD 8200  /* PLAYBACK POWERDOWN DELAY TIME - TYP = 8.1 ms */
#define THOLD 1     /* ADDRESS HOLD TIME - TYP = 0s */

/* 
 Comando de la ISD1420 asociado Control Port del puerto paralelo.
 Pines usados: 
 Pin 1 (strobe) conectado a Pin 27 (/rec) de ISD1420.
 Pin 14 (Auto Feed XT) conectado a pin 24 (/playe) de ISD1420.
 */
#define PLAYEON  0x02 /* /PLAYE = 0 -> Activa ISD para Reproducir */ 
#define PLAYEOFF 0
#define RECON    0x01 /* /REC = 0 -> Activa ISD para Grabar */ 
#define RECOFF   0
#define STATIC   0    /* ISD1420 en modo estatico -> /PLAYE=1 y /REC=1 */ 
 
/* 
 Seleccion de Modos de operacion y Direccionamiento Interno de
 memoria de la ISD1420 asociado al Data Port del Puerto Paralelo.
 */ 

/* Modo de Operacion */
#define OPMODE 0xC0  /* ISD1420 en Modo de Operacion -> A6=1 y A7=1 */
#define OP0    0x01  /* Modo de Operacion - Message Cueing -> A0=1 */
#define OP4    0x10  /* Modo de Operacion - Consecutive Addressing -> A4=1 */

/* Direccionamiento de Memoria */
#define RESET_ADDRESS 0 /* Direccion 0 de memoria interna de ISD1420 */
 

char player[BUFSIZ];  /* Almacena nombre del Reproductor de audio */

int rec_isd1420(int isd_address, char *msg_path);
int reclst_isd1420(char *msg_path_lst);
int play_isd1420(int isd_address);
int playn_isd1420(int N);
void inicialize_isd_hard(void);
int iline(register char *line, char c);
void help(void);

/* 
 Lee bit 5 del byte (PORT+1) - STATUS PORT / bit PE / pin 12,
 conectado a pin 25 (/recled) de ISD1420.
 Devuelve 0 cuando encontramos un EOM al reproducir un mensage y 1
 cuando no hay EOM presente.
 */
int getEOM(void) { return (inb(PORT+1) & 0x20); }   

int main(int argc, char *argv[], char *envp[])
{
   int c, error=FALSE;
   
   /* Dar Permisos a PORT, PORT+1 y PORT+2 */
   if(ioperm(PORT, 3, 1)) 
   {
      perror("rap-isd, error en funcion ioperm"); 
      exit(EXIT_FAILURE);
   }
   
   if(argc >= 2 && *argv[1] == '-')
   {
       c = argv[1][1];
       switch(c)
       {
       case 'r':
          if(argc==5)
          {
             strcpy(player, argv[4]);
             rec_isd1420(atoi(argv[2]), argv[3]); 
          }    
          else
          {
             fprintf(stderr, "%s: %s\n", NAMEPRG, ERRPAR);
             error=TRUE;
          }  
          break;
       case 'l':
          if(argc==4)
          {
             strcpy(player, argv[3]);
             reclst_isd1420(argv[2]);
          }    
          else
          {
             fprintf(stderr, "%s: %s\n", NAMEPRG, ERRPAR);
             error=TRUE;
          }  
          break;
       case 'p':
          if(argc==3)
             play_isd1420(atoi(argv[2]));
          else
          {
             fprintf(stderr, "%s: %s\n", NAMEPRG, ERRPAR);
             error=TRUE;
          }  
          break;
       case 's':
          if(argc==3)
             playn_isd1420(atoi(argv[2]));
          else
          {
             fprintf(stderr, "%s: %s\n", NAMEPRG, ERRPAR);
             error=TRUE;
          }  
          break;
       case 'i':
          if(argc==2)
             inicialize_isd_hard();
          else
          {
             fprintf(stderr, "%s: %s\n", NAMEPRG, ERRPAR);
             error=TRUE;
          }  
          break;
       case 'm':
          if(argc==2)
          {
             printf("\nDireccion del Puerto Paralelo: 0x%x\n", PORT);
             printf("\nUd. Puede cambiar este valor, desde la variable\n");
             printf("PORT, ubicada en primeras lineas del codigo fuente, \n");
             printf("luego compile el programa nuevamente.\n\n");

				 printf("Para ejemplos lea el archivo: README.TXT incluido.\n\n");
          }   
          else
          {
             fprintf(stderr, "%s: %s\n", NAMEPRG, ERRPAR);
             error=TRUE;
          }  
          break;
       case 'h':
          help();
          break;
       default:
          help();
          break;
       }
   }
   else
   {
      help();
      error=TRUE;
   }   
               
   if(ioperm(PORT, 3, 0)) /* Quitando Permisos a la direccion PORT */
   {

      exit(EXIT_FAILURE);
   }     
   
   if(error==FALSE)   
      exit(EXIT_SUCCESS);
   else
      exit(EXIT_FAILURE);
}   

/*
 rec_isd1420: graba el mensage msg_path en la direccion
 isd_address de la memoria ISD1420.
 Donde : 0 <= isd_address <= 159, y msg_path es el path donde
 se encuentra el archivo de sonido a grabar. 
 Devuelve: OK -> Todo bien y ERROR -> Todo mal... 
 */

int rec_isd1420(int isd_address, char *msg_path)
{
   char command[4096];
   
   outb(STATIC, PORT+2);       /* /PLAYE=1 y /REC=1 */

   if(isd_address >= 0 && isd_address <= 159)
      outb(isd_address, PORT); /* Direccion de memoria de ISD1420 */
   else
   {
      printf("Direccion %d no valida, rango valido: 0-159\n", isd_address);
      return ERROR;            /* direccion no valida */
   }   

   usleep(TSET);   
   printf("ISD1420: Grabando mensage.\n");
   printf("Direccion de Memoria: %d \n", isd_address);
   printf("Archivo: %s\n", msg_path);
   sprintf(command, "%s %s > /dev/null ", player, msg_path);   
   
   outb(RECON, PORT+2);        /* /REC=0 - Grabamos */
   /* usleep(TRPUD) ; // descomentar si hace falta */
   system(command);            /* Reproducir sonido en la PC */

   outb(RECOFF, PORT+2);       /* /REC=1 - Grabacion terminada */   
   printf("\n");
   usleep(TRPDD);					 /* Esperamos tiempo de apagado */
   
   return OK;
}   

/*
 reclst_isd1420: Graba los mensages indicados del archivo msg_path_lst
 en la memoria ISD1420. La lista contiene el path de cada archivo
 de audio a grabar. Los mensages son grabados consecutivamente
 desde la direccion 0 de la memoria y son terminandos con un /EOM 
 (END OF MESSAGE) dentro de la memoria ISD.
 Devuelve: OK -> Todo bien y ERROR -> Todo mal... 
 Nota: Es conveniente grabar silencio como primer mensage de una
 duracion de 0.5 segundos (primeras 4 posiciones de memoria) para
 que si el ISD se dispara por ruido, no reprodusca ningun mensage.
 Quizas mas seguro seria grabar 2 mensages de 250 ms.
*/ 

int reclst_isd1420(char *msg_path_lst)
{
   FILE *msg_filelst;
   char command[4096], line[4096];
   int stat=OK, msg_recorded=0;
   
   outb(STATIC, PORT+2);       /* /PLAYE=1 y /REC=1 */
   outb(RESET_ADDRESS, PORT);  /* Ponemos 0 direccion de mem ISD.
                                  Resetea Puntero A4 =0 */
   usleep(TSET);                            
   outb(OPMODE+OP4, PORT);    /* 11010000 - Consecutive Addresing A4=1
                                 ello es para que en cada /REC=0
                                 el dispositivo no resetee el puntero
                                 de direcciones interno. */
   usleep(TSET);                                                                  
   
   if((msg_filelst = fopen(msg_path_lst, "r")) != NULL)
   {
      while(fgets(line, 4096, msg_filelst))
      {
         if(!iline(line, '#')) /*ignora lineas en blanco o antecedidas con #*/
           continue;
         line[strlen(line)-1] = '\0';
         sprintf(command, "%s %s > /dev/null", player, line);
         printf("\nISD1420: Grabando mensage.\n");
         printf("Numero de mensage: %d \n", ++msg_recorded);
         printf("Archivo: %s\n", line);

         usleep(TSET);
         outb(RECON, PORT+2);      /* /REC=0 - Grabamos */
         /* usleep(TRPUD) ; // descomentar si hace falta */
         system(command); 			  /* Reproducir Sonido del PC*/
         outb(RECOFF, PORT+2);     /* /REC=1 -> poner EOM (terminar grabar)*/   
         printf("\n");
         usleep(TRPDD);				  /* Esperamos tiempo de apagado */
         sleep(1);					  /* Esperamos tensiones en el pin
                                      /REC se estabilize y se ponga a 1 .
                                      Nos aceguramos una correcta grabacion.*/                                      
      }  

      printf("\nTotal de Mensages Grabados: %d\n\n", msg_recorded);
      fclose(msg_filelst);
   } 
   else 
   {
      fprintf(stderr, "rap-isd: %s archivo inexistente.\n", msg_path_lst);
      stat=ERROR;
   }  

   outb(RESET_ADDRESS, PORT);     /* Resetemos puntero interno de direcciones
                                     y quitamos modo de operacion conse-
                                     cutive addressing. */  
   return stat;
}   

/*
 play_isd1420: Reproduce mensage de la ISD1420 ubicado en la direccion
 isd_address de la memoria. Espera hasta que termina de reproducirse
 el mensage determinado por su EOM.
 Donde : 0 <= isd_address <= 159
 Devuelve: OK -> Todo bien y ERROR -> Todo mal... 
 */

int play_isd1420(int isd_address)
{
   
   outb(STATIC, PORT+2);       /* /PLAYE=1 y /REC=1 */

   if(isd_address >= 0 && isd_address <= 159)
      outb(isd_address, PORT); /* Direccion de memoria de ISD1420 */
   else
   {
      printf("Direccion %d no valida, rango valido: 0-159\n", isd_address);
      return ERROR;            /* direccion no valida */
   }   
      
   usleep(TSET);
   printf("ISD1420: Reproduciendo mensage.\n");
   printf("Direccion de Memoria: %d \n", isd_address);
   printf("Esperando fin de mensage (EOM) ...\n");
   outb(PLAYEON, PORT+2);     /* /PLAYE=0 */
   usleep(TPPUD);					/* Esperamos Tiempo de encendido */
   outb(PLAYEOFF, PORT+2);    /* /PLAYE=1 */      
   while(getEOM());           /* Esperamos fin de mensage /EOM */
   usleep(TPPDD);      
   
   outb(RESET_ADDRESS, PORT); /* En modo estatico esto resetea el puntero
      									de direcciones interno de la memoria. */
   return OK;
}   

/*
 playn_isd1420: Saltea N-1 mensages determinados por un /EOM y luego reproduce 
 el mensage que le sigue.  Espera hasta que termina de reproducirse
 el mensage determinado por su EOM.
 Equivale a decir que reproduce el mensage N de la memoria ISD.
 Decimos que el mensage N=1 es el primero de todos.
 Donde : 1 <= N <= 160 como maximo. 
 Devuelve: OK -> Todo bien y ERROR -> Todo mal... 
 
*/

int playn_isd1420(int N)
{
   int i;
   
   outb(STATIC, PORT+2);        /* /PLAYE=1 y /REC=1 */
   outb(RESET_ADDRESS, PORT);   /* Puntero de direcciones interno reseteado */
   usleep(TSET);

   if(N < 1 || N > 160)
   {
      printf("Numero %d de mensage invalido, rango valido: 1-160\n", N);
      return ERROR;          /* direccion no valida */
   }   
   
   /* Modo de operacion: A0 -> MESSAGE CUEING y A4 -> CONSECUTIVE ADDRESSING */
   outb(OPMODE+OP0+OP4, PORT); /* 11010001 -> A7=1,A6=1 + A0=1,A4=1 */
   usleep(TSET);

   
   for(i=1; i < N; i++) /* Tiempo aprox de ciclo for: (N-1)*2(TSET) */
   {
      usleep(TSET);
      outb(PLAYEON, PORT+2);    /* /PLAYE=0 -> saltear 1 mensage */
      usleep(TSET);             /*  Tiempo minimo para /PLAYE=0 */
      outb(PLAYEOFF, PORT+2);   /* /PLAYE=1 */
   }   

   /* Sacamos Modo Message Cueing pero dejamos Consecutive addressing, ello
      es para que no se resetee el puntero de direccciones interno
      de la ISD1420. Ademas el proximo /PLAYE=0 No saltea mensage. Sino
      que reproduce.*/
      
   outb(OPMODE+OP4, PORT); /* 11010000 -> A7=1,A6=1 + A0=0,A4=1 */
   usleep(TSET);

   printf("ISD1420: Reproduciendo mensage.\n");
   printf("Mensage Numero: %d \n", N);
   printf("Esperando fin de mensage (EOM) ...\n");

   outb(PLAYEON, PORT+2);     /* /PLAYE=0 -> Reproducimos mensage N.*/
   usleep(TPPUD);					/*  Esperamos tiempo de encendido */
   outb(PLAYEOFF, PORT+2);    /* /PLAYE=1 */
   while(getEOM());           /* Esperamos fin de mensage /EOM */
   usleep(TPPDD);      			/*  Esperamos tiempo de apagado. 
                                  Para /PLAYE este tiempo no tiene mucho
                                  sentido, pero es util si vamos a ejecutar
                                  muchas veces esta funcion.*/
      
   outb(RESET_ADDRESS, PORT); /* Sacamos modo de operacion y reseteamos 
                                 puntero de direcciones interno de la 
                                 ISD1420. A4=0*/
   return OK;
}   

/*
 inicialize_isd_hard: inicializa el hardware para grabar la memoria
 ISD1420 conectado al puerto paralelo.
 Es decir pone a la ISD1420 en modo estatico (sin hacer nada) y
 la linea de direcciones A0...A7 es puesta a Cero.
 En otras palabras PORT y PORT+2 del puero son puestos a CERO, los
 niveles que presentara a la salida el puerto sera de 0 Volts.
*/
void inicialize_isd_hard(void) 
{
   printf("Inicializando Puerto Paralelo...\n");
   outb(STATIC, PORT+2);
   outb(RESET_ADDRESS, PORT); 
   usleep(TSET);
   printf("Listo, conecte la ISD1420 a la interfaz de hardware.\n");
}   


void help(void)
{
   fprintf(stderr, "\nRAP-ISD %s (Rec And Play ISD1420 Memory)\n", VERSION);
   fprintf(stderr, "Sintaxis: rap-isd [OPCION] [argumento/s]\n");
   fprintf(stderr, "OPCIONES:\n");
   fprintf(stderr, "\t -r isd_address file.wav wav_player (Graba mensage en direccion\n");
   fprintf(stderr, "\t    isd_address de la memoria ISD1420)\n");
   fprintf(stderr, "\t -p isd_address (Reproduce mensage ubicado en la direccion isd_address)\n");
   fprintf(stderr, "\t -l wav-list.txt wav_player (Graba mensages listados en wav-list.txt,\n");
   fprintf(stderr, "\t    delimitandolos con EOM en la memoria ISD1420)\n");
   fprintf(stderr, "\t -s msg_number (Reproduce mensage N de la ISD1420, cada mensage debe\n");
   fprintf(stderr, "\t    estar limitado con un EOM dentro de la ISD1420)\n");
   fprintf(stderr, "\t -i Inicializa Puerto paralelo, use esta opcion antes de conectar el\n");
   fprintf(stderr, "\t    Puerto Paralelo a la memoria ISD1420. Tambien puede usarse para\n");
   fprintf(stderr, "\t    para poner a la ISD1420 en modo estatico (no graba ni reproduce).\n");
   fprintf(stderr, "\t -m Informacion adicional del programa y ejemplos.\n\n");
   fprintf(stderr, "\nAutor: Boris Estudiez\n");
   fprintf(stderr, "Mail(1): stk@freeshell.org\n");
   fprintf(stderr, "Mail(2): 43824@electronica.frc.utn.edu.ar\n");
   fprintf(stderr, "Web: http://stk.freeshell.org\n\n");
}  

/*
  iline: toma una cadena y regresa 0 si solamente contiene espacios
  en blanco o si su primera letra es igual a la contenida en C. 
*/

int iline(register char *line, char c)
{
   while(isspace(*line))
      *++line;
   if(*line == c)
      return 0;
   else if(*line == '\0')
      return 0;
   else
      return 1; 
}