
/*
 * TalkingHead.c
 *
 * Author: Eric Lundquist
 *
 * Description: Open a serial port connection to the DecTalk Host computer port, set phoneme logging on.
 * Open a serial port connection to the Mini SSC II servo controller.  Read phonetic strings from the
 * DecTalk and command the babbling head servos to move lips in sync with the speech.
 *
 * History:
 * January 10, 2003 - Initial creation...Lundquist
 *
 */
 
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <termios.h>
 #include <errno.h>
 #include <pthread.h>
 
void *dectalkReader (void *);
void *miniSSCWriter (void *);
void *servoPositioner (void *);
char getnextchar();
char nextchar();
void snooze(int);
void mouth(int);

 /* Some Global constants */
 #define DTHOSTDEVICE  "/dev/ttyS4"    /* Port that DecTalk Host Port is tied to */
 #define HEADDEVICE    "/dev/ttyS5"    /* Port that the Mini SSC II is tied to */
 #define BAUDRATE      B9600           /* DecTalk and SSC both use 9600 8N1 */
 #define _POSIX_SOURCE 1    /* POSIX Compliant Source */
 #define RINGBUFFERSIZE 2048           /* Size of circular ring buffer */
 #define SETUPSTRING   "SETUP>"        /* Expected response from DecTalk */
 #define SYLABLETIME    50            /* ms per sylable at 180 words per minute */
 #define COMMATIME      160            /* comma pause */
 #define SENTENCETIME   640            /* Pause at end of sentence */
 
 /* Some Global variables */
 int inpfd;    /* File Descriptor for input */
 int outfd;    /* File Descriptor for output */
 char ringBuffer[RINGBUFFERSIZE];    /* Global circular ring buffer */
 int ringStart = 0;        /* Ring buffer start index (updated by output) */
 int ringEnd = 0;          /* Ring buffer end index (updated by input) */
 /* Viseme Table */
 /* Servo layout
  * 0 - jaw
  * 1 - top lip center left
  * 2 - top lip center right
  * 3 - top lip far right
  * 4 - lower lip far left
  * 5 - lower lip center left
  * 6 - lower lip center right
  * 7 - lower lip far right
  */
 int visemes [13][8] = {
     { 85, 125, 175, 125, 184, 105, 185, 75},    /* 0 Mouth closed m, p, b ' ' */
     { 125, 145, 155, 75, 184, 135, 125, 25},    /* 1 d, t, z, n, en, r, rr, s, sh */
     { 86, 185, 115, 1, 254, 185, 75, 1},    /* 2 l, el */
     { 95, 175, 125, 41, 224, 165, 115, 51},    /* 3 g, k, ax, hx, yx */
     { 94, 145, 155, 2, 254, 145, 145, 2},    /* 4 ix, sh, jh, ch */
     { 135, 165, 135, 61, 194, 105, 175, 51},    /* 5 ih, iy, ay */
     { 105, 175, 125, 3, 214, 145, 115, 21},    /* 6 eh, ey, ah */
     { 107, 225, 65, 81, 194, 175, 65, 91},    /* 7 ow */
     { 137, 134, 145, 82, 184, 125, 145, 81},    /* 8 ae, ao, aa */
     { 87, 194, 95, 1, 234, 155, 105, 31},    /* 9 w. uw */
     { 115, 254, 45, 2, 184, 175, 65, 61},    /* 10 uh */
     { 75, 154, 125, 31, 254, 174, 35, 1},    /* 11 v, f */
     { 97, 194, 105, 71, 184, 154, 95, 61}};   /* 12 dh, th */
     
 int desired[8] = {127, 127, 127, 127, 127, 127, 127, 127}; /* Destination servo pos */
 int currpos[8] = {0, 0, 0, 0, 0, 0, 0, 0};    /* current servo position */
 
 main() {
     char str[256];
     struct termios portOptions;
     pthread_t readerId;
     pthread_t writerId;
     pthread_t servoId;
     int status;
     
     printf("TalkingHead v1.0\n");
     
     /* Open DecTalk Host Port */
     #ifdef DEBUG
         printf("Opening DecTalk Host Port\n");
     #endif
     inpfd = open(DTHOSTDEVICE, O_RDWR | O_NOCTTY);
     if (inpfd < 0) {
         perror("Unable to open DecTalk Host Port");
     }
     #ifdef DEBUG
         printf("DecTalk Host Port Opened\n");
     #endif
     tcgetattr(inpfd, &portOptions);    /* Get port option structure */
     bzero(&portOptions, sizeof(portOptions));
     portOptions.c_cflag = B9600 | CS8 | CLOCAL | CREAD;
     portOptions.c_iflag = IGNPAR | IXON | IXOFF | IXANY;
     portOptions.c_oflag = 0;
     portOptions.c_lflag = 0;
     portOptions.c_cc[VTIME] = 2;
     portOptions.c_cc[VMIN] = 1;
     portOptions.c_cc[VSTART] = 021;
     portOptions.c_cc[VSTOP] = 023;
     tcflush(inpfd, TCIFLUSH);    /* clear anything pending */
     tcsetattr(inpfd, TCSANOW, &portOptions);    /* Set up the port */
     #ifdef DEBUG
         printf("Set DecTalk Host Port Options\n");
     #endif
     status = pthread_create(&readerId, NULL, dectalkReader, NULL);
     if (status != 0) {
         perror ("dectalkReader pthread_create failed");
         exit(0);
     }
     
     /* Open Mini SSC Port */
     #ifdef DEBUG
         printf("Opening Mini SSC Port\n");
     #endif
     outfd = open(HEADDEVICE, O_RDWR | O_NOCTTY);
     if (outfd < 0) {
         perror("Unable to open Mini SSC Port");
     }
     #ifdef DEBUG
         printf("Mini SSC Port Opened\n");
     #endif
     tcflush(outfd, TCIFLUSH);    /* clear anything pending */
     tcsetattr(outfd, TCSANOW, &portOptions);    /* Set up the port */
     #ifdef DEBUG
         printf("Set Mini SSC Port Options\n");
     #endif
     status = pthread_create(&writerId, NULL, miniSSCWriter, NULL);
     if (status != 0) {
         perror ("miniSSCWriter pthread_create failed");
         exit(0);
     }
     
     /* Fire up the servo positioner thread */
     status = pthread_create(&servoId, NULL, servoPositioner, NULL);
     if (status != 0) {
         perror ("servoPositioner pthread_create failed");
         exit(0);
     }
     
     pthread_join(readerId, NULL);
     pthread_join(writerId, NULL);
     pthread_join(servoId, NULL);
        
 }
 
 void *dectalkReader(void *arg) {
     char inLine[256];
     int inLength;
     int inOffset;
     int outLength;
     int i;
     
     #ifdef DEBUG
         printf("dectalkReader thread started\n");
     #endif
     tcsendbreak(inpfd, 0);
     #ifdef DEBUG
         printf("sent a BREAK\n");
     #endif
     inLength = 0;
     inOffset = 0;
     while (inOffset < strlen(SETUPSTRING)) {
         inLength = read(inpfd, &inLine[inOffset], 255);
         inOffset += inLength;
     }
     #ifdef DEBUG
         printf("Received from DecTalk: %s\n", inLine);
     #endif	
     outLength = write(inpfd, "SET LOG PHONEME ON\r", 19);
     sleep(1);    /* ZZzzzzz.... */
     outLength = write(inpfd, "EXIT\r", 5);
     sleep(1);
     tcflush(inpfd, TCIFLUSH);    /* clear anything pending */
     #ifdef DEBUG
         printf("Listening for DecTalk Host Phonemes\n");
     #endif
     
     /* Main input loop */
     while (1) {
         inLength = read(inpfd, inLine, 255);
         #ifdef DEBUG
             printf("received %d characters\n", inLength);
	 #endif
         for (i=0; i<inLength; i++) {
             ringBuffer[ringEnd] = inLine[i];
             ringEnd++;
             if (ringEnd >= RINGBUFFERSIZE) {
                 ringEnd = 0;
             }
         }
         #ifdef DEBUG
             printf("start=%d end=%d\n", ringStart, ringEnd);
         #endif
     }    
 }
 
 void *miniSSCWriter (void *arg) {
     int duration;
     unsigned char ch;
     
     #ifdef DEBUG
         printf("miniSSCWriter thread started\n");
     #endif
 
     /* Main output processing loop */
     while (1) {
         if (ringStart == ringEnd) {
             /* Nothing to do */
             snooze(1);    /* Sleep for a millisecond */
         } else {
             /* There are characters in the buffer */
             ch = getnextchar();
             #ifdef DEBUG
	         printf("Processing Character %c\n", ch);
             #endif

             if ((ch == '!') || (ch == '.') || (ch == '?')) {    /* !, ., ? */
                 mouth(0);    /* Close mouth */
                 snooze (SENTENCETIME);    /* end of sentence delay */

             } else if (ch == ',') {
		 mouth(0);
                 snooze (COMMATIME);    /* comma pause */

             } else if ((ch == ' ') || (ch == 'b') || (ch == 'p') || (ch == 'm') ||    /* ' ', b, p, m */
                 ((ch == 'e') && (nextchar() == 'm'))) {
                 if (ch == 'e') {
                     getnextchar();
                 }
                 mouth(0);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

             } else if (((ch == 'd') && (nextchar() != 'h')) ||    /* d, t, z, s, sh, r, rr, n, en */
                 ((ch == 't') && (nextchar() != 'h')) ||
                  (ch == 's') ||
                 ((ch == 'z') && (nextchar() != 'h')) ||
                 ((ch == 'n') && (nextchar() != 'x')) ||
                 ((ch == 'e') && (nextchar() == 'n')) ||
                 (ch == 'r')) {
                 if (((ch == 'r') && (nextchar() == 'r')) ||
                     ((ch == 's') && (nextchar() == 'h')) ||
                     (ch == 'e')) {
                     /* pull the next r */
                     getnextchar();
                 }
                 mouth(1);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

             } else if ((ch == 'l') ||                        /* l, el */
                 ((ch == 'e') && (nextchar() == 'l'))) {
                 if (ch == 'e') {
                     getnextchar();
                 }
                 mouth(2);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

             } else if ((ch == 'k') || (ch == 'g') ||    /* yx, g, k, hx, ax */
                 ((ch == 'y') && (nextchar() != 'u')) ||
                 ((ch == 'a') && (nextchar() == 'x')) ||
                 ((ch == 'h') && (nextchar() == 'x'))) {
                 if (((ch == 'a') && (nextchar() == 'x')) ||
                     ((ch == 'h') && (nextchar() == 'x')) ||
                     ((ch == 'y') && (nextchar() == 'x'))) {
                     getnextchar();    /* burn second char */
                 }
                 mouth(3);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

             } else if (((ch == 'i') && (nextchar() == 'x')) ||            /* ix, sh, jh, ch */
                 ((ch == 's') && (nextchar() == 'h')) ||
                 ((ch == 'j') && (nextchar() == 'h')) ||
                 ((ch == 'c') && (nextchar() == 'h'))) {
                 getnextchar();    /* burn second char */
                 mouth(4);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

             } else if (((ch == 'i') && (nextchar() == 'h')) ||     /* ih, iy, ay */
                 ((ch == 'i') && (nextchar() == 'y')) ||
                 ((ch == 'a') && (nextchar() == 'y'))) {
                 getnextchar();   /* burn next char */
                 mouth(5);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

            } else if (((ch == 'e') && (nextchar() == 'h')) ||     /* eh, ey, ah */
                 ((ch == 'e') && (nextchar() == 'y')) ||
                 ((ch == 'a') && (nextchar() == 'h'))) {
                 getnextchar();         /* burn second character */
                 mouth(6);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

            } else if ((ch == 'o') && (nextchar() == 'w')) {       /* ow */
                 getnextchar();
                 mouth(7);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

            } else if (((ch == 'a') && (nextchar() == 'e')) ||            /* ae, ao, aa */
                 ((ch == 'a') && (nextchar() == 'o')) ||
                 ((ch == 'a') && (nextchar() == 'a'))) {
                 getnextchar();
                 mouth(8);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

            } else if ((ch == 'w') ||                             /* w, uw */
                 ((ch == 'u') && (nextchar() == 'w'))) {
                 if (ch == 'u') {
                     getnextchar();
                 }
                 mouth(9);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

            } else if ((ch == 'u') && (nextchar() == 'h')) {       /* uh */
                 getnextchar();
                 mouth(10);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

            } else if ((ch == 'v') || (ch == 'f')) {                /* v, f */
                 mouth(11);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(duration);

            } else if (((ch == 'd') && (nextchar() == 'h')) ||       /* dh, th */
                 ((ch == 't') && (nextchar() == 'h'))) {
                 getnextchar();    /* burn second char */
                 mouth(12);
                 if (nextchar() == '<') {
                     duration = getduration();
                 } else {
                     duration = SYLABLETIME;
                 }
                 snooze(SYLABLETIME);
            } else {
		 #ifdef DEBUG
		     printf("skipping character %c\n", ch);
		 #endif
		 mouth(0);
            }
	 }    
     }    
}
 
