/*
 * A morse code practice utility. (Contains those characters that can appear
 * on the FCC ham license exam.)
 *
 * Running "morse" without arguments or input gives self-doc.
 *
 * It doesn't keep PERFECT time, but it seems reasonably close
 * for reasonable word speeds on my slow SUN IPC!
 *
 * Joe Dellinger
 * Tue Aug 11 14:01:02 HST 1992
 * University of Hawaii at Manoa
 *
 * Legal stuff:
 * This code is (ridiculously) heavily modified from morse.c from the Reno UNIX
 * distribution. I (Joe) also used slightly modified versions of a subroutine
 * from Richard Ottolini at Unocal for Sun workstation tone generation.
 * Scott Seligman at Stanford added support for other sorts of devices and
 * made several other changes. John Shalamskas helped test and made comments.
 *
 * I don't think anybody cares if you redistribute this, modify it, etc...
 * But don't claim you wrote it, try to sell it, or take anyone's name out
 * of the code! If you modify it, PLEASE INDICATE WHEN, WHERE, AND HOW
 * YOU MODIFIED IT IN THE COMMENT BELOW. Please.
 */
/*
 * Joe Dellinger, UH Manoa, inserted this sample modification entry June 1992.
 */

/*
 * The following stuff is here because this started life as a routine from
 * the Reno UNIX distribution. (It's also a good boilerplate bit of legalese
 * to have in there anyway.)
 */
/*
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
char            copyright[] =
"@(#) Copyright (c) 1988 Regents of the University of California.\n\
 All rights reserved.\n";
char            copyleftjoe[] =
"@(#) Copyright (c) 1992 Joe Dellinger, University of Hawaii at Manoa.\n";
#endif			/* not lint */

/*
 *============================================================================
 * Here starts the code!
 *============================================================================
 */
/*
 * Useful for seeing what the interleaved reading and writing loops are
 * really up to.
 *
 * #define DEBUG
 *
 * If you want to be overwhelmed with information about the probabilities
 * of each letter being chosen.
 *
 * #define DEBUGG
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <ctype.h>
#include <signal.h>
#include "beep.h"
/* Define USG for termio a la System V. */
#ifdef __hpux
#define USG
#endif

static int      whichfrequ = 0;
static float    frequency1 = 800.;
static float    frequency2 = 602.;
static float    frequency;
static float    volume = .3;
static float    dot_time;
static float    dash_time;
static float    intra_char_time;
static float    inter_char_time;
static float    inter_word_time;
static float    catchup_time;
static int      showletters = 0;
static int      showmorse = 0;
static int      wordsbefore = 0;
static int      wordsafter = 0;
static int      fancyending = 1;
static int      noticebad = 0;
static int      testing = 0;
static int      showtesting = 0;
static int      dynamicspeed = 0;
static int      charbychar = 0;
static int      tryagaincount = 1;
static float    words_per_minute;
static float    fwords_per_minute;
static int      randomletters = 0;
#define LETMESEE 2
static int      typeaway = 0;

static int      totalhitcount = 0;
static int      totalmisscount = 0;
static int      helpmeflag = 0;

#define MAXWORDLEN	20
#define TESTBUFSZ (MAXWORDLEN*10)
static int      testpointer = -1;
static int      testlength = 0;
static int      behindness = 0;
static char     teststring[TESTBUFSZ];
static int      yourpointer = -1;
static int      yourlength = 0;
static char     yourstring[TESTBUFSZ];

/*
 * How many times can a given character not be asked before
 * kicking up the probability of asking that one by one randomfactor unit.
 */
#define RIPECOUNT	64

#define TWOFIFTYSIX 256
static char    *(code[TWOFIFTYSIX]);
static int      randomfactor[TWOFIFTYSIX];
static int      randomripe[TWOFIFTYSIX];
extern int      testterminal ();
extern int      randomletter ();

/*
 * How many characters behind before it decides you're having
 * trouble keeping up.
 */
#define BEHIND		1
#define WAYBEHIND	3
#define TOOFARBEHIND	6
/*
 * If SLOWPOKE or more wpm ticks go by, then it decides you are having lots
 * of trouble remembering this character, and need to be asked it more
 * often.
 */
#define SLOWPOKE	10
/* You aren't slow -- you left and came back! */
#define SLOWPOKEMAX	(50 * SLOWPOKE)
/*
 * If FASTPOKE or less wpm ticks go by, then it decides you are good at this
 * character, and need to be asked it less often.
 */
#define FASTPOKE	4

/*
 * These control how quickly the dynamicspeed option acts when you are
 * fast or slow. Easier to slow down than speed up!
 */
#define ERRORSLOWER	1.04
#define ALOTSLOWER	1.15
#define ALITTLESLOWER	1.02
#define ALITTLEFASTER	1.02

/*
 * How many inter_char_time's to give you to answer after the end of
 * a word before considering that you are not keeping up.
 * Maximum of 2.3, minimum of 0.
 * The bigger the value, the easier it is to kick in the "automatic
 * speedup" when using the "-d" option. The maximum means you have (almost)
 * right up to the beginning of the next word to answer and still have it
 * count as keeping up.
 */
#define SPORTING_RATIO 1.5

/*
 * The bigger, the more evenly things start out.
 * (Must be at least 2)
 */
#define RANDOMBASELEVEL	7
/*
 * RANDOMINCWORSE scales how badly you are punished for being wrong
 * or taking too long. RANDOMINCBETTER scales how you are rewarded for
 * answering quickly or being right.
 */
