/* * Full_Narrator.c * * This example program sends a string of phonetic text to the narrator * device and, while it is speaking, highlights, word-by-word, a * corresponding English string. In addition, mouth movements are drawn * in a separate window. * * Compile with SAS C 5.10 lc -b1 -cfistq -v -y -L * * Requires Kickstart V37 or greater. */ #include <exec/types.h> #include <exec/memory.h> #include <dos/dos.h> #include <intuition/intuition.h> #include <ctype.h> #include <exec/exec.h> #include <fcntl.h> #include <devices/narrator.h> #include <clib/exec_protos.h> #include <clib/alib_protos.h> #include <clib/intuition_protos.h> #include <clib/graphics_protos.h> #include <clib/dos_protos.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #ifdef LATTICE int CXBRK(void) { return(0); } /* Disable SAS CTRL/C handling */ int chkabort(void) { return(0); } /* really */ #endif /* * Due to an omission, the sync field defines were not included in older * versions of the narrator device include files. So, if they haven't * already been defined, do so now. */ #ifndef NDF_READMOUTH /* Already defined ? */ #define NDF_READMOUTH 0x01 /* No, define here */ #define NDF_READWORD 0x02 #define NDF_READSYL 0x04 #endif #define PEN3 3 /* Drawing pens */ #define PEN2 2 #define PEN1 1 #define PEN0 0 BOOL FromCLI = TRUE; BYTE chans[4] = {3, 5, 10, 12}; LONG EyesLeft; /* Left edge of left eye */ LONG EyesTop; /* Top of eyes box */ LONG EyesBottom; /* Bottom of eyes box */ LONG YMouthCenter; /* Pixels from top edge */ LONG XMouthCenter; /* Pixels from left edge */ LONG LipWidth, LipHeight; /* Width and height of mouth */ struct TextAttr MyFont = {"topaz.font", TOPAZ_SIXTY, FS_NORMAL, FPF_ROMFONT,}; struct IntuitionBase *IntuitionBase = NULL; struct GfxBase *GfxBase = NULL; struct MsgPort *VoicePort = NULL; struct MsgPort *MouthPort = NULL; struct narrator_rb *VoiceIO = NULL; struct mouth_rb *MouthIO = NULL; struct IntuiText HighLight; struct NewWindow NewWindow; struct Window *TextWindow; struct Window *FaceWindow; struct RastPort *FaceRast; void main(int argc, char **argv) { LONG i; LONG sentence; LONG Offset; LONG CharsLeft; LONG ScreenPos; LONG WordLength; LONG LineNum; UBYTE *Tempptr; UBYTE *English; UBYTE *OldEnglish; UBYTE c; UBYTE *PhonPtr; /* Pointer to phonetic text */ LONG PhonSize; /* Size of phonetic text */ UBYTE *PhonStart[100]; /* Start of phonetic sentences */ LONG NumPhonStarts; /* Number of phonetic sentences */ UBYTE *EngPtr; /* Pointer to English text */ LONG EngSize; /* Size of English text */ UBYTE *EngStart[100]; /* Start of English sentences */ LONG NumEngStarts; /* Number of English sentences */ UBYTE *EngLine[24]; /* Start of line on screen */ LONG EngBytes[24]; /* Bytes per line on screen */ LONG NumEngLines; /* Number of lines on screen */ extern void Cleanup(UBYTE *errmsg); extern void ClearWindow(struct Window *TextWindow); extern void DrawFace(void); extern void UpdateFace(void); /* * (0) Note whether the program was started from the CLI or from * Workbench. */ if (argc == 0) FromCLI = FALSE; /* * (1) Setup the phonetic text to be spoken. If there are any non- * alphabetic characters in the text (such as NEWLINES or TABS) * replace them with spaces. Then break up the text into sentences, * storing the start of each sentence in PhonStart array elements. */ PhonPtr = "KAA1RDIYOWMAYAA5PAXTHIY. AY /HAED NEH1VER /HER4D AXV IHT " "BIXFOH5R, BAHT DHEH5R IHT WAHZ - LIH4STIXD AEZ (DHAX FOH5RM " "AXV /HAA5RT DIHZIY5Z) DHAET FEH4LD (NAAT WAH5N OHR TUW5) - " "BAHT (AO7L THRIY5 AXV DHAX AA5RTAXFIHSHUL /HAA5RTQ " "RIXSIH5PIYINTS). (AH LIH5TUL RIXSER5CH) PROHDUW5ST (SAHM " "IH5NTRIHSTIHNX RIXZAH5LTS). AHKOH5RDIHNX TUW (AEN AA5RTIHKUL " "IHN DHAX NOWVEH5MBER EY2THQX NAY5NTIYNEYTIYFOH1R NUW IY5NXGLIND " "JER5NUL AXV MEH5DIXSIN), (SIH5GEREHT SMOW5KIHNX) KAO4ZIHZ " "(DHIHS LIY5THUL DIHZIY5Z) DHAET WIY4KINZ (DHAX /HAA5RTS " "PAH4MPIHNX PAW2ER). WAYL (DHIY IHGZAE5KT MEH5KINIXZUM) IHZ " "NAAT KLIY5R, DAA5KTER AA5RTHER JEY2 /HAARTS SPEH5KYULEYTIHD " "DHAET NIH4KAXTIY2N- OHR KAA5RBIN MUNAA5KSAYD IHN DHAX SMOW5K- " "SAH5M/HAW1 POY4ZINZ DHAX /HAA5RT, AEND LIY4DZ TUW (/HAA5RT " "FEY5LYER)."; PhonSize = strlen(PhonPtr); NumPhonStarts = 0; PhonStart[NumPhonStarts++] = PhonPtr; for (i = 0; i < PhonSize; ++i) { if (isspace((int)(c = *PhonPtr++))) *(PhonPtr-1) = ' '; if ((c == '.') || (c == '?')) { *PhonPtr = '\0'; PhonStart[NumPhonStarts++] = ++PhonPtr; } } /* * (2) Create the English text corresponding to the phonetic text above. * As before, insure that there are no TABS or NEWLINES in the text. * Break the text up into sentences and store the start of each * sentence in EngStart array elements. */ EngPtr = "Cardiomyopathy. I had never heard of it before, but there it was " "listed as the form of heart disease that felled not one or two but " "all three of the artificial heart recipients. A little research " "produced some interesting results. According to an article in the " "November 8, 1984, New England Journal of Medicine, cigarette smoking " "causes this lethal disease that weakens the heart's pumping power. " "While the exact mechanism is not clear, Doctor Arthur J Hartz " "speculated that nicotine or carbon monoxide in the smoke somehow " "poisons the heart and leads to heart failure."; EngSize = strlen(EngPtr); NumEngStarts = 0; EngStart[NumEngStarts++] = EngPtr; for (i = 0; i < EngSize; ++i) { if (isspace((int)(c = *EngPtr++))) *(EngPtr-1) = ' '; if ((c == '.') || (c == '?')) { *EngPtr = '\0'; EngStart[NumEngStarts++] = ++EngPtr; } } /* * (3) Open Intuition and Graphics libraries. */ if (!(IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",0))) Cleanup("can't open intuition"); if ((GfxBase=(struct GfxBase *)OpenLibrary("graphics.library", 0)) == NULL) Cleanup("can't open graphics"); /* * (4) Setup the NewWindow structure for the text display and * open the text window. */ NewWindow.LeftEdge = 20; NewWindow.TopEdge = 100; NewWindow.Width = 600; NewWindow.Height = 80; NewWindow.DetailPen = 0; NewWindow.BlockPen = 1; NewWindow.Title = " Narrator Demo "; NewWindow.Flags = SMART_REFRESH | ACTIVATE | WINDOWDEPTH | WINDOWDRAG; NewWindow.IDCMPFlags = NULL; NewWindow.Type = WBENCHSCREEN; NewWindow.FirstGadget = NULL; NewWindow.CheckMark = NULL; NewWindow.Screen = NULL; NewWindow.BitMap = NULL; NewWindow.MinWidth = 600; NewWindow.MinHeight = 80; NewWindow.MaxWidth = 600; NewWindow.MaxHeight = 80; if ((TextWindow = (struct Window *)OpenWindow(&NewWindow)) == NULL) Cleanup("Text window could not be opened"); /* * (4) Setup the NewWindow structure for the face display, open the * window, cache the RastPort pointer, and draw the initial face. */ NewWindow.LeftEdge = 20; NewWindow.TopEdge = 12; NewWindow.Width = 120; NewWindow.Height = 80; NewWindow.DetailPen = 0; NewWindow.BlockPen = 1; NewWindow.Title = " Face "; NewWindow.Flags = SMART_REFRESH | WINDOWDEPTH | WINDOWDRAG; NewWindow.IDCMPFlags = NULL; NewWindow.Type = WBENCHSCREEN; NewWindow.FirstGadget = NULL; NewWindow.CheckMark = NULL; NewWindow.Screen = NULL; NewWindow.BitMap = NULL; NewWindow.MinWidth = 120; NewWindow.MinHeight = 80; NewWindow.MaxWidth = 120; NewWindow.MaxHeight = 80; if ((FaceWindow = (struct Window *)OpenWindow(&NewWindow)) == NULL) Cleanup("Face window could not be opened"); FaceRast = FaceWindow->RPort; DrawFace(); /* * (5) Create read and write msg ports. */ if ((MouthPort = CreatePort(NULL,0)) == NULL) Cleanup("Can't get read port"); if ((VoicePort = CreatePort(NULL,0)) == NULL) Cleanup("Can't get write port"); /* * (6) Create read and write I/O request blocks. */ if (!(MouthIO = (struct mouth_rb *) CreateExtIO(MouthPort,sizeof(struct mouth_rb)))) Cleanup("Can't get read IORB"); if (!(VoiceIO = (struct narrator_rb *) CreateExtIO(VoicePort,sizeof(struct narrator_rb)))) Cleanup("Can't get write IORB"); /* * (7) Set up the write I/O request block and open the device. */ VoiceIO->ch_masks = &chans[0]; VoiceIO->nm_masks = sizeof(chans); VoiceIO->message.io_Command = CMD_WRITE; VoiceIO->flags = NDF_NEWIORB; if (OpenDevice("narrator.device", 0, VoiceIO, 0) != NULL) Cleanup("OpenDevice failed"); /* * (8) Set up the read I/O request block. */ MouthIO->voice.message.io_Device = VoiceIO->message.io_Device; MouthIO->voice.message.io_Unit = VoiceIO->message.io_Unit; MouthIO->voice.message.io_Message.mn_ReplyPort = MouthPort; MouthIO->voice.message.io_Command = CMD_READ; /* * (9) Initialize highlighting IntuiText structure. */ HighLight.FrontPen = 1; HighLight.BackPen = 0; HighLight.DrawMode = JAM1; HighLight.ITextFont = &MyFont; HighLight.NextText = NULL; /* * (10) For each sentence, put up the English text in BLACK. As * Narrator says each word, highlight that word in BLUE. Also * continuously draw mouth shapes as Narrator speaks. */ for (sentence = 0; sentence < NumPhonStarts; ++sentence) { /* * (11) Begin by breaking the English sentence up into lines of * text in the window. EngLine is an array containing a * pointer to the start of each English text line. */ English = EngStart[sentence] + strspn((UBYTE *)EngStart[sentence], " "); NumEngLines = 0; EngLine[NumEngLines++] = English; CharsLeft = strlen(English); while (CharsLeft > 51) { for (Offset = 51; *(English+Offset) != ' '; --Offset) ; EngBytes[NumEngLines-1] = Offset; English += Offset + 1; *(English-1) = '\0'; EngLine[NumEngLines++] = English; CharsLeft -= Offset + 1; } EngBytes[NumEngLines-1] = CharsLeft; /* * (12) Clear the window and draw in the unhighlighted English text. */ ClearWindow(TextWindow); HighLight.FrontPen = 1; HighLight.LeftEdge = 10; HighLight.TopEdge = 20; for (i = 0; i < NumEngLines; ++i) { HighLight.IText = EngLine[i]; PrintIText(TextWindow->RPort, &HighLight, 0, 0); HighLight.TopEdge += 10; } HighLight.TopEdge = 20; HighLight.FrontPen = 3; HighLight.IText = EngLine[0]; /* * (13) Set up the write request with the address and length of * the phonetic text to be spoken. Also tell device to * generate mouth shape changes and word sync events. */ VoiceIO->message.io_Data = PhonStart[sentence]; VoiceIO->message.io_Length = strlen(VoiceIO->message.io_Data); VoiceIO->flags = NDF_NEWIORB | NDF_WORDSYNC; VoiceIO->mouths = 1; /* * (14) Send the write request to the device. This is an * asynchronous write, the device will return immediately. */ SendIO(VoiceIO); /* * (15) Initialize some variables. */ ScreenPos = 0; LineNum = 0; English = EngLine[LineNum]; OldEnglish = English; MouthIO->voice.message.io_Error = 0; /* * (16) Issue synchronous read requests. For each request we * check the sync field to see if the read returned a mouth * shape change, a start of word sync event, or both. We * continue issuing read requests until we get a return code * of ND_NoWrite, which indicates that the write has finished. */ for (DoIO(MouthIO);MouthIO->voice.message.io_Error != ND_NoWrite;DoIO(MouthIO)) { /* * (17) If bit 1 of the sync field is on, this is a start * of word sync event. In that case we highlight the * next word. */ if (MouthIO->sync & NDF_READWORD) { if ((Tempptr = strchr(English, ' ')) != NULL) { English = Tempptr + 1; *(English-1) = '\0'; } PrintIText(TextWindow->RPort, &HighLight, 0, 0); WordLength = strlen(OldEnglish) + 1; HighLight.IText = English; OldEnglish = English; ScreenPos += WordLength; if (ScreenPos >= EngBytes[LineNum]) { HighLight.LeftEdge = 10; HighLight.TopEdge += 10; ScreenPos = 0; English = OldEnglish = EngLine[++LineNum]; HighLight.IText = English; } else HighLight.LeftEdge += 10*WordLength; } /* * (18) If bit 0 of the sync field is on, this is a mouth * shape change event. In that case we update the face. */ if (MouthIO->sync & NDF_READMOUTH) UpdateFace(); } /* * (19) The write has finished (return code from last read equals * ND_NoWrite). We must wait on the write I/O request to * remove it from the message port. */ WaitIO(VoiceIO); } /* * (20) Program completed, cleanup and return. */ Cleanup("Normal completion"); } void Cleanup(UBYTE *errmsg) { /* * (1) Cleanup and go away. This routine does not return but EXITs. * Everything it does is pretty self explanatory. */ if (FromCLI) printf("%s\n\r", errmsg); if (TextWindow) CloseWindow(TextWindow); if (FaceWindow) CloseWindow(FaceWindow); if (VoiceIO && VoiceIO->message.io_Device) CloseDevice(VoiceIO); if (VoiceIO) DeleteExtIO(VoiceIO); if (VoicePort) DeletePort(VoicePort); if (MouthIO) DeleteExtIO(MouthIO); if (MouthPort) DeletePort(MouthPort); if (GfxBase) CloseLibrary(GfxBase); if (IntuitionBase) CloseLibrary(IntuitionBase); exit(RETURN_OK); } void ClearWindow(struct Window *TextWindow) { LONG OldPen; /* * (1) Clears a window. */ OldPen = (LONG)TextWindow->RPort->FgPen; SetAPen(TextWindow->RPort, 0); SetDrMd(TextWindow->RPort, JAM1); RectFill(TextWindow->RPort, 3, 12, TextWindow->Width-3, TextWindow->Height-2); SetAPen(TextWindow->RPort, OldPen); } void DrawFace() { /* * (1) Draws the initial face. The variables defined here are used in * UpdateFace() to redraw the mouth shape. */ EyesLeft = 15; EyesTop = 20; EyesBottom = 35; XMouthCenter = FaceWindow->Width >> 1; YMouthCenter = FaceWindow->Height - 25; SetAPen(FaceWindow->RPort, PEN1); RectFill(FaceWindow->RPort, 3, 10, FaceWindow->Width-3, FaceWindow->Height-2); SetAPen(FaceWindow->RPort, PEN0); RectFill(FaceWindow->RPort, EyesLeft, EyesTop, EyesLeft+25, EyesTop+15); RectFill(FaceWindow->RPort, EyesLeft+65, EyesTop, EyesLeft+90, EyesTop+15); SetAPen(FaceWindow->RPort, PEN3); Move(FaceWindow->RPort, XMouthCenter-(FaceWindow->Width >> 3), YMouthCenter); Draw(FaceWindow->RPort, XMouthCenter+(FaceWindow->Width >> 3), YMouthCenter); } void UpdateFace() { /* * (1) Redraws mouth shape in response to a mouth shape change message * from the device. Its all pretty self explanatory. */ WaitBOVP(&FaceWindow->WScreen->ViewPort); SetAPen(FaceRast, PEN1); RectFill(FaceRast, 3, EyesBottom, FaceWindow->Width-3, FaceWindow->Height-2); LipWidth = MouthIO->width*3; LipHeight = MouthIO->height*2/3; SetAPen(FaceRast, PEN3); Move(FaceRast, XMouthCenter - LipWidth, YMouthCenter); Draw(FaceRast, XMouthCenter , YMouthCenter - LipHeight); Draw(FaceRast, XMouthCenter + LipWidth, YMouthCenter); Draw(FaceRast, XMouthCenter, YMouthCenter + LipHeight); Draw(FaceRast, XMouthCenter - LipWidth, YMouthCenter); }