/* * NAME * talk - speak text from the command line or a file * * SYNOPSIS * talk [-v] [-] [-f file] [string...] * * DESCRIPTION * this program uses speak.tos to speak text from the command line, * one or more files, or stdin. it is a front end to the old speak.tos * program. * * NOTES * talk uses two entry points into speak.tos. the process works * as follows: Load speak.tos into memory using the Mode 3 option * of the Pexec GEMDOS call (load/nogo). if successful, this call will * return the address of the base page of the loaded program. * Add 0x100 to this to get the first byte of the executable * code. then create "subroutines" by poking in rts instructions in * the right places. at this point, we can jump into speak.tos, * executing the 2 subroutines. you MUST save registers (not sure * which, i save 'em all) since speak clobbers them. the text to * speak is poked into a Cconrs-like buffer. * * here are the addresses (relative to start of program): * * routine 1 start 0x32 (muck with phonemes) * routine 1 rts 0x0e * routine 2 start 0x88 (generate speach) * routine 2 rts 0x6c * * input buffer 0x6eee * * calling the subroutines for phoneme and speech generation is * currently done in c_speak.s which also saves all the registers prior * to making the actual calls. i tried this in C and it failed. you * really need to save registers and i don't know a way to do that * without using assembler somehow (inline __asm__ or routine). note * that the inline assembler code in speak_line() has not been tested * and is for GNU C anyway. * * talk looks for speak.tos in the current directory, with environment * variable SPEAK or via the path. * * it would be nice to beef up the clean_line() function to remove * non-letters and perhaps fix things like numbers, acronyms, etc. * * ABOUT SPEAK.TOS * i know little of its history. it has a limited user interface, so * i know it got hacked up (like this here) for other uses. i picked * this up on compuserve ages ago. if you think you may have it but * the name has changed, here are some facts: * * sum speak.tos 56759 28 * ls -l speak.tos 28416 bytes * strings -5 speak.tos turned up: * * Atari 520ST speech synthesizer V2.0 * * MC68000/AY-3-8910 SPEECH SYNTHERSIZER V:2.0 * Copyright 1986 A.D.BEVERIDGE & M.N.DAY * ALL RIGHTS RESERVED. * * so i guess it can't be used commercially. * * RELEASE NOTES * hacked together, originally in alcyon and now GNU C by: * Bill Rosenkranz, 1989? through 14 sep 1991 * rosenkra@convex.com * * my last source mod dates from 18 sep 1990 */ #ifndef lint static char *rcsid_talk_c = "$Id: talk.c,v 2.1 1991/09/14 21:13:34 rosenkra Exp $"; #endif /*lint*/ /* * $Log: talk.c,v $ * Revision 2.1 1991/09/14 21:13:34 rosenkra * clean up for release to atari.archive. * * Revision 2.0 1991/09/14 15:52:28 rosenkra * GNU C version. improved user interface (stdin, file(s), and cmdline; * search cwd, env, and PATH for speak, etc). also has inline assembly * for c_speak.s, but is still untested. includes which() function * which functions like which(1) only it can use any env variable (not * just PATH) and any of a pool of path name seperators. * * Revision 1.0 1991/09/14 01:51:22 rosenkra * Initial revision * */ #include #include /* for getenv() */ #include /* for str*() */ #include /* for Pexec */ #ifdef HAS_ACCESS #include /* for access() */ #endif /*HAS_ACCESS*/ /* * name of the speak program */ #define SPEAK_PROG "speak.tos" #ifdef DBG # undef DBG #endif #ifdef DEBUG # define DBG(x) printf x #else # define DBG(x) #endif #ifdef MAXLINE # undef MAXLINE #endif #define MAXLINE 256 /* max length of input line (stream or cmd) */ #ifndef MAXPATH # define MAXPATH 256 /* max length of file path */ #endif #ifdef SEARCH_PATH # define MAXENV 1024 /* max size of env string */ #endif /* * globals: */ char rts[2] = {0x4E, 0x75}; /* patch for subs (68000 rts inst) */ long program; /* The base of SPEAK */ char speak_path[MAXPATH]; /* for path of speak.tos */ char line_buf[MAXLINE]; /* holds string to speak */ int verbose = 0; /* tells what is going on */ /* * local fcns: */ void usage (void); void load_speak_prog (void); void speak_line (char *); void speak_stream (FILE *); void wait_ms (long); void c_speak (void); char *clean_line (char *); #ifdef SEARCH_PATH char *which (char *, char *, char *); #endif /*SEARCH_PATH*/ /*------------------------------*/ /* main */ /*------------------------------*/ int main (int argc, char *argv[]) { FILE *in; /* stream if file */ /* * check first arg for "-v" */ argc--, argv++; if ((argv[0][0] == '-') && (argv[0][1] == 'v') && (argv[0][2] == 0)) { verbose = 1; argc--, argv++; } /* * Load in SPEAK and fix it up...if successful, we'll be able * to TELL the user if he made any errors :-) */ load_speak_prog(); /* * parse command line */ for ( ; argc && (**argv == '-'); argc--, argv++) { switch (*(*argv+1)) { case 'v': if (strncmp (*argv, "-vers", 5) == 0) { printf ("%s\n", rcsid_talk_c); exit (0); } verbose = 1; break; case 'h': usage (); exit (0); break; /*NOTREACHED*/ case 0: speak_stream (stdin); break; case 'f': /* * read file * * first open it... */ argv++, argc--; if (!argc) { usage (); exit (1); } if ((in = fopen (*argv, "r")) == (FILE *) 0L) { printf ("talk: could not open data file\n"); speak_line ("could not open data file"); exit (1); } speak_stream (in); fclose (in); break; default: usage (); exit (1); break; /*NOTREACHED*/ } } for ( ; argc && *argv; argc--, argv++) { /* * copy all words on command line to a buffer. use spaces * between words */ line_buf[0] = '\0'; while (argc--) { strcat (line_buf, *argv++); strcat (line_buf, " "); } /* * do it! */ if (verbose) printf ("%s\n", line_buf); speak_line (line_buf); } exit (0); } /*------------------------------*/ /* usage */ /*------------------------------*/ void usage () { printf ("Usage: talk [-v] [-] [-f file] [words ...]\n"); printf ("\n"); printf ("-v means verbose and should be first argument (echo line).\n"); printf ("- or -f file must appear before any words on command line.\n"); printf ("can have more than one -f file option and optional words.\n"); printf ("- means read from stdin.\n"); printf ("\n"); printf ("Example: talk -f f1 - -f f2 and now some words\n"); printf ("\n"); printf ("speaks file f1, then stdin, then file f2 then words on command.\n"); } /*------------------------------*/ /* load_speak_prog */ /*------------------------------*/ void load_speak_prog () { long base; /* basepage location of loaded speak */ char *penv; /* -> environment variable SPEAK */ char *p; #ifndef HAS_ACCESS FILE *ftest; /* stream for speak.tos */ #endif /*HAS_ACCESS*/ speak_path[0] = 0; /* * find speak.tos. search order: * * 1) cwd * 2) env variable SPEAK contains full path (eg c:\bin\speak.tos) * 3) search path */ #ifdef HAS_ACCESS if (access (SPEAK_PROG, F_OK) >= 0) { strcpy (speak_path, SPEAK_PROG); if (verbose) printf ("talk: found %s in current directory\n", speak_path); } #else /*! HAS_ACCESS*/ if ((ftest = fopen (SPEAK_PROG, "r")) != (FILE *) 0L) { fclose (ftest); strcpy (speak_path, SPEAK_PROG); if (verbose) printf ("talk: found %s in current directory\n", speak_path); } #endif /*HAS_ACCESS*/ else { if (verbose) printf ("talk: %s not in current directory. check env.\n", SPEAK_PROG); if ((penv = getenv ("SPEAK")) != (char *) NULL) { strcpy (speak_path, penv); if (verbose) printf ("found %s from SPEAK env variable.\n", speak_path); } #ifdef SEARCH_PATH else { if (verbose) printf ("talk: cannot find SPEAK in env. Search path.\n"); if ((p = which (SPEAK_PROG, "PATH", ",")) == (char *) NULL) { printf ("talk: %s not found in PATH=%s\n", SPEAK_PROG, getenv ("PATH")); printf ("Either set environment variable SPEAK to the full path of %s\n", SPEAK_PROG); printf ("or make sure %s is in current directory or in your PATH.\n", SPEAK_PROG); exit(1); } else { strcpy (speak_path, p); if (verbose) printf ("talk: found %s from PATH\n", speak_path); } } #else /*! SEARCH_PATH*/ else { printf ("talk: %s not found\n", SPEAK_PROG); printf ("Either set environment variable SPEAK to the full path of %s\n", SPEAK_PROG); printf ("or make sure %s is in current directory.\n", SPEAK_PROG); exit(1); } #endif /*SEARCH_PATH*/ } /* * do load/nogo */ base = (long) Pexec ((short) 3, (long) speak_path, (long) 0, (long) 0); if (base < 0) { printf ("talk: Pexec failure\n"); exit (1); } /* * mark program start 256 bytes after basepage... */ program = (long) (base + 0x00000100L); /* * fix the 2 subs by adding returns... */ strncpy ((char *) (program+0x0e), rts, 2); strncpy ((char *) (program+0x6c), rts, 2); return; } /*------------------------------*/ /* speak_stream */ /*------------------------------*/ void speak_stream (FILE *stream) { /* * read lines from stream calling speak_line on every one... */ while (1) { /* * read a line. speak_line eliminates newline, etc. */ fgets (line_buf, MAXLINE-1, stream); if (feof (stream)) { break; } /* * do it! (printf allows ^C to terminate) */ if (verbose) printf ("%s\n", line_buf); speak_line (line_buf); } return; } /*------------------------------*/ /* speak_line */ /*------------------------------*/ void speak_line (char *ps) { register char *buffer; register char *pstart; long save_ssp; /* * clean up the line. clean_line ret NULL if line is empty (ie * devoid of readable text). * * this can get more sophisticated. */ if ((pstart = clean_line (ps)) == (char *) NULL) return; /* * check for nothing to say... */ if (strlen (pstart) == 0) return; /* * force the length of the the string not to exceed 125 */ pstart[125] = '\0'; /* * note location of speak.tos input buffer */ buffer = (char *) (program + 0x6EEEL); /* * this is a Cconrs-type buffer so our string has to be modified * by having the first byte the length of the buffer and the second * the length of the actual string. we copy directly to speak.tos * buffer here... */ strcpy (buffer+2, pstart); /* data to speak */ *(buffer+1) = strlen (pstart); /* and its length */ *buffer = (char) 0xFE; /* size of entire buffer */ /* * SPEAK will respeak the last line if the current * input line is a '\n'. The '\n' is replaced by a * space to defeat this redundant speech on double spaced * files. */ if (!*(buffer+1)) { buffer[1] = (char) 1; buffer[2] = ' '; buffer[3] = '\0'; } /* * Call speak. */ #if defined(__GNUC__) && defined(INLINE_ASM) __asm__ volatile \ ("\ movml d0-d7/a0-a6,sp@-; /* save all regs */ \ moveq #50,d0; \ addl %0,d0; \ movel d0,a2; \ jbsr a2@; \ moveq #136,d0; \ addl %0,d0; \ movel d0,a2; \ jbsr a2@; \ movml sp@+, d0-d7/a0-a6" \ : /* outputs */ \ : "g"(program) /* inputs */ \ : "d0", "a2" /* clobbered regs */ \ ); #else c_speak (); #endif /*__GNUC__&&INLINE_ASM*/ /* * slight delay...then return. delay is needed to make a smooth * transition between continued lines. delay is in milliseconds. */ #ifdef NEED_DELAY wait_ms (200L); #endif /*NEED_DELAY*/ return; } /*------------------------------*/ /* clean_line */ /*------------------------------*/ char *clean_line (char *ps) { /* * here we can get more sophisticated than this. we only kill newlines * and leading/trailing whitespace but could also delete punctuation, * expand numbers, abbreviations, etc. */ register char *pstart; register char *pend; /* * kill any newlines just in case */ pstart = ps; while (*pstart && ((*pstart == '\n') || (*pstart == '\r'))) { *pstart = ' '; pstart++; } /* * kill leading whitespace */ pstart = ps; while (*pstart && ((*pstart == ' ') || (*pstart == '\t'))) pstart++; /* * kill trailing whitespace */ pend = &pstart[strlen(pstart)-1]; while (((long)pend > (long)pstart) && ((*pend==' ') || (*pend=='\t'))) { *pend = '\0'; pend--; } return (pstart); } #ifdef NEED_DELAY /*------------------------------*/ /* wait_ms */ /*------------------------------*/ #define MSLOOP 125 void wait_ms (long ms) { /* * wait prescribed number of miliseconds (approx). * inner loop takes about 1 ms on normal 8Mhz ST. */ int i; if (ms <= 0) return; for ( ; ms > 0; ms--) for (i = MSLOOP; i > 0; i--) ; return; } #endif /*NEED_DELAY*/ #ifdef SEARCH_PATH /* * NAME * which - search PATH directories for given file * * called by: anything * * calls: strpbrk, strtok, getenv, strcpy, access, strncpy, sprintf * * returns: ptr to full pathname of file or NULL if not found * * notes: * * file should be simple filename (i.e. nnnnnnnn.eee). if it contains * any / or \ characters and the file cannot be found in the given path, * a null ptr is returned. delim is list of possible path component * seperator (eg ", "). env_name should be "PATH" or "path" typically, * but can be anything that would be in the environment. * * ptr returned, if valid, points to static array maintained here. use * it or (potentially) loose it... * * typical call: * * file_full_path = (char *) which (filename, "PATH", ","); */ #include #include #include #ifdef HAS_ACCESS #include #endif /*HAS_ACCESS*/ #ifdef DBG # undef DBG #endif #ifdef DEBUG # define DBG(x) printf x #else # define DBG(x) #endif /*------------------------------*/ /* which */ /*------------------------------*/ char *which (char *file, char *env_name, char *delim) { static char pathname[MAXPATH]; char pbuf[MAXENV]; char *p; #ifndef HAS_ACCESS FILE *fd; #endif /*HAS_ACCESS*/ strcpy (pathname, file); /* * check the obvios: cwd */ #ifdef HAS_ACCESS if (access (pathname, 0) != -1) return (pathname); #else /*! HAS_ACCESS*/ if ((fd = fopen (pathname, "r")) != NULL) { fclose (fd); return (pathname); } #endif /*HAS_ACCESS*/ /* * file not in cwd. if file contains a specific path (i.e. contains * a \ (or /) or if PATH environment is not set, return a NULL. * else search for file along PATH. */ if (strpbrk (file, "\\/") || !(p = getenv (env_name))) { DBG (("which: env= %s\n", p)); return ((char *) NULL); } DBG (("which: env= %s\n", p)); /* * copy value of env_name */ if (strlen (p) > MAXENV) strncpy (pbuf, p, MAXENV); else strcpy (pbuf, p); /* * start breaking up the string... */ if (p = strtok (pbuf, delim)) { do { #define lastchar(s) (p)[strlen(p)-1] DBG (("which: token= %s\n", p)); if ((lastchar(p) == '\\') || (lastchar(p) == '/')) sprintf (pathname, "%0.50s%0.20s", p, file); else if (index (p, '\\')) sprintf (pathname, "%0.50s\\%0.20s", p, file); else if (index (p, '/')) sprintf (pathname, "%0.50s/%0.20s", p, file); else sprintf (pathname, "%0.50s\\%0.20s", p, file); DBG (("which: pathname= %s\n", pathname)); #ifdef HAS_ACCESS if (access (pathname, 0) != -1) return (pathname); #else /*! HAS_ACCESS*/ if ((fd = fopen (pathname, "r")) != (FILE *) NULL) { fclose (fd); return (pathname); } #endif /*HAS_ACCESS*/ } while (p = strtok ((char *)0L, delim)); } /* * if we get here, we did not find it... */ return ((char *) NULL); } #endif /*SEARCH_PATH*/ #if 0 /* * c_speak.c - stub to invoke SPEAK.TOS * * this doe NOT work unless registers are saved before (*f)() is * invoked and restored after return. GNU C does not do this. */ #ifndef lint static char *rcsid_c_speak_c = "$Id$"; #endif /* * $Log$ */ /* * offsets from program start to where we jump... */ #define CALL_1_OFFSET 0x32 #define CALL_2_OFFSET 0x88 /* * this is actually a pointer to the start of the loaded program */ extern long program; /*------------------------------*/ /* c_speak */ /*------------------------------*/ void c_speak () { register void (*f)(); /* * first call, set up phonemes, make call... */ f = (void (*)()) (program + CALL_1_OFFSET); (*f)(); /* * second call, speak... */ f = (void (*)()) (program + CALL_2_OFFSET); (*f)(); return; } #endif