#define RANDOMINCWORSE	6
#define RANDOMINCBETTER	7
#define RANDOMMAX	(30 * RANDOMBASELEVEL)
/*
 * The average length of a random word (chosen using exponential distribution).
 * After implementing this I'm not so sure an exponential distribution
 * actually models the distribution of real word lengths in English very well.
 * It's not too bad, though, and the words themselves are all garbage anyway,
 * so what the heck.
 */
#define RANDWORDLEN	3.5
/* Put in a newline instead of a space when past this column */
#define RANLINELENGTH	50

/* An EOF without the EOF (@) sound */
#define SILENTEOF -2
/* Toggle tone frequency on control-G within input file */
#define FREQU_TOGGLE ((int)'\007')

/*
 * If you want the morse code to come out synchronized with the printing
 * of dots and dashes with the -m option, then define this. The problem
 * is that then the morse code then sounds ratty on slower CPU's.
 * John Shalamskas (KJ9U) suggested turning the precise morse-code printing
 * synching off because he didn't like the resulting code quality!
 */
#undef FLUSHCODE

/*
 * Choose your favorite random number generator!
 */
#ifndef USERANDOM
#ifndef USELRAND
#ifndef USERAND
#define USERANDOM	/* USELRAND or USERAND are the other choices */
#endif
#endif
#endif

#ifdef USERANDOM
#define RANDOM()      random()
#define SEEDRANDOM(s) srandom((int)s)
long            random ();
#endif
#ifdef USELRAND
#define RANDOM()      lrand48()
#define SEEDRANDOM(s) srand48((long)(s))
long            lrand48 ();
#endif
#ifdef USERAND
/*
 * UGH, are you really sure you want to use this one?
 * This one really stinks!
 */
#define RANDOM()      rand()
#define SEEDRANDOM(s) srand((int)(s))
int             rand ();
#endif

extern          die (), cleanup (), suspend ();