char getnextchar() {
     char ch;
     
     ch = ringBuffer[ringStart++];
     if (ringStart >= RINGBUFFERSIZE) {
         ringStart = 0;
     }
     return ch;
 }
 
 int getduration() {
     char str[64];
     char ch;
     int duration, pitch;
     int status;
     int i;
     
     i = 0;
     ch = getnextchar();    /* should be a '<' */
     if (ch != '<') {
         printf("getduration expected < \n");
         return 0;
     }
     while ((ch != '>') && (i<63)) {
         str[i++] = ch;
         ch = getnextchar();
     }
     str[i++] = ch;
     str[i++] = 0;    /* terminate local string */
     if (i>20) {    /* something is rotton here */
         printf("getduration couldn't find > in %s\n", str);
     }
     
     status = sscanf(str, "<%d,%d>", &duration, &pitch);    /* let scanf work its magic */
     if (status < 1) {
         printf("getduration couldn't find duration in %s\n", str);
     }
     return duration;
 }    
    
 char nextchar() {
     return ringBuffer[ringStart];
 }
 
 void *servoPositioner (void *arg) {
     int i;
     unsigned char servoCmd[3];
     struct timeval Timeout;
          
     #ifdef DEBUG
         printf("servoPositioner Thread started\n");
     #endif
     while (1) {
         for (i=0; i<8; i++) {
             if (desired[i] != currpos[i]) {
                 if (currpos[i] < desired[i]) {
                     currpos[i]++;
                 } else {
                     currpos[i]--;
                 }
                 currpos[i] = (desired[i] + currpos[i]) / 2;
                 servoCmd[0] = 0xff;
                 servoCmd[1] = i;
                 servoCmd[2] = currpos[i];
                 write(outfd, servoCmd, 3);
             }
         }
 
         Timeout.tv_usec = 20;
         Timeout.tv_sec = 0;
         select(0, 0, NULL, NULL, &Timeout);
     }         
 }
 
 void mouth(int position) {
     int i;
     
#ifdef DEBUG
     printf("mouth position %d\n", position);
#endif

     for (i=0; i<8; i++) {
         desired[i] = visemes[position][i];
     }
 }

 void snooze(int msec) {
 
     struct timeval Timeout;

#ifdef DEBUG
     if (msec != 1)
         printf("snoozing %d msec\n", msec);
#endif     
     Timeout.tv_usec = msec * 1000;
     Timeout.tv_sec = 0;
     select(0, 0, NULL, NULL, &Timeout);
}
