/* * * * * * * * * * * * * * * * * * * * * * 
 *  eSpeakPackage.c  -- Implementation of the eSpeakPackage interface within C
 *                      for Macintosh 
 *  
 *  author of this C-callable package:  Henry M. Walker
 *     last revised for Linux:  October 3, 2013
 *     last revised for Macintosh:  February 21, 2015
 *
 *  utilizes open source eSpeak package, with its speak program
 *     eSpeak's author:  Jonathan Duddington
 *     eSpeak source:  http://espeak.sourceforge.net/
 *
 * This program and all MyroC software is licensed under the Creative Commons
 * Attribution-Noncommercial-Share Alike 4.0 International License.
 * Details may be found at http://creativecommons.org/licenses/by-nc-sa/4.0/
 *
 * * * * * * * * * * * * * * * * * * * * * */

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

#include "eSpeakPackage.h"
#include <winsock2.h>        /* needed for pipe/socket to eSpeak process */

#include <ctype.h>           /* needed for tolower character function */
#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 <stdio.h>
#include <time.h>            /* needed for random voice selection */
 
/* * * * * * * * * * * string constants * * * * * * * * * * */
/* full path for the speak program */
char * speak_program = "C:/espeak/command_line/espeak.exe";

/* speak options for female and male voice characteristics */
/* parameter notes
                                  female      male
   -s:  speed in words-per-minute  120        120
   -a:  amplitude (volume)          70         70
   -p:  pitch                      120         50
   -v:  voice (language)         en-us+f4   en-us+m2
*/
char * female_options = "-s140 -ven-us+f4 -a70 -p85";
char * male_options   = "-s140 -ven-us+m2 -a70 -p50 ";

/* * * * * * * * * * global variables * * * * * * * * * * */
/* socket initialization variables---define if not already available from MyroC */
#ifndef wVersionRequested
   WORD wVersionRequested = 0; // initialization to show no socket initialization yet
   WSADATA wsaData;
#endif 

/* current voice option (female or male) */
char * current_voice = "";

/* end-of-transmission character */
char exitChar = 4;

/* pipe handle for pipe to eSpeak program */
HANDLE to_eSpeak_proc_Wr = NULL;

/* process variable for eSpeakProcess */
PROCESS_INFORMATION procInfo;

/* variable for storing whether function returns "success" (true) or not */
BOOL bSuccess = FALSE; 

/* flag to record whether eSpeak 0rocess created */
int eSpeakProcCreated = 0;  // 1 = eSpeak process already created; 0 no current process

/* flag to enable printing for code tracing */
int enableTracing = 0;  /* 0 for printing; 1 suppress printing */

/* establish a connection to the speak program with the given command-line options 
   function returns handle of pipe that connects to speak */ 