main (argc, argv)
    int             argc;
    char          **argv;
{
extern char    *optarg;
extern int      optind;
int             ch;
char           *p;
int             ii, jj;
int             firsttime, notdoneyet;
int             yourchar;
float           randexp, randnum;
extern time_t   time ();
int             linepos;

    if (argc == 1 && isatty (fileno (stdin)))
    {
	printf ("Usage:\n");
	printf ("morse [options] < text_file\n");
	printf ("morse [options] words words words\n");
	printf ("morse [options] -r\n");
	printf ("morse [options] -i\n");
	printf ("Options:\n");
	printf ("-i    Play what you type.\n");
	printf ("-I    Like -i but don't turn off keyboard echoing.\n");
	printf ("-r    Generate random text. Starts out slanted towards easy\n");
	printf ("      letters, then slants towards ones you get wrong.\n");
	printf ("-w words_per_minute\n-f frequency_in_hertz\n-v volume (zero to one, rather nonlinear)\n");
	printf ("-g alternate_frequency (toggles via control-G in input FILE at a word break)\n");
	printf ("-F Farnsworth_character_words_per_minute\n");
	printf ("-e    leave off the EOT sound at the end\n");
	printf ("-c    complain about illegal characters instead of just ignoring them\n");
	printf ("-b    print each word before doing it\n");
	printf ("-a    print each word after doing it\n");
	printf ("-l    print each letter just before doing it\n");
	printf ("-m    print morse dots and dashes as they sound\n");
#ifdef FLUSHCODE
	printf ("      (this printing-intensive option slows the wpm down!)\n");
#endif
	printf ("-t    Type along with the morse, but don't see what\n");
	printf ("      you're typing (unless you make a mistake).\n");
	printf ("      You are allowed to get ahead as much as you want.\n");
	printf ("      If you get too far behind it will stop and resync with you.\n");
	printf ("      You can force it to resync at the next word end by hitting control-H.\n");
	printf ("      Hit ESC to see how you are doing, control-D to end.\n");
	printf ("-T    Like -t but see your characters (after they are played).\n");
	printf ("-s    Stop after each character and make sure you get it right. (implies -t)\n");
	printf ("-p NUM\n");
	printf ("      Make you get it right NUM times, for penance. (implies -s)\n");
	printf ("      (Yes, NUM = 0 means you can sin all you want.)\n");
	printf ("-d    Dynamically speed up or slow down depending on how you are doing.\n");
	printf ("      (if also -s, then -d _only speeds up_!)\n");
	printf ("\n");
	printf ("For the raw beginner trying to learn morse code I recommend\n");
	printf ("morse -r -s -T -d -w 5 -F 15 -p 5\n");
	printf ("then later\n");
	printf ("morse -r -T -d -w 5 -F 15\n");
	printf ("and\n");
	printf ("QSO | morse -e -T -d -w 5 -F 15\n");
	printf ("\n");
	printf ("Written by (mostly) joe@montebello.soest.hawaii.edu\n");
	exit (0);
    }

    for (ii = 0; ii < TWOFIFTYSIX; ii++)
	code[ii] = NULL;

/* Load in the morse code code */
    code[(int) '0'] = "-----";
    code[(int) '1'] = ".----";
    code[(int) '2'] = "..---";
    code[(int) '3'] = "...--";
    code[(int) '4'] = "....-";
    code[(int) '5'] = ".....";
    code[(int) '6'] = "-....";
    code[(int) '7'] = "--...";
    code[(int) '8'] = "---..";
    code[(int) '9'] = "----.";

    code[(int) 'a'] = ".-";
    code[(int) 'b'] = "-...";
    code[(int) 'c'] = "-.-.";
    code[(int) 'd'] = "-..";
    code[(int) 'e'] = ".";
    code[(int) 'f'] = "..-.";
    code[(int) 'g'] = "--.";
    code[(int) 'h'] = "....";
    code[(int) 'i'] = "..";
    code[(int) 'j'] = ".---";
    code[(int) 'k'] = "-.-";
    code[(int) 'l'] = ".-..";
    code[(int) 'm'] = "--";
    code[(int) 'n'] = "-.";
    code[(int) 'o'] = "---";
    code[(int) 'p'] = ".--.";
    code[(int) 'q'] = "--.-";
    code[(int) 'r'] = ".-.";
    code[(int) 's'] = "...";
    code[(int) 't'] = "-";
    code[(int) 'u'] = "..-";
    code[(int) 'v'] = "...-";
    code[(int) 'w'] = ".--";
    code[(int) 'x'] = "-..-";
    code[(int) 'y'] = "-.--";
    code[(int) 'z'] = "--..";

    /* Punctuation */
    code[(int) '='] = "-...-";
    code[(int) '?'] = "..--..";
    code[(int) '/'] = "-..-.";
    code[(int) ','] = "--..--";
    code[(int) '.'] = ".-.-.-";

    /* Procedural signs */
    code[(int) '+'] = ".-.-.";
    code[(int) '@'] = "...-.-";

    for (ii = 0; ii < TWOFIFTYSIX; ii++)
    {
	/* Everything starts equally fresh */
	randomripe[ii] = 0;

	if (code[ii] == NULL)
	{
	    /* Ensures these will never be chosen */
	    randomfactor[ii] = 0;
	}
	else
	{
	    /* Start out favoring easy ones */
	    randomfactor[ii] = RANDOMBASELEVEL - strlen (code[ii]);
	    if (randomfactor[ii] < 1)
		randomfactor[ii] == 1;
	}
    }

    words_per_minute = 20.;
    fwords_per_minute = -1.;

    while ((ch = getopt (argc, argv, "F:w:lbamf:g:v:tTdesp:riIc")) != EOF)
	switch ((char) ch)
	{
	case 'i':
	    typeaway = 1;
	    break;
	case 'I':
	    typeaway = LETMESEE;
	    break;
	case 'r':
	    randomletters = 1;
	    break;
	case 'c':
	    noticebad = 1;
	    break;
	case 'e':
	    fancyending = 0;
	    break;
	case 'T':
	    testing = 1;
	    showtesting = 1;
	    break;
	case 't':
	    testing = 1;
	    break;
	case 's':
	    charbychar = 1;
	    testing = 1;
	    break;
	case 'p':
	    charbychar = 1;
	    testing = 1;
	    sscanf (optarg, "%d", &tryagaincount);
	    break;
	case 'd':
	    dynamicspeed = 1;
	    break;
	case 'w':
	    sscanf (optarg, "%f", &words_per_minute);
	    break;
	case 'F':
	    sscanf (optarg, "%f", &fwords_per_minute);
	    break;
	case 'l':
	    showletters = 1;
	    break;
	case 'b':
	    wordsbefore = 1;
	    break;
	case 'a':
	    wordsafter = 1;
	    break;
	case 'm':
	    showmorse = 1;
	    break;
	case 'f':
	    sscanf (optarg, "%f", &frequency1);
	    break;
	case 'g':
	    sscanf (optarg, "%f", &frequency2);
	    break;
	case 'v':
	    sscanf (optarg, "%f", &volume);
	    if (volume < 0.)
		volume = 0.;
	    if (volume > 1.)
		volume = 1.;
	    break;
	default:
	    fprintf (stderr, "Type \"morse\" without arguments to get self-doc!\n");
	    exit (1);
	    break;
	}
    argc -= optind;
    argv += optind;

    if (fwords_per_minute <= 0.)
	fwords_per_minute = words_per_minute;
    new_words_per_minute ();

    frequency = frequency1;

    if (BeepInit () != 0)
    {
	fprintf (stderr, "Can't access speaker.\n");
	exit (1);
    }

    signal (SIGINT, die);
    signal (SIGTERM, die);
    signal (SIGQUIT, die);
    signal (SIGTSTP, suspend);

    if (testing || typeaway)
    {
	openterminal ();
    }

    if (typeaway)
    {
	testing = 0;
	showtesting = 0;
	charbychar = 0;
	wordsbefore = 0;
	wordsafter = 0;
	randomletters = 0;

	notdoneyet = 1;

	while (notdoneyet)
	{
	    pollyou ();

	    for (jj = 0; jj < yourlength; jj++)
	    {
		yourchar = yourstring[(yourpointer - yourlength + 1 + jj + TESTBUFSZ) % TESTBUFSZ];

		/* Control-D: finished */
		if (yourchar == (int) '\004')
		{
		    toneflush ();
		    notdoneyet = 0;
		    break;
		}

		if (isspace (yourchar))
		{
		    if (showletters)
		    {
			toneflush ();
			printf ("%c", yourchar);
			fflush (stdout);
		    }

		    tone (frequency, inter_word_time, 0.);

		    continue;
		}

		morse (yourchar);
	    }
	    yourlength -= jj;
	}
    }
    else if (randomletters)
    {
	SEEDRANDOM (time (NULL));
	randexp = 1. / (1. - 1. / (float) (RANDWORDLEN));
	linepos = 0;
	while (1)
	{
	    dowords (randomletter ());
	    linepos++;

	    /* Knock a few bits off the top so we're sure it won't overflow */
	    /* Shift a few bits because the lower bits stink */
	    /* Add in the time so it doesn't repeat from run to run */
	    randnum = (float) (
			       ((RANDOM () >> 9) + (long) (time (NULL)) >> 4)
			       & 0x00FFFFFF);
	    randnum = randnum - randexp * (int) (randnum / randexp);
	    if (randnum >= 1.)
		if (linepos >= RANLINELENGTH)
		{
		    dowords ((int) '\n');
		    linepos = 0;
		}
		else
		{
		    dowords ((int) ' ');
		    linepos++;
		}
	}
    }
    else
    {
	if (*argv)
	{
	    firsttime = 1;

	    do
	    {
		if (!firsttime)
		{
		    dowords ((int) ' ');
		}
		else
		    firsttime = 0;

		for (p = *argv; *p; ++p)
		    dowords ((int) *p);
	    } while (*++argv);
	}
	else
	{
	    while ((ch = getchar ()) != EOF)
		dowords (ch);
	}
    }

    if (fancyending)
	dowords (EOF);
    else
	dowords (SILENTEOF);

    fflush (stdout);

    if (testing)
    {
	/*
	 * WE'RE completely done, and YOU aren't! Force catch up. (Note if
	 * charbychar = YES we won't get here, since we're always caught up
	 * after each character as it comes out.)
	 */
	while (testlength > 0)
	{
	    tone (frequency, catchup_time, 0.);
	    toneflush ();
	    testterminal ();
	}
    }

    /* Just to be sure! */
    toneflush ();

    if (showmorse || wordsbefore || wordsafter || showletters || showtesting)
	printf ("\n");
    fflush (stdout);

    if (testing)
	report ();

/* If you make any mistakes exit with a return code! */
    cleanup ();
    return (totalmisscount > 0);
}

