/*
 * 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 <stdio.h>
#include <stdlib.h>		/* for getenv() */
#include <strings.h>		/* for str*() */
#include <osbind.h>		/* for Pexec */
#ifdef HAS_ACCESS
#include <unistd.h>		/* 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 <stdio.h>
#include <stdlib.h>
#include <strings.h>
#ifdef HAS_ACCESS
#include <unistd.h>
#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


