/* * * * * * * * * * * * * * * * * * * * * * 
 *  eSpeakPackage.h  -- Header to utilize the eSpeak package within C
 *
 *  eSpeak's author:  Jonathan Duddington
 *  eSpeak source:  http://espeak.sourceforge.net/
 *
 *  author of this adaptation package:  Henry M. Walker
 *  last revised:  October 3, 2013
 *
 * * * * * * * * * * * * * * * * * * * * * */

/* Note: each function name starts with eSpeak to make it 
   easier to understand whether it is a speech function. */

#include "eSpeakPackage.h"
#include <sys/types.h>       /* file of data types needed for many compilers */
#include <ctype.h>           /* needed for tolower */
#include <unistd.h>          /* needed for fork, getpid procedures */
#include <stdlib.h>          /* needed for exit procedures */
#include <string.h>          /* needed for constructing full path name */
#include <sys/wait.h>       /* needed for waitpid */
#include <stdio.h>
#include <espeak/speak_lib.h>

/* string commands for textlines-to-speech program */
char * terminating_string = "eSpeakABBCCCDDDEEEEFFFFdone";
char * set_voice_female = "eSpeakABBCCCDDDEEEEFFFFfemale";
char * set_voice_male = "eSpeakABBCCCDDDEEEEFFFFmale";

/* directory of textlines-to-speech program
   and startPulseAudio and stopPulseAudio, if needed */
/* Use BASE_EIRECTORY, if given as a compile parameter,
   otherwise, use /usr/local/bin
*/
#ifndef BIN_DIRECTORY
char * speech_dir = "/usr/local/bin";
#else
// pass //char * BIN_DIRECTORY as a parameter;
char * speech_dir = BIN_DIRECTORY;
#endif

/* variables to record process id of child */
pid_t pid_audio_start, pid_audio_stop, pid_to_speech;                

/* basic pipe structure */
int to_speech[2];   /* communication to textlines-to-speech */
int from_speech[2]; /* communication from textlines-to-speech */
int error_speech[2];/* standard error for textlines-to-speech */

int debug = 0;  /* debugging flag --- 1 for printing; 0 supress printing */

/* set up the local environment to utilize the eSpeak package 
 */