new_words_per_minute ()
{
float           wtick, ftick, tick;

    tick = 60. / (words_per_minute * 50);

    /*
     * In the limit as wpm goes past fwpm, Farnsworth becomes kosher PARIS
     */
    if (fwords_per_minute <= words_per_minute)
	ftick = 60. / (words_per_minute * 50);
    else
	ftick = 60. / (fwords_per_minute * 50);

    wtick = (50. * tick - 31. * ftick) / 19.;

    /*
     * This time is used when the computer is waiting on you to hit a key; it
     * is useful to scale the granularity with the real overrall words per
     * minute. This also serves as a measuring rod to see if you are
     * responding "fast enough". If you are too slow, then obviously you are
     * having trouble with that character, and should be given it more OFTEN.
     * Heh heh heh...
     */
    catchup_time = tick;

    /*
     * Things between characters and words go at the "remainder" speed,
     * whatever space you need to make the sped-up Farnsworth characters come
     * out with the correct overall words per minute.
     */
    inter_char_time = wtick * 3.;
    inter_word_time = wtick * 7.;

    /* Things within the character go at the Farnsworth speed */
    intra_char_time = ftick;
    dot_time = ftick;
    dash_time = ftick * 3.;
}

static int      tryingagain = 0, slowpoke = 0;

dowords (c)
    int             c;
{
static int      wordc = 0;
static char     word[MAXWORDLEN];
char           *wordp;
int             ii;
int             againcount;

/*
 * If a word gets too long, just cut it off by inserting a space.
 * Just call ourselves with the character we wish we'd gotten...
 */
    if (wordc == MAXWORDLEN - 1 && !(isspace (c) || c == EOF || c == SILENTEOF || c == FREQU_TOGGLE))
	dowords ((int) ' ');

    if (isspace (c) || c == EOF || c == SILENTEOF || c == FREQU_TOGGLE)
    {
	if (wordc > 0)
	{
	    word[wordc] = '\0';

/*
 * We have just read in a new complete word from the input, (hopefully)
 * during the time of an inter-word space minus an inter-char space.
 * Now let's go back and see what happened with the PREVIOUS word,
 * the one that we had just finished playing. Did the user keep
 * up with us?
 */
#ifdef DEBUG
	    fprintf (stderr, " [%d] ", behindness);
#endif
	    if (testing && dynamicspeed && !charbychar)
	    {
		/*
		 * (If charbychar then behindness is ALWAYS 0 at this
		 * point...)
		 */
		if (behindness == 0)
		{
		    /* You're a speed demon! Speed up a bit, then! */
		    words_per_minute *= ALITTLEFASTER;
		    new_words_per_minute ();
		}
		else if (behindness > WAYBEHIND)
		{
		    /* You're way behind! Slow way down. */
		    words_per_minute /= ALOTSLOWER;
		    new_words_per_minute ();
		}
		else if (behindness > BEHIND)
		{
		    /* You're behind! Slow down a bit. */
		    words_per_minute /= ALITTLESLOWER;
		    new_words_per_minute ();
		}
	    }


/*
 * If the user was WAY too far behind stop and catch up with
 * the "new" word as the first one.
 */
	    if (testing && (behindness > TOOFARBEHIND || helpmeflag))
	    {
		if (helpmeflag)
		    printf ("\nOK, let's restart.\n");
		else
		    printf ("\nYou are too far behind! Let's restart.\n");
		fflush (stdout);

		toneflush ();

		/* Flush the keyboard buffer */
		pollyou ();

		/* Forget the past */
		helpmeflag = 0;
		behindness = 0;
		testlength = 0;
		yourlength = 0;

		/* Give the user a little rest. */
		sleep (2);
		printf ("\nWPM now %d\n", (int) (words_per_minute + .5));
		fflush (stdout);
		sleep (2);
		printf ("\nREADY?\n");
		fflush (stdout);
		sleep (1);
		printf ("\nSET\n");
		fflush (stdout);
		sleep (1);
		printf ("\nGO!\n");
		fflush (stdout);
	    }

/*
 * Start treating the new word.
 */
	    if (wordsbefore)
	    {
		/* Try to keep your out-of-sync text from getting swirled in */
		if (showtesting)
		    printf ("\n");

		printf ("%s", word);

		if (showmorse || showletters || wordsafter || showtesting)
		{
		    printf ("  ");
		    for (ii = 0; ii < 16 - (wordc + 2); ii++)
		    {
			printf (" ");
		    }
		}

		fflush (stdout);
	    }

	    if (testing && charbychar)
	    {
		againcount = 0;
	    }

	    for (wordp = word; *wordp != '\0'; wordp++)
	    {
		tryingagain = 0;
	tryagain:
		morse (*wordp);

		if (testing)
		{
		    if (charbychar)
		    {
			toneflush ();
			/* Force catchup */
			slowpoke = 0;
			while (behindness > 0)
			{
			    if (testterminal () && tryagaincount > 0)
			    {
				/*
				 * OOPS! They got it WRONG! MAKE THEM TRY
				 * AGAIN!
				 */
				printf ("Try again.\n");
				/*
				 * Yeah I know gotos are inelegant but I
				 * don't feel like figuring out the "elegant"
				 * way to do this right now.
				 */
				againcount = tryagaincount - 1;
				tryingagain = 1;
				goto tryagain;
			    }
			    else
			    {
				/*
				 * They got it right, or they didn't answer
				 * yet.
				 */
				if (behindness > 0)
				{
				    /*
				     * They are STILL thinking, the
				     * slowpokes. Wait a bit before trying
				     * again.
				     */
				    tone (frequency, catchup_time, 0.);
				    /*
				     * Keep track of how long they're taking
				     * to answer!
				     */
				    if (slowpoke < SLOWPOKEMAX)
					slowpoke++;
				    toneflush ();
				}
				else if (dynamicspeed && !slowpoke && !tryingagain)
				{
				    /*
				     * They got it right without errors the
				     * first time and we didn't have to wait
				     * for them! A speed demon! Speed up a
				     * bit, then!
				     */
				    words_per_minute *= ALITTLEFASTER;
				    new_words_per_minute ();
				}
			    }
			}

			/* Insufficient penance? */
			if (againcount > 0)
			{
			    againcount--;
			    goto tryagain;
			}
		    }
		    else
		    {
			testterminal ();
		    }
		}
	    }

	    toneflush ();
	    if (testing)
		testterminal ();

	    if (wordsafter)
	    {
		printf (" (%s)", word);
	    }

	    if (wordsbefore || wordsafter || showmorse)
		printf ("\n");
	    else if (showletters || showtesting)
	    {
		if (c != EOF && c != SILENTEOF && c != FREQU_TOGGLE)
		{
		    if (showletters)
			printf ("%c", c);

		    if (showtesting)
			testaddchar (c);
		}
	    }

/*
 * WHEW! FINISHED QUEUEING THE WORD FOR PLAYING!
 * Now finish up all the other sundry details...
 */

	    /* Flush the output printing queue... */
	    fflush (stdout);
	    /*
	     * Pause for a bit; this gives the user a sporting chance at
	     * catching up with us.
	     */
	    tone (frequency, SPORTING_RATIO * inter_char_time, 0.);
	    toneflush ();
	    /* Start sounding an inter-word space */
	    tone (frequency, inter_word_time - SPORTING_RATIO * inter_char_time, 0.);

	    /* While that silence is playing check if the user has caught up. */
	    if (testing)
		testterminal ();

	    /* We finished this word; reset the word character count */
	    wordc = 0;
	}
	else if (!(wordsbefore || wordsafter || showmorse)
		 &&
		 (showletters || showtesting))
	{
	    if (c != EOF && c != SILENTEOF && c != FREQU_TOGGLE)
	    {
		if (showletters)
		    printf ("%c", c);

		if (showtesting)
		    testaddchar (c);
	    }
	}

	if (c == EOF)
	{
	    morse (EOF);
	    toneflush ();
	}
	else if (c == SILENTEOF)
	{
	    toneflush ();
	}
	else if (c == FREQU_TOGGLE)
	{
	    /* Switch to the other frequency */
	    /* (Won't work from keyboard, only from a file.) */
	    whichfrequ = 1 - whichfrequ;
	    switch (whichfrequ)
	    {
	    case 1:
		frequency = frequency2;
		break;
	    case 0:
	    default:
		frequency = frequency1;
		break;
	    }
	}
    }
    else
    {
	word[wordc++] = c;
    }
}

/*
 * Don't try to test the person DURING the call into morse!
 */