void setupSpeakConnection (char * options) {  
   /* socket startup ---but only if this step not already performed */
   if (wVersionRequested == 0) {
      wVersionRequested = MAKEWORD(2,2);
      int err = WSAStartup(wVersionRequested, &wsaData);
      if (err != 0) {
         printf ("WSAStartup failed with error: %d\n", err); fflush(stdout);
         printf ("    error code:  %d\n", WSAGetLastError());
         exit (1);
      }
      //printf ("socket startup accomplished\n"); fflush(stdout);
   }

   /*  create pipe for communication with eSpeak process
       Main                                      eSpeak
      Process                Pipe               Process
      Handler                                   Handler
      to_eSpeak_proc_Wr ->->->->->->->->->->-> to_eSpeak_proc_Rd
   */
   /* set up pipe to communicate text to espeak process */
   HANDLE to_eSpeak_proc_Rd = NULL;
   to_eSpeak_proc_Wr = NULL;

   // set security attributes, including the bInheritHandle, so pipe handles are inherited
   SECURITY_ATTRIBUTES saAttr; 
   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
   saAttr.bInheritHandle = TRUE; 
   saAttr.lpSecurityDescriptor = NULL; 

   if (enableTracing) {
      printf ("creating pipe to eSpeak process\n");
   }

   // create a pipe for the image handler process's STDIN
   if ( ! CreatePipe(&to_eSpeak_proc_Rd, &to_eSpeak_proc_Wr, &saAttr, 0) ) {
      fprintf (stderr, "  error in creating pipe for eSpeak process\n");
      exit (1);
   }

   // write handle for STDIN is not inherited
   if ( ! SetHandleInformation(to_eSpeak_proc_Wr, HANDLE_FLAG_INHERIT, 0) ) {
     fprintf (stderr, "  error in setting stdin for eSpeak process\n");
     exit (1);
   }

  if (enableTracing)
      printf ("pipe created to eSpeak process\n");

   /* set up full terminal command to start speak 
      command will combine program name and options, 
         separated by a space, and terminated by a null */
   int char_length = strlen (speak_program) + strlen (options) + 2;
   char * command_line = malloc (sizeof(char) * char_length);
   strcpy (command_line, speak_program);
   strcat (command_line, " ");
   strcat (command_line, options);
   if (enableTracing) {
      printf ("full speak command-line call:  %s\n", command_line); fflush(stdout);
   }

   // Create a child process that uses the previously created pipes for STDIN and STDOUT.
   STARTUPINFO siStartInfo;
 
   // Set up members of the PROCESS_INFORMATION structure. 
 
   ZeroMemory( &procInfo, sizeof(PROCESS_INFORMATION) );
 
   // Set up members of the STARTUPINFO structure. 
   // This structure specifies the STDIN and STDOUT handles for redirection.
 
   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
   siStartInfo.cb = sizeof(STARTUPINFO); 
   //siStartInfo.hStdError = stderr;
   //siStartInfo.hStdOutput = stdout;
   siStartInfo.hStdInput = to_eSpeak_proc_Rd;
   siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
 
   // Create the child process. 
   bSuccess = CreateProcess(NULL, 
                 command_line,  // command line 
                 NULL,          // process security attributes 
                 NULL,          // primary thread security attributes 
                 TRUE,          // handles are inherited 
                 0,             // creation flags 
                 NULL,          // use parent's environment 
                 NULL,          // use parent's current directory 
                 &siStartInfo,  // STARTUPINFO pointer 
                 &procInfo);   // receives PROCESS_INFORMATION 
   
   // If an error occurs, exit the application. 
   if ( ! bSuccess ) {
      fprintf (stderr, "   error in creating eSpeak process\n");fflush(stderr);
      fprintf (stderr, "   error number:  %ld\n", GetLastError()); fflush(stderr);
      exit (1);
   }
   // Close handle to the child process and its primary thread.

   // Some applications might keep these handles to monitor the status
   // of the child process, for example. 

   CloseHandle(procInfo.hThread);
     
   // Close handle to the stdout pipe no longer needed by the child process.
   // If handle are not explicitly closed, there is no way to recognize that the child process has ended.
   CloseHandle(to_eSpeak_proc_Rd);

    /* clean up */
    free (command_line);

   /* record eSpeak process now running */
   eSpeakProcCreated = 1;

   if (enableTracing) {
      printf ("eSpeak handler process created\n"); fflush(stdout);
   }
}

/* set up the local environment to utilize the eSpeak package 
 */
void eSpeakConnect () {  
   if (eSpeakProcCreated) {
      perror ("program already connected to eSpeak program");
      perror ("new connection not allowed");
      return;
   }

   /* set voice randomly to female or male */
   srand (time ((time_t *) 0) ); // initialize random generator

   int i, rand_num;
   for (i = 0; i < 10; i++)
      rand_num = rand() % 10;

   char * option_string;
   if (rand_num < 5)
      {  /* select female voice */
         current_voice = "female";
         option_string = female_options;
   } else {  /* select male voice */
         current_voice = "male";
         option_string = male_options;
   }

   /* set up speak program */
   setupSpeakConnection (option_string);

   /* announce initialization, with gender information */
   char str [80];
   sprintf (str, "initialized with %s voice", current_voice);

   if (enableTracing) {
      printf ("%s\n", str); fflush(stdout);   
   }
   eSpeakTalk (str);

   /* connection established */
   if (enableTracing) {
      printf ("eSpeakConnect done\n"); fflush(stdout);
   }
 }

/* clean up the local environment when the eSpeak package is no longer needed
 */
void eSpeakDisconnect ()
{  
   /* check connection to speak program currently established */
   if (eSpeakProcCreated == 0)
      {
         perror ("program not connected to eSpeak program");
         perror ("disconnection not appropriate");
         return;
      }

   /* close connection */
   // send "end of transmission" character
   long unsigned int dwWritten;
   bSuccess = WriteFile (to_eSpeak_proc_Wr, &exitChar, 1, &dwWritten, NULL);
   if (! bSuccess || dwWritten == 0) {
      fprintf (stderr, "    error in sending text to eSpeak process\n");
      exit(1);
   }
   CloseHandle(to_eSpeak_proc_Wr);
   //TerminateProcess (procInfo.hProcess, 0);  // terminate with exit code 0
   eSpeakProcCreated = 0;

   if (enableTracing) {
      printf ("speech processing completed\n");
   }
}

/* create audio for the user
   @param text:  the string to be converted to audible speech
                    --- any string is valid
                 (for Linux compatibility, string should not begin with
                     eSpeakABBCCCDDDEEEEFFFF   
                 )
 */
void eSpeakTalk (const char * text)
{ 
   /* send text through pipe to speak program */
   if (enableTracing) {
      printf ("starting to speak %s\n", text);fflush(stdout);
   }
  
   /* copy string, so original string is not changed */
   char working_string [strlen(text)+1]; 
   strcpy (working_string, text);

   /*  send working copy to speak program one line at a time */
   char *token = strtok (working_string, "\n");

   while (token != NULL)
      {  BOOL bSuccess;
         long unsigned dwWritten;
         if (enableTracing) {
            printf ("starting to send %s\n", token);fflush(stdout);
         }
         if (strlen(token) > 0) {
            // send text
            bSuccess = WriteFile (to_eSpeak_proc_Wr, token, strlen(token), &dwWritten, NULL);
            if (! bSuccess || dwWritten == 0) {
               fprintf (stderr, "    error in sending text to eSpeak process\n");
               exit(1);
            }
            // send new line, as espeak program responds line at a time
            bSuccess = WriteFile (to_eSpeak_proc_Wr, "\n", 1, &dwWritten, NULL);
            if (! bSuccess || dwWritten == 0) {
               fprintf (stderr, "    error in sending text to eSpeak process\n");
               exit(1);
            }
         }
         if (enableTracing) {
            printf ("done sending %s\n", token); fflush(stdout);
         }

         token = strtok (NULL, "\n");
      }
 
   /* again, encourageOS to act on eSpeak test output without delay */
   //fflush(to_eSpeak_proc_Wr);
}

/* 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;
   long unsigned int dwWritten;

   /* capitalize input string */
   char genderCaps[len];
   for (i = 0; i < strlen(gender); i++)
       genderCaps[i] = tolower(gender[i]);
   genderCaps [len-1] = 0;

   /* if desired voice is the same as current voice, nothing to do */
   if (strcmp (current_voice, genderCaps) == 0)
      {
         if (enableTracing)
            printf ("voice already set as %s --- no voice change needed\n",
                    current_voice);
         return;
      }   

    /* new voice needed */
    if (strcmp("female", genderCaps) == 0)
      {
         /* cancel old speak program, if needed */
         if (eSpeakProcCreated) {
            // send "end of transmission" character
            bSuccess = WriteFile (to_eSpeak_proc_Wr, &exitChar, 1, &dwWritten, NULL);
            if (! bSuccess || dwWritten == 0) {
               fprintf (stderr, "    error in sending text to eSpeak process\n");
               exit(1);
            }

            CloseHandle(to_eSpeak_proc_Wr);
 //         //Suspend our execution until the child has terminated.
            WaitForSingleObject(procInfo.hProcess, INFINITE);

            //TerminateProcess (procInfo.hProcess, 0);  // terminate with exit code 0
            eSpeakProcCreated = 0;
         }

         if (enableTracing) {
            printf ("setting to female voice\n"); fflush(stdout);
         }

         /* set up and announce new speech program with correct gender */
         current_voice = "female";
         setupSpeakConnection (female_options);
         eSpeakTalk ("speech set to female voice");
      }
   else if (strcmp("male", genderCaps) == 0)
      {
         /* cancel old speak program, if needed */
         if (eSpeakProcCreated) {
            // send "end of transmission" character
            bSuccess = WriteFile (to_eSpeak_proc_Wr, &exitChar, 1, &dwWritten, NULL);
            if (! bSuccess || dwWritten == 0) {
               fprintf (stderr, "    error in sending text to eSpeak process\n");
               exit(1);
            }
            CloseHandle(to_eSpeak_proc_Wr);

 //         //Suspend our execution until the child has terminated.
            WaitForSingleObject(procInfo.hProcess, INFINITE);

           //TerminateProcess (procInfo.hProcess, 0);  // terminate with exit code 0
            eSpeakProcCreated = 0;
         }

         if (enableTracing) {
            printf ("setting to male voice\n"); fflush(stdout);
         }

        /* set up and announce new speech program with correct gender */
         current_voice = "male";
         setupSpeakConnection (male_options);
         eSpeakTalk ("speech set to male voice");    
      }
    else 
      {
         printf ("unrecognized gender voice:  %s\n", gender);   
      }

   if (enableTracing) {
      printf ("done set gender function\n"); fflush(stdout);
   }
}

