/* * * * * * * * * * * * * * * * * * * * * * 
 *  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 <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 = "/usr/local/bin/speak";

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

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

/* file handle for pipe to speak program */
FILE * speak_file_pointer = NULL;

int debug = 0;  /* debugging flag --- 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)
{
   /* 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 (debug)
      printf ("full speak command-line call:  %s\n", command_line);

    /* set up speak program to receive text through pipe */
    speak_file_pointer = popen (command_line, "w");
    if (speak_file_pointer == NULL)
       {
          perror ("error in starting/restarting speak program");
          exit (1);
       }

    /* clean up */
    free (command_line);
}

/* set up the local environment to utilize the eSpeak package 
 */
void eSpeakConnect ()
{  
   if (speak_file_pointer != NULL)
      {
          perror ("rogram 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);

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

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

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

   /* close connection */
   pclose (speak_file_pointer);
   speak_file_pointer = NULL;

   if (debug)
     {
      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 (debug)
      printf ("starting to speak %s\n", text);
  
   /* 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)
      {
	fprintf (speak_file_pointer, "%s\n", token);
         if (debug)
           printf ("done sending %s\n", token);

         token = strtok (NULL, "\n");
      }

   /* instruct OS to act on eSpeak file output without delay */
   fflush(speak_file_pointer);
}

/* 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 desired voice is the same as current voice, nothing to do */
   if (strcmp (current_voice, genderCaps) == 0)
      {
         if (debug)
            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 (speak_file_pointer != NULL)
            pclose (speak_file_pointer);

         /* 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 (speak_file_pointer != NULL)
            pclose (speak_file_pointer);

         /* 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);
      }
}