morse (c)
    int             c;
{
    if (showletters)
    {
	if (c == EOF)
	    printf ("EOT");
	else if (c == '.' && showmorse)
	    printf ("PERIOD");
	else if (c == '=' && showmorse)
	    printf ("BREAK");
	else
	    printf ("%c", c);

	fflush (stdout);
    }

    if (isalpha (c))
    {
	if (testing)
	    testaddchar (c - (isupper (c) ? 'A' : 'a') + 'a');
	show (code[c - (isupper (c) ? 'A' : 'a') + 'a']);
    }
    else if (c == EOF)
    {
	show (code[(int) '@']);
    }
    else if (code[c] != NULL)
    {
	if (testing)
	    testaddchar (c);
	show (code[c]);
    }
    else
    {
	/* Oops! This letter is junk! */

	if (noticebad)
	{
	    if (showletters)
	    {
		fflush (stdout);
	    }

	    /* Simulate a stumble */
	    tone (frequency, 2. * inter_word_time, 0.);
	    toneflush ();
	}

	if (showletters)
	{
	    /* Wipe out what we just printed */
	    fflush (stdout);
	    printf ("\b");
	    printf (" ");
	    printf ("\b");
	    fflush (stdout);
	}

	if (noticebad)
	{
	    if (showletters)
	    {
		/* And replace it with an error message */
		printf ("*UNKNOWN_CHARACTER*");
		fflush (stdout);
	    }

	    /* Give the error call */
	    show ("........");

	    /* Regroup */
	    tone (frequency, inter_word_time, 0.);
	}
    }

    if (showmorse)
	printf (" ");
    fflush (stdout);
    toneflush ();
    tone (frequency, inter_char_time - intra_char_time, 0.);
}


/*
 * Don't try to test the person WHILE doing dots and dashes!
 */
show (s)
    char           *s;
{
char            c;

    while ((c = *s++) != '\0')
    {
	tone (frequency, intra_char_time, 0.);

#ifdef FLUSHCODE
	if (showmorse)
	    toneflush ();
#endif

	switch (c)
	{
	case '.':
	    tone (frequency, dot_time, volume);
	    break;
	case '-':
	    tone (frequency, dash_time, volume);
	    break;
	}

	if (showmorse)
	{
	    printf ("%c", c);
	    fflush (stdout);
#ifdef FLUSHCODE
	    toneflush ();
#endif
	}
    }
}

/*
 * This only gets passed valid characters: ones
 * that have a morse code associated with them
 * or ones for which isspace(c) is true.
 */
testaddchar (c)
    char            c;
{
    testpointer = (testpointer + 1) % TESTBUFSZ;
    teststring[testpointer] = c;
#ifdef DEBUG
    fprintf (stderr, " (%c,%d,%d) ", c, testlength, behindness);
#endif
    testlength++;
    if (testlength > TESTBUFSZ)
    {
	fprintf (stderr, "\n\nInput buffer queue overflow! Make TESTBUFSZ bigger!\n");
	fprintf (stderr, "(Or don't fall so far behind)\n");

	die ();
    }

/*
 * Since you are never asked to type spaces (you can type them if
 * you want, but they are ignored) spaces in the input file don't
 * count against your "behindness".
 */
    if (!isspace (c))
	behindness++;
}

youraddchar (c)
    char            c;
{
    yourpointer = (yourpointer + 1) % TESTBUFSZ;
    yourstring[yourpointer] = c;
#ifdef DEBUG
    fprintf (stderr, " <%c,%d> ", c, yourlength);
#endif
    yourlength++;
    if (yourlength > TESTBUFSZ)
    {
	fprintf (stderr, "\n\nKeyboard typeahead buffer queue overflow! Make TESTBUFSZ bigger!\n");
	fprintf (stderr, "(Or don't type so far ahead... how did you expect to get them right anyway?)\n");

	die ();
    }
}

pollyou ()
{
int             ii, num;
char           *string;

    num = readterminal (&string);

    for (ii = 0; ii < num; ii++)
	youraddchar (string[ii]);
}