void eSpeakConnect ()
{  
   
  /* start pulseaudio running for audio output */
  /* startPulseAudio script returns
        ok if user actively working at workstation
        no if user logged in remotely
  */

  FILE * f_audio_shell;

  if (debug)
    perror ("starting eSpeakConnect");
  
  /* construct full path name of startPulseAudio program */

  #ifdef COMPUTER_SHARED
  char * prog_call = "startPulseAudio";
  char * full_script = malloc (strlen(speech_dir) + strlen(prog_call) + 2);
  full_script[0] = 0;
  strcpy (full_script, speech_dir);
  strcat (full_script, "/");
  strcat (full_script, prog_call);
  if (debug)
     printf ("full programm call:  %s\n", full_script);
  
  
  /* set up script to check user and start pulseaudio*/
   f_audio_shell = popen (full_script, "r");
  if (f_audio_shell == NULL)
    {
      perror ("error in starting pulseaudio");
      exit (1);
    }

  free (full_script);
  if (debug)
     printf ("pipe opened \n");
  
  /* retrieve 2 character result from startPulseAudio 
     results should be   ok or no */
  char script_response[3];
  fscanf (f_audio_shell, "%s", script_response);
  script_response[2] = 0;

  if (strcmp (script_response, "ok") != 0)
    {
      printf ("problem in starting audio\n");
      printf ("note:  audio cannot be generated if user logged in remotely\n");
      exit (1);
    }

    /* pulseaudio running for user currently typing at workstation's keyboard */
    if (debug)
      printf ("pulse audio started\n");

#endif
  
  /* create pipes for 2-way communcation with textlines-to-speech program
      pipe to_speech 
           from this eSpeakPackage.c to textlines-to-speech.c
      pipe from_speech
           from textlines-to-speech.c to this eSpeakPackage.c
      pipe error_speech
           standard error for textlinex-to-speech.c, which can be ignored

      within each pipe:   fd[0] will be input end
                          fd[1] will be output end
  */
   /* create pipes and check for errors */
   if (pipe (to_speech) < 0) 
     { perror("pipe error");
       exit (1);
     }
  
   if (pipe (from_speech) < 0) 
     { perror("pipe error");
       exit (1);
     }

     if (pipe (error_speech) < 0) 
     { perror("pipe error");
       exit (1);
       }

   /* fork processing
           parent:  this package
           child:  textlines-to-speech
   */
   if ((pid_to_speech = fork()) < 0)
     { perror ("error in fork");  
       exit (1);
     }

  if (0 == pid_to_speech)             
    { printf ("Starting eSpeak\n");

      /* processing for textlines-to-speech */
       /* to_speech pipe provides input */
       close (to_speech[1]);  /* close output end */
       /* set standard input to to_speech pipe */
       if (to_speech[0] !=  STDIN_FILENO) 
         {
           if (dup2(to_speech[0], STDIN_FILENO) != STDIN_FILENO)
             { perror("dup2 error for standard input");
               exit(1);
             }
           close(to_speech[0]); /* not needed after dup2 */
         }

      /* from_speech pipe provides output */
       close (from_speech[0]);  /* close input end */
       /* set standard output to pipe */
       if (from_speech[1] !=  STDOUT_FILENO) 
         {
           if (dup2(from_speech[1], STDOUT_FILENO) != STDOUT_FILENO)
             { perror("dup2 error for standard output");
               exit(1);
             }
           close(from_speech[1]); /* not needed after dup2 */
         }

        /* error_speech pipe provides standard error */
       close (error_speech[0]);  /* close output end */
       /* set standard input to to_speech pipe */
       if (error_speech[1] !=  STDERR_FILENO) 
         {
           if (dup2(error_speech[1], STDERR_FILENO) != STDERR_FILENO)
             { perror("dup2 error for standard error");
                 exit(1);
               fprintf (stdout, "error connecting stderr\n");
             }
           close(error_speech[1]);  /* not needed after dup2 */
         }
  
     
       /* start program textlines-to-speech running
          to speak successive lines of text
      
          textlines-to-speech takes successive lines from standard input
          input continues until "eSpeakABBCCCDDDEEEEFFFFdone" read
       */

       /* construct full path name of textlines-to-speech program */
       char * prog_call = "textlines-to-speech";
       char * full_call = malloc (strlen(speech_dir) + strlen(prog_call) + 2);
       full_call[0] = 0; /* start with null string */
       strcpy (full_call, speech_dir);
       strcat (full_call, "/");
       strcat (full_call, prog_call);

       if (debug)
        fprintf (stderr, "full program call for textlines-to-speech:  %s\n", 
          full_call);
   
       /* start executing textlines-to-speech for speech synthesis
          with stdin, stdout, and stderr attached to pipes
       */
       execlp (full_call, full_call, (char *) 0);

       /* execle will return only if exec function fails */
       fprintf (stderr, "error in setting up textlines-to-speech\n");
       exit(0);
     }

  else
    { /* prepare eSpeackPackage and user for communications
         to textlines-to-speech */

      close (to_speech[0]);    /* close input end */
      close (from_speech[1]);  /* close output end */

      /* wait for textlines-to-speech process created */
      int chars_processed = -5;
      read (from_speech[0], &chars_processed, 4);
      if (debug)
        printf ("response from textlines-to-speech:  '%d'\n", chars_processed);


      if (debug)
        printf ("eSpeakConnect done\n");

    }
}

/* clean up the local environment when the eSpeak package is no longer needed
 */
void eSpeakDisconnect ()
{  
  /* printf ("starting eSpeakDisconnect\n"); */
  /* stop textlines-to-speech with sentinel string*/
  write (to_speech[1], "\n", 1);
  write (to_speech[1], terminating_string, strlen(terminating_string));
  write (to_speech[1], "\n", 1);
  /* printf ("terminating textlines-to-speech\n"); */
  int status;
  waitpid(pid_to_speech, &status, 0); // called with no extra options
  /* printf ("now cleanup pulseaudio\n"); */

  /* close pipe to textlines-to-speech */
  /*waitpid (pid);*/  /* wait for textlines-to-speech to finish */

#ifdef COMPUTER_SHARED
  /* terminate pulseaudio daemon, if computer environment is shared */
  pid_audio_stop = fork();      /* create new process */
  if ( -1 == pid_audio_stop)    /* check for error in spawning child process */
    { perror ("error in fork");  
      exit (1);
    }

  if (0 == pid_audio_stop)      /* check if this is the new child process */
    { /* stopping pulseaudio */
      char * prog_call = "stopPulseAudio";
      char * full_script = malloc (strlen(speech_dir) + strlen(prog_call) + 2);
      full_script[0] = 0;
      strcpy (full_script, speech_dir);
      strcat (full_script, "/");
      strcat (full_script, prog_call);
      /* printf ("full programm call:  %s\n", full_script); */

      execlp (full_script, full_script, (char *)0);
      free (full_script);
      exit(0);
    } 
  else 
    { /* processing for parent */
       int chars_processed = -5;
      read (from_speech[0], &chars_processed, 4);
      if (debug)
        printf ("response from textlines-to-speech:  '%d'\n", chars_processed);

      waitpid (pid_audio_stop, &status, 0);// called with no extra options
      if (debug)
        printf ("speech processing completed\n");
   }
#endif

}

/* create audio for the user
   @param text:  the string to be converted to audible speech
                 any string is valid, as long as it does not begin
                     eSpeakABBCCCDDDEEEEFFFF   

   @pre:  strlen (text) < MAX
 */
void eSpeakTalk (const char * text)
{ 
  /* send text through pipe to textlines-to-speech program */
  if (debug)
    printf ("starting to speak %s\n", text);
  
  /* create full array of MAX characters with message,
     copy input string
     pad array with spaces
     conclude array with a null character
     send working copy to textlines-to-speech one line at a time
  */

  char working_string [MAX];
  memset (working_string, ' ', MAX);
  
  strcpy (working_string, text);
     // curious that strncpy seems to allow overflow of an espeak buffer
     // hence pre-condition:  strlen (text) < MAX
  working_string[strlen(text)] = ' ';
  working_string[MAX-1] = 0;

  if (strlen (text) > MAX)
    {
      fprintf (stderr, "Warning:  eSpeakTalk text truncated to %d characters\n",
               MAX);
      working_string[MAX-1] = 0;
    }

  char *token = strtok (working_string, "\n");

  while (token != NULL)
    {
      // create full array of MAX characters with message,
      // followed by spaces and concluded with a null character
      char text_phrase [MAX];
      memset (text_phrase, ' ', MAX);
      strcpy (text_phrase, token);
      text_phrase[strlen(text_phrase)] = ' ';
      text_phrase[MAX-1] = 0;
      
      write (to_speech[1], token, strlen(token));
      write (to_speech[1], "\n", 1);
      if (debug)
        printf ("done sending %s\n", token);

      int chars_processed = -5;
      read (from_speech[0], &chars_processed, 4);
      if (debug)
        printf ("response from textlines-to-speech:  '%d'\n", chars_processed);
  
      token = strtok (NULL, "\n");
     }
  
  /* printf ("exiting eSpeakTalk %s\n", text); */
}

/* set quality of voice to female or male 
   @param gender:  "female" sets woman's voice
                   "male" sets man's voice
   if gender is neither "female" nor "male", voice unchanged
*/
void eSpeakSetGender (const char * gender)
{
  int i;
  int len = strlen(gender)+1;

  /* capitalize input string */
  char genderCaps[len];
  for (i = 0; i < strlen(gender); i++)
    genderCaps[i] = tolower(gender[i]);
  genderCaps [len-1] = 0;
  
  if (strcmp("female", genderCaps) == 0)
    {
      /* tell textlines-to-speech to switch to female voice */
      write (to_speech[1], set_voice_female, strlen(set_voice_female));
      write (to_speech[1], "\n", 1);
    }
  else if (strcmp("male", genderCaps) == 0)
    {
      /* tell textlines-to-speech to switch to male voice */
      write (to_speech[1], set_voice_male, strlen(set_voice_male));
      write (to_speech[1], "\n", 1);
    }
  int chars_processed;
  read (from_speech[0], &chars_processed, 4);
  if (debug)
    printf ("response from textlines-to-speech:  '%d'\n", chars_processed); 
}