int
testterminal ()
{
int             testinc, yourinc;
int             correctchar, yourchar, yourcharnocase;
int             errorcount;

    errorcount = 0;

/*
 * There is nothing in the input file queue right now,
 * so we can't process any of your keystrokes.
 * Defer processing until we can catch up with YOU!
 */
    if (testlength == 0)
	return errorcount;

    /* We're ready for you; but are you ready for us? */
    pollyou ();

/*
 * Process your entries and the input queue entries in parallel
 */
    if (yourlength > 0 && testlength > 0)
    {
	for (testinc = 0, yourinc = 0;
	     testinc < testlength && yourinc < yourlength;
	     testinc++, yourinc++)
	{
	    correctchar = teststring[(testpointer - testlength + 1 + testinc + TESTBUFSZ) % TESTBUFSZ];

	    /*
	     * The latter half of this if shouldn't be necessary, but just in
	     * case...
	     */
	    if (isspace (correctchar) || code[correctchar] == NULL)
	    {
		if (showtesting)
		{
		    printf ("%c", correctchar);
		    fflush (stdout);
		}

		/* White space doesn't count for "behindness" */
		behindness++;
		/* The _other_ pointer wasn't used; don't increment it. */
		yourinc--;

		/* Short circuit the loop */
		continue;
	    }


	    yourchar = yourstring[(yourpointer - yourlength + 1 + yourinc + TESTBUFSZ) % TESTBUFSZ];
	    if (isalpha (yourchar))
		yourcharnocase = yourchar - (isupper (yourchar) ? 'A' : 'a') + 'a';
	    else
		yourcharnocase = yourchar;

	    /* Did you type something rude? If so, just ignore it. */
	    if (isspace (yourchar) || code[yourcharnocase] == NULL)
	    {
		/* ESCAPE: dump status info */
		/* Control-D: dump status info and then bye bye */
		/* Control-H: force restart */
		if (yourchar == '\033' || yourchar == (int) '\004')
		{
		    report ();

		    if (yourchar == (int) '\004')
			die ();
		}
		else if (yourchar == '\b')
		{
		    helpmeflag = 1;
		}

		/* The _other_ pointer wasn't used; don't increment it. */
		testinc--;
		/* Short circuit the loop */
		continue;
	    }

	    if (yourcharnocase != correctchar)
	    {
		errorcount++;
		totalmisscount++;

		printf ("\n\007%c (%s) for %c (%s)\n",
			yourchar, code[yourcharnocase],
			correctchar, code[correctchar]);
		fflush (stdout);

		if (charbychar)
		{
		    /* Give them a bit of time to think about their error */
		    tone (frequency, inter_word_time, 0.);
		    toneflush ();
		}
		if (dynamicspeed && !charbychar)
		{
		    /*
		     * Slow down. Doesn't make sense to slow down for errors,
		     * though, if you've got all the time you want to think
		     * about each one.
		     */
		    words_per_minute /= ERRORSLOWER;
		    new_words_per_minute ();
		}
		if (randomletters && !tryingagain)
		{
		    /*
		     * Ask ones that confused you more often!
		     */
		    if (code[yourcharnocase] != NULL)
		    {
			randomfactor[yourcharnocase] += (3 * RANDOMINCWORSE / 2);
			if (randomfactor[yourcharnocase] > RANDOMMAX)
			    randomfactor[yourcharnocase] = RANDOMMAX;
		    }

		    randomfactor[correctchar] += RANDOMINCWORSE * 2;
		    if (randomfactor[correctchar] > RANDOMMAX)
			randomfactor[correctchar] = RANDOMMAX;
		}
	    }
	    else
	    {
		if (!tryingagain)
		    totalhitcount++;

		if (showtesting)
		{
		    printf ("%c", yourchar);
		    fflush (stdout);
		}
		if (randomletters && !tryingagain)
		{
		    if (slowpoke == SLOWPOKEMAX)
		    {
			printf ("\nNice to have you back again, I was getting bored!\n");
		    }
		    else if (slowpoke >= SLOWPOKE * 3)
		    {
			/*
			 * Did you take too long thinking about it? If so,
			 * you probably need to be asked this one more
			 * often...
			 */
			randomfactor[correctchar] += (3 * RANDOMINCWORSE / 2);
			if (randomfactor[correctchar] > RANDOMMAX)
			    randomfactor[correctchar] = RANDOMMAX;

			/*
			 * Hits this slow shouldn't count! You were obviously
			 * just guessing! (But it doesn't count as an error
			 * either.)
			 */
			totalhitcount--;
		    }
		    else if (slowpoke > FASTPOKE)
		    {
			randomfactor[correctchar] +=
			 (slowpoke * RANDOMINCWORSE) / (2 * SLOWPOKE);
			if (randomfactor[correctchar] > RANDOMMAX)
			    randomfactor[correctchar] = RANDOMMAX;
		    }
		    else if (slowpoke <= (FASTPOKE / 2))
		    {
			/*
			 * Ask ones that you quickly answer correctly less
			 * often!
			 */
			randomfactor[correctchar] -= (3 * RANDOMINCBETTER / 2);
			/*
			 * Don't let randomfactor hit 0, or you'll NEVER be
			 * asked this one AGAIN!
			 */
			if (randomfactor[correctchar] < 1)
			    randomfactor[correctchar] = 1;
		    }
		    else if (slowpoke <= FASTPOKE)
		    {
			randomfactor[correctchar] -= (RANDOMINCBETTER / 2);
			if (randomfactor[correctchar] < 1)
			    randomfactor[correctchar] = 1;
		    }
		}
	    }

	}
	testlength -= testinc;
	behindness -= testinc;
	yourlength -= yourinc;
    }

/*
 * If there are some extra white space characters in the input queue
 * it's OK, we'll get to them next time or we'll clean them out at the
 * end.
 */
    return errorcount;
}


/*----------------------------------------*/

tone (hertz, duration, amplitude)
    float           hertz, duration, amplitude;
{
    Beep ((int) (duration * 1000), (int) (amplitude * 100), (int) hertz);
}


toneflush ()
{
    BeepWait ();
}


/*----------------------------------------*/

#include <sys/ioctl.h>
#include <fcntl.h>
#ifdef USG
#include <sys/termio.h>
struct termio   oldtermgtty;
struct termio   termgtty;
#else
#include <sys/file.h>
struct sgttyb   oldtermgtty;
struct sgttyb   termgtty;
#endif
static char    *terminal = "/dev/tty";
static int      termfd;
static int      oldflgs, newflgs;
static int      termopen = 0;

openterminal ()
{
    /* get parameters and open terminal */

#ifdef USG
    termfd = open (terminal, O_RDWR | O_NDELAY, 0);
    ioctl (termfd, TCGETA, &termgtty);
    oldtermgtty = termgtty;
    if (typeaway != LETMESEE)
	termgtty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
    termgtty.c_lflag &= ~ICANON;
    termgtty.c_cc[VMIN] = 1;
    termgtty.c_cc[VTIME] = 0;
    ioctl (0, TCSETAW, &termgtty);
#else
    termfd = open (terminal, O_RDWR, 0);
    ioctl (termfd, TIOCGETP, &termgtty);
    oldtermgtty = termgtty;
    if (typeaway != LETMESEE)
	termgtty.sg_flags &= ~ECHO;
    termgtty.sg_flags |= CBREAK;
    ioctl (termfd, TIOCSETP, &termgtty);
    oldflgs = fcntl (termfd, F_GETFL);
    newflgs = oldflgs | FNDELAY;
#endif

    termopen = 1;
}

int
readterminal (string)
    char          **string;
{
/* This must be declared static! */
static char     line[TESTBUFSZ];
int             n;

#ifndef USG
    fcntl (termfd, F_SETFL, newflgs);
#endif
    n = read (termfd, line, sizeof (line) - 1);
#ifndef USG
    fcntl (termfd, F_SETFL, oldflgs);
#endif

    if (n > 0)
    {
	line[n] = '\0';
	*string = line;
    }
    else
	*string = NULL;

    return n;
}

closeterminal ()
{
#ifdef USG
    ioctl (termfd, TCSETAW, &oldtermgtty);
#else
    ioctl (termfd, TIOCSETP, &oldtermgtty);
#endif
    close (termfd);
}

die ()
{
    cleanup ();
    exit (1);
}

cleanup ()
{
    if (termopen)
	closeterminal ();
    BeepCleanup ();
}

suspend ()
{
    signal (SIGTSTP, suspend);
    cleanup ();
    kill (getpid (), SIGSTOP);
    if (termopen)
	openterminal ();
    BeepResume ();
}


/*----------------------------------------*/

int
randomletter ()
{
int             ii;
int             sum, sum2;
long            ranspot;
extern time_t   time ();
static int      lasttime = -1;
static long     norepeat;

/*
 * This keeps the not-so-random random number generator from ignoring
 * certain characters forever!
 */
    norepeat = ((long) time (NULL) / 31) % 17291;

/*
 * All the usable letters get one unit riper.
 */
    for (ii = 0; ii < TWOFIFTYSIX; ii++)
    {
	if (randomfactor[ii] > 0)
	{
#ifdef DEBUGG
	    fprintf (stderr, "%c: %d %d\n",
		     (char) ii, randomfactor[ii], randomripe[ii]);
#endif
	    randomripe[ii]++;
	}
    }

    sum = 0;
    for (ii = 0; ii < TWOFIFTYSIX; ii++)
	sum += (randomfactor[ii] + (int) (randomripe[ii] / RIPECOUNT));

/*
 * The low bits of random aren't very random, I don't care WHAT
 * the manual claims.
 */
    do
    {
	ranspot = ((RANDOM () >> 4) % sum + norepeat) % sum;

	sum2 = 0;
	for (ii = 0; ii < TWOFIFTYSIX - 1; ii++)
	{
	    sum2 += (randomfactor[ii] + (int) (randomripe[ii] / RIPECOUNT));

	    if (sum2 > ranspot)
		break;
	}
	/* Do it again if you got the same as last time! */
    } while (ii == lasttime);

    /* This one is FRESH again. */
    randomripe[ii] = 0;
    /* Remember for next time. */
    lasttime = ii;

    return ii;
}

report ()
{
int             ii, jj, count;
float           sum;
int             randomstr[TWOFIFTYSIX];
extern int      rancomp ();

    printf ("\nCurrent words per minute: %.1f\n", words_per_minute);

    printf ("Total hits %d, misses %d", totalhitcount, totalmisscount);
    if (totalmisscount > 0)
	printf (", hit per miss ratio %.1f\n", (float) totalhitcount / (float) totalmisscount);
    else
	printf ("\n");

    if (randomletters)
    {
	printf ("Most to least frequent choices:\n");
	count = 0;
	sum = 0.;
	for (ii = 0; ii < TWOFIFTYSIX; ii++)
	{
	    if (randomfactor[ii] > 0)
	    {
		sum += (randomfactor[ii] + (randomripe[ii] / (float) RIPECOUNT));
		randomstr[count] = ii;
		count++;
	    }
	}

	qsort ((char *) randomstr, count, sizeof (randomstr[0]), rancomp);

	for (ii = 0; ii < count; ii++)
	{
/*
 * Insert a space for each jump across an integer.
 * The normalization (count/sum) ensures that if all
 * letters were equally probable, they would all have value 1.
 * Since they are not generally equally probable, then 1 is just the average.
 * Thus the rightmost space in the printout marks where the average is.
 * Further left spaces separate off blocks of letters that are approximately
 * twice as probable as the average, three times, etc.
 */
	    if (ii > 0)
	    {
		for (jj = 0; jj <
		     (int) (
			    (randomfactor[randomstr[ii - 1]] + (randomripe[randomstr[ii - 1]] / (float) RIPECOUNT))
			    * count / sum) -
		     (int) (
			    (randomfactor[randomstr[ii]] + (randomripe[randomstr[ii]] / (float) RIPECOUNT))
			    * count / sum);
		     jj++)
		    printf (" ");
	    }
	    printf ("%c", (char) randomstr[ii]);
	}
	printf ("\n");
    }

    /*
     * So you don't get penalized for being "slow" after this.
     */
    if (charbychar)
	slowpoke = SLOWPOKEMAX + 1;

    fflush (stdout);
}

int
rancomp (elem1, elem2)
    int            *elem1, *elem2;
{
float           a, b;

    a = (randomfactor[(*elem1)] + (randomripe[(*elem1)] / (float) RIPECOUNT));
    b = (randomfactor[(*elem2)] + (randomripe[(*elem2)] / (float) RIPECOUNT));

    if (a == b)
	return 0;
    else if (a > b)
	return -1;
    else
	return 1;
}
