]> thyrsus.com Git - morse-classic.git/commitdiff
Move morse into its own subdirectory.
authorEric S. Raymond <esr@thyrsus.com>
Mon, 24 Jan 2005 20:15:54 +0000 (20:15 +0000)
committerEric S. Raymond <esr@thyrsus.com>
Mon, 24 Jan 2005 20:15:54 +0000 (20:15 +0000)
morse.d/Makefile [new file with mode: 0644]
morse.d/alarm.c [new file with mode: 0644]
morse.d/alarm.h [new file with mode: 0644]
morse.d/beep.h [new file with mode: 0644]
morse.d/beepLinux.c [new file with mode: 0644]
morse.d/beepOSS.c [new file with mode: 0644]
morse.d/beepX11.c [new file with mode: 0644]
morse.d/morse.c [new file with mode: 0644]

diff --git a/morse.d/Makefile b/morse.d/Makefile
new file mode 100644 (file)
index 0000000..b0ab64d
--- /dev/null
@@ -0,0 +1,32 @@
+DEVICE=X11
+
+BEEPERS = beepLinux.c beepOSS.c beepX11.c
+SOURCES = alarm.c morse.c alarm.h beep.h $(BEEPERS)
+
+# The flags necessary to link with the X11 libraries.
+X11LIBS = -L /usr/X11R6/lib -lX11
+
+# Any additional flags your favorite C compiler requires to work.
+CFLAGS  = -O3 ${DEFINES}
+
+morse: morse${DEVICE}
+       rm -f $@
+       ln morse${DEVICE} $@
+
+morseX11:      morse.o beepX11.o alarm.o
+       $(CC) $(X11LIBS) -o $@ morse.o beepX11.o alarm.o
+
+morseLinux:    morse.o beepLinux.o alarm.o
+       $(CC) -o $@ morse.o beepLinux.o alarm.o -lm
+
+morseOSS:      morse.o beepOSS.o
+       $(CC) $(CFLAGS) -o $@ morse.o beepOSS.o -lm
+
+morse.o:       beep.h Makefile
+beepX11.o:     beep.h alarm.h
+beepLinux.o:   beep.h alarm.h
+beepOSS.o:     beep.h
+alarm.o:       alarm.h
+
+clean:
+       rm -f *.o morse QSO morseX11 morseLinux morseOSS
diff --git a/morse.d/alarm.c b/morse.d/alarm.c
new file mode 100644 (file)
index 0000000..25eb9cf
--- /dev/null
@@ -0,0 +1,58 @@
+/* alarm.c -- wakeup calls */
+
+/*
+-- Implementation of alarm.h
+*/
+
+#include <stdio.h>
+#include <signal.h>
+#include <sys/time.h>
+
+static int alarmPending = 0;  /* Nonzero when the alarm is set. */
+
+static void ualarm();
+static void AlarmHandler();
+
+void AlarmSet(time)
+    int time;
+{
+    alarmPending = 1;
+    sigset(SIGALRM, AlarmHandler);
+    ualarm(1000 * time, 0);
+}
+
+/*
+-- If an alarm signal is lurking (due to a prior call to SetAlarm), then
+-- pause until it arrives.  This procedure could have simply been written:
+--   if (alarmPending) pause();
+-- but that allows a potential race condition.
+*/
+void AlarmWait()
+{
+    sighold(SIGALRM);
+    if (alarmPending)
+      sigpause(SIGALRM);
+    sigrelse(SIGALRM);
+}
+
+
+static void ualarm(us)
+    unsigned us;
+{
+    struct itimerval rttimer, old_rttimer;
+
+    rttimer.it_value.tv_sec  = us / 1000000;
+    rttimer.it_value.tv_usec = us % 1000000;
+    rttimer.it_interval.tv_sec  = 0;
+    rttimer.it_interval.tv_usec = 0;
+    if (setitimer(ITIMER_REAL, &rttimer, &old_rttimer)) {
+       perror("ualarm");
+       exit(1);
+    }
+}
+
+
+static void AlarmHandler()
+{
+    alarmPending = 0;
+}
diff --git a/morse.d/alarm.h b/morse.d/alarm.h
new file mode 100644 (file)
index 0000000..33a5fb1
--- /dev/null
@@ -0,0 +1,20 @@
+/* alarm.h -- wakeup call interface */
+
+/*
+-- Routines for using the system interval timer to time beeps.  Useful
+-- for implementing the functions in beep.h on systems that don't provide
+-- a more straightforward BeepWait() equivalent.
+--
+-- These routines use the ALRM signal.
+*/
+
+
+/*
+-- Set the alarm for a time specified in ms.
+*/
+void AlarmSet(time_t);
+
+/*
+-- Wait for the alarm, or return immediately if the alarm isn't set.
+*/
+void AlarmWait(void);
diff --git a/morse.d/beep.h b/morse.d/beep.h
new file mode 100644 (file)
index 0000000..7e1796b
--- /dev/null
@@ -0,0 +1,59 @@
+/* beep.h -- seligman 5/92 */
+
+/*
+-- Machine-dependent code for sounding a beep.
+*/
+
+#ifndef _BEEP_H
+#define _BEEP_H
+
+
+/*
+-- Called exactly once, before any other function in this interface.
+-- Returns nonzero on error.
+*/
+int BeepInit(void);
+
+
+/*
+-- Sound a beep for a time specified in ms.
+-- The volume is in the range [0..100], and the pitch is in Hz.
+--
+-- May return immediately, after the sounding of the beep is completed,
+-- or any time in between.  May be called while a previous beep is still
+-- sounding, in which case the previous beep finishes before the new one
+-- begins. Overall timing will be much better if this routine can return
+-- during the sounding of the beep, especially if it's a "zero-volume beep",
+-- meaning it's really just the timed pause of silence between tones.
+-- ("morse.c" tries to do all its thinking during the pauses between beeps,
+-- mostly in the longer ones between words.)
+--
+-- May use the ALRM signal for timing.
+--
+-- Returns nonzero on error.
+*/
+int Beep(int time, int volume, int pitch);
+
+
+/*
+-- Wait until any currently sounding beeps have completed.
+-- Returns nonzero on error.
+*/
+int BeepWait(void);
+
+
+/*
+-- Clean up any altered state before exiting or suspending the program.
+-- Returns nonzero on error.
+*/
+int BeepCleanup(void);
+
+
+/*
+-- Restore the world when program is resumed after having been suspended.
+-- Returns nonzero on error.
+*/
+int BeepResume(void);
+
+
+#endif /* _BEEP_H */
diff --git a/morse.d/beepLinux.c b/morse.d/beepLinux.c
new file mode 100644 (file)
index 0000000..add29bb
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * From: jmorriso@bogomips.ee.ubc.ca (John Paul Morrison)
+ * Subject: beepLinux.c
+ * To: joe@montebello.soest.hawaii.edu
+ * Date: Fri, 18 Nov 1994 15:32:33 -0800 (PST)
+ * X-Linux: watch for it: Linux 94 aka "Helsinki"
+ * 
+ * beepLinux.c for morse (lightly tested, seems to work fine)
+ */
+
+/*
+ * beep for the Linux console (PC Speaker) 
+ *
+ * (beepSun.c might work if you have a sound card installed and /dev/audio)
+ *
+ * by John Paul Morrison <jmorriso@ve7jpm.ampr.org>
+ *
+ */
+
+/* beepLinux.c -- 11/94 */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/kd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "alarm.h"
+#include "beep.h"
+
+
+static int fd;
+
+int BeepInit()
+{
+  if ((fd = open("/dev/console",O_WRONLY)) == -1)
+    {
+      fprintf(stderr,"You have no permissions to use /dev/console (chmod a+w).\n");
+      return 1;
+    }
+  return 0;
+}
+
+int Beep(time, volume, pitch)
+int time, volume, pitch;
+{
+    int count;
+
+    AlarmWait();
+    if (volume == 0)
+       count = 0;
+    else
+       count = (1193180 / pitch) & 0xffff;
+
+    ioctl(fd, KDMKTONE, (time << 16) | count);
+    AlarmSet(time); 
+    return 0;
+}
+
+int BeepWait()
+{
+    AlarmWait();
+    return 0;
+}
+
+int BeepCleanup()
+{
+    close(fd);
+    return 0;
+}
+
+int BeepResume()
+{
+    return 0;
+}
+
+/*
+ * ---------------------------------------------------------------------------
+ * BogoMIPS Research Labs  --  bogosity research & simulation  --  VE7JPM  -- 
+ * jmorriso@bogomips.ee.ubc.ca ve7jpm@ve7jpm.ampr.org jmorriso@ve7ubc.ampr.org
+ * ---------------------------------------------------------------------------
+ */
diff --git a/morse.d/beepOSS.c b/morse.d/beepOSS.c
new file mode 100644 (file)
index 0000000..6573539
--- /dev/null
@@ -0,0 +1,207 @@
+/* beepOSS.c -- Jacek M. Holeczek 3/2000 */
+/* A lot of code taken from the beepSun.c */
+
+/*
+  Implementation of beep for the Open Sound System /dev/dsp device.
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <sys/soundcard.h>
+#include <math.h>
+
+#include "beep.h"
+
+#define DEVICE_NAME "/dev/dsp"
+#define RATE   (8000)  /* 8 K samples = 1 second */
+#define RAMP   (RATE * 5 / 1000)       /* 5 millisecond ramp */
+#define MAXTIME        (2 * RATE)      /* 2 seconds max tone time */
+
+static int audio = 0;
+static int channels = 0; /* 1=mono, 2=stereo */
+static unsigned char silence[2*MAXTIME];
+static unsigned char soundbuf[2*(MAXTIME - RAMP)];
+static unsigned char ramp_down[2*RAMP];
+
+int BeepInit (void)
+{
+  int i;
+  
+  /*
+   * Initialize the sound of silence
+   */
+  for (i = 0; i < 2*MAXTIME; i++)
+    silence[i] = 128;
+  
+  audio = open(DEVICE_NAME, O_WRONLY, 0);
+  if (audio < 0)
+    {
+      return 1; /* not o.k. */
+    } else {
+      
+      i = 0x00ff0008; /* 255 fragments, 256 bytes each */
+      if (ioctl(audio, SNDCTL_DSP_SETFRAGMENT, &i))
+       {
+         fprintf(stderr, "SNDCTL_DSP_SETFRAGMENT error : 0x%08x\n", i);
+         return 2;
+       }
+      
+      channels = 1; /* 1 channel = mono */
+      if (ioctl(audio, SNDCTL_DSP_CHANNELS, &channels)==-1)
+       {
+         fprintf(stderr, "SNDCTL_DSP_CHANNELS error : %i\n", channels);
+         return 3;
+       }
+      if (channels != 1)
+       {
+         channels = 2; /* 2 channels = stereo */
+         if (ioctl(audio, SNDCTL_DSP_CHANNELS, &channels)==-1)
+           {
+             fprintf(stderr, "SNDCTL_DSP_CHANNELS error : %i\n", channels);
+             return 4;
+           }
+       }
+      if (!(channels == 1 || channels == 2))
+       {
+         fprintf(stderr, "Neither mono nor stereo mode could be set.\n");
+         return 5;
+       }
+      
+      i = AFMT_U8; /* standard unsigned 8 bit audio encoding */
+      if (ioctl(audio, SNDCTL_DSP_SETFMT, &i)==-1)
+       {
+         fprintf(stderr, "SNDCTL_DSP_SETFMT error : %i\n", i);
+         return 6;
+       }
+      
+      i = RATE; /* sampling rate RATE */
+      if (ioctl(audio, SNDCTL_DSP_SPEED, &i)==-1)
+       {
+         fprintf(stderr, "SNDCTL_DSP_SPEED error : %i\n", i);
+         return 7;
+       }
+      
+      write (audio, silence, channels*2);
+      BeepWait();
+      return 0; /* o.k. */
+    }
+}
+
+int Beep (int time, int volume, int pitch)
+{
+  static int      last_pitch = -1, last_n = -1, last_volume = -1;
+  int             i, n, first_len, down_len, cycle;
+  double          dt;
+  
+  /*
+   * Finagle the number of samples
+   */
+  n = (time / 1000.) * RATE;   /* Number samples in tone time */
+  n = n < MAXTIME ? n : MAXTIME;       /* clip to buffer size */
+  n = n < 2 * RAMP ? 2 * RAMP : n;     /* leave room for ramps */
+  
+  /*
+   * Catch stupidity
+   */
+  if (pitch <= 0)
+    volume = 0;
+  
+  if (volume <= 0)
+    {
+      write (audio, silence, channels*n);
+    }
+  else
+    {
+      /*
+       * clip to Nyquist
+       * (Of course this means that if you ask for too high a frequency you
+       * just get silence, since you sample all the zero-crossings of the
+       * sine wave.)
+       */
+      pitch = pitch < RATE / 2 ? pitch : RATE / 2;
+      cycle = ((RATE + 1.e-6) / pitch); /* samples per cycle */
+      
+      if (cycle > MAXTIME / 2)
+       cycle = MAXTIME / 2;
+      
+      /* round down length of (rampup + mesa top) to integral cycle */
+      first_len = ((n - RAMP) / cycle) * cycle;
+      if (first_len < cycle)
+       first_len = cycle;
+      if (first_len > MAXTIME - RAMP)
+       first_len = MAXTIME - RAMP;
+      
+      /* round down length of (rampdown) to integral cycle */
+      down_len = ((RAMP) / cycle) * cycle;
+      if (down_len < cycle)
+       down_len = cycle;
+      if (down_len > RAMP)
+       down_len = RAMP;
+      
+      /*
+       * Can we just reuse what we had before?
+       */
+      if (pitch != last_pitch || n > last_n || volume != last_volume)
+       {
+         last_pitch = pitch;
+         last_n = n;
+         last_volume = volume;
+         
+         dt = 2. * M_PI / cycle;       /* sine scale factor */
+         
+         /* Ramp up; begin with silence */
+         
+         for (i = 0; i < RAMP; i++)
+           {
+             soundbuf[i*channels] = rint( 128. +
+                                          ((float) i / RAMP) * (volume * 1.26) * sin (i * dt)
+                                          );
+             if (channels == 2)
+               soundbuf[i*channels+1] = soundbuf[i*channels];
+           }
+         
+         /* Mesa top */
+         for (i = RAMP; i < first_len; i++)
+           {
+             soundbuf[i*channels] = rint( 128. +
+                                          1. * (volume * 1.26) * sin (i * dt)
+                                          );
+             if (channels == 2)
+               soundbuf[i*channels+1] = soundbuf[i*channels];
+           }
+         
+         /* Ramp down; end with silence */
+         for (i = 0; i < down_len; i++)
+           {
+             ramp_down[i*channels] = rint( 128. +
+                                           (1. - (float) (i + 1) / down_len) * (volume * 1.26) * sin (i * dt)
+                                           );
+             if (channels == 2)
+               ramp_down[i*channels+1] = ramp_down[i*channels];
+           }
+       }
+      
+      write (audio, soundbuf, channels*first_len);
+      write (audio, ramp_down, channels*down_len);
+    }
+  
+  return 0;
+}
+
+int BeepWait (void)
+{
+  ioctl (audio, SNDCTL_DSP_SYNC, 0);
+  return 0;
+}
+
+int BeepCleanup (void)
+{
+  return 0;
+}
+
+int BeepResume (void)
+{
+  return 0;
+}
diff --git a/morse.d/beepX11.c b/morse.d/beepX11.c
new file mode 100644 (file)
index 0000000..dc9c444
--- /dev/null
@@ -0,0 +1,81 @@
+/* beepX11.c -- seligman 5/92 */
+
+/*
+-- Implementation of beep.h for X11.
+--
+-- Compile with the library "-lX11".
+*/
+
+#include <stdio.h>
+#include <X11/Xlib.h>
+#include "alarm.h"
+#include "beep.h"
+
+static Display *dpy = 0;
+static XKeyboardControl initialState;
+
+#define BellFlags (KBBellPercent | KBBellPitch | KBBellDuration)
+
+
+int BeepInit()
+{
+    XKeyboardState state;
+
+    if (! (dpy = XOpenDisplay(0))) {
+       perror("Couldn't open display");
+       return 1;
+    }
+
+    /* Save initial state so it can be restored later. */
+    XGetKeyboardControl(dpy, &state);
+    initialState.bell_duration = state.bell_duration;
+    initialState.bell_percent  = state.bell_percent;
+    initialState.bell_pitch    = state.bell_pitch;
+
+    return 0;
+}
+
+
+int Beep(time, volume, pitch)
+    int time, volume, pitch;
+{
+    XKeyboardControl values;
+
+    AlarmWait();
+
+    if (volume != 0  &&  pitch != 0) {
+       values.bell_duration = time;
+       values.bell_percent  = 100;
+       values.bell_pitch    = pitch;
+
+       XChangeKeyboardControl(dpy, BellFlags, &values);
+       XBell(dpy, volume - 100);
+       XFlush(dpy);
+    }
+
+    AlarmSet(time);
+    return 0;
+}
+
+
+int BeepWait()
+{
+    AlarmWait();
+    return 0;
+}
+
+
+int BeepCleanup()
+{
+    if (dpy != 0) {
+       XChangeKeyboardControl(dpy, BellFlags, &initialState);
+       XFlush(dpy);
+    }
+    return 0;
+}
+
+
+int BeepResume()
+{
+    return 0;
+}
diff --git a/morse.d/morse.c b/morse.d/morse.c
new file mode 100644 (file)
index 0000000..29af690
--- /dev/null
@@ -0,0 +1,1935 @@
+/*
+ * Copyright (c) 1988 Regents of the University of California.
+ * Copyright (c) 1992 Joe Dellinger, University of Hawaii at Manoa
+ * Copyright (c) 2005 Eric S. Raymond.
+ *
+ * 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. 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.
+ */
+/*
+ * 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!
+ *
+ * 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.
+ *
+ * For a more detailed history, see the HISTORY file
+ */
+
+/*
+ * 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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <ctype.h>
+#include <signal.h>
+#include "beep.h"
+
+#define FREQUENCY      800.0
+#define FREQUENCY2     602.0
+#define VOLUME         0.5
+#define ERROR_VOLUME           0.5
+#define WORDS_PER_MINUTE       20.0
+#define MAX_BEHINDNESS 0
+#define ERROR_FREQUENCY        2000.0
+
+static int      whichfrequ = 0;
+static float    frequency1 = FREQUENCY;
+static float    frequency2 = FREQUENCY2;
+static float    frequency;
+static float    volume = VOLUME;
+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      keepquiet = 0;
+static int      dynamicspeed = 0;
+static int      charbychar = 0;
+static int     international = 0;
+static int     allprosigns = 0;
+static int     allpunctuation = 1;
+static int      tryagaincount = 1;
+static float    words_per_minute = WORDS_PER_MINUTE;
+static float   error_frequency = ERROR_FREQUENCY;
+static float   error_volume = ERROR_VOLUME;
+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+1)*10)
+static int      wordlen = MAXWORDLEN;
+static int      wordcount = -1;
+static time_t   starttime;
+static int      timeout = -1;
+static int      testpointer = -1;
+static int      testlength = 0;
+static int      behindness = 0;
+static int      max_behindness = MAX_BEHINDNESS;
+static char     teststring[TESTBUFSZ];
+static int      yourpointer = -1;
+static int      yourlength = 0;
+static char     yourstring[TESTBUFSZ];
+static int      linepos = 0;
+
+/*
+ * 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      errorlog[TWOFIFTYSIX];
+static int      randomfactor[TWOFIFTYSIX];
+static int      randomripe[TWOFIFTYSIX];
+static int      testterminal (void);
+static int      randomletter (void);
+
+/*
+ * Pointers to termcap/terminfo "smso/so" (enter_standout_mode) and "rmso/se"
+ * (exit_standout_mode) terminal string capabilities (initialized respectively
+ * with ANSI sequences "\E[7m" to turn the inverse video mode on and "\E[0m"
+ * to turn all video attributes off).
+ */
+static const char           *enter_standout_mode = "\033[7m";
+static const char           *exit_standout_mode = "\033[0m";
+
+/*
+ * Value of (Wrong - Right), which, if exceeded, will cause the program
+ * to start prompting you. Above MAX_ERROR_THRESHOLD it will never prompt.
+ * Don't let the user bank too much credit for past correct answers;
+ * limit it by min(ERROR_FLOOR, error_threshold).
+ */
+#define MAX_ERROR_THRESHOLD            1000
+#define ERROR_FLOOR                    -3
+static int      error_threshold = MAX_ERROR_THRESHOLD;
+static int      error_floor = ERROR_FLOOR;
+
+/*
+ * 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 LINELENGTH     78
+
+/* 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 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
+
+static void            new_words_per_minute (void);
+static void            dowords (int c);
+static void            morse (int c);
+static void            show (char *s);
+static void            testaddchar (char c);
+static void            youraddchar (char c);
+static void            pollyou (void);
+static void            tone (float hertz, float duration, float amplitude);
+static void            toneflush (void);
+static void            openterminal (void);
+static int             readterminal (char **string);
+static void            closeterminal (void);
+static void            report (void);
+
+static void            die (), suspend ();
+static void            cleanup ();
+
+int
+main (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 ();
+
+    if (argc == 1 && isatty (fileno (stdin)))
+    {
+/*
+ * SELF DOC
+ */
+       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 ("-n NUM (default %d means random length)\n", MAXWORDLEN);
+       printf ("      Make words (groups) NUM characters long.\n");
+       printf ("      Valid values are between %d and %d.\n", 1, MAXWORDLEN);
+       printf ("-R NUM (default 0 means unlimited)\n");
+       printf ("      Set the total time (in minutes) to generate text.\n");
+       printf ("-N NUM (default 0 means unlimited)\n");
+       printf ("      Set the total number of words (groups) to generate.\n");
+       printf ("-C \'STRING\' (default all available characters)\n");
+       printf ("      Select characters to send from this STRING only.\n");
+       printf ("-w words_per_minute (default %g)\n-f frequency_in_hertz (default %g)\n-v volume (zero to one, rather nonlinear, default %g)\n",
+               WORDS_PER_MINUTE, FREQUENCY, VOLUME);
+       printf ("-g alternate_frequency (default %g)\n      (toggles via control-G in input FILE at a word break)\n", FREQUENCY2);
+       printf ("-F Farnsworth_character_words_per_minute\n");
+       printf ("-e    leave off the <SK> 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 ("      (The rightmost space in the printout marks where the average is.\n");
+       printf ("      Farther left spaces separate off blocks of letters that are\n");
+       printf ("      about twice as probable as the average to occur, three times, etc.)\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 ("-q    Quietly resyncs with your input (after you make a mistake).\n");
+       printf ("-p NUM (default 0)\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 ("-E NUM (default %d)\n", MAX_ERROR_THRESHOLD);
+       printf ("      If your count of wrong answers minus right answers for a given character\n");
+       printf ("      exceeds this, the program will start prompting you.\n");
+       printf ("      If %d or above, it will never prompt. (implies -t)\n", MAX_ERROR_THRESHOLD);
+       printf ("-M NUM (default %d)\n", MAX_BEHINDNESS);
+       printf ("      If you get more than this number of characters behind, pause until you\n");
+       printf ("      do your next letter. (1 behind is normal, 0 behind means never pause.)\n");
+       printf ("      (implies -t)\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 ("-A    Add ISO 8850-1 (Latin-1) signs to test set.\n");
+       printf ("-B    Add uncommon punctuation to test set.\n");
+       printf ("-S    Add uncommon prosigns to test set.\n");
+       printf ("-X    Set error volume. Defaults to %g.\n", ERROR_VOLUME);
+       printf ("      Error volume 0 means use console speaker.\n");
+       printf ("-x    Set frequency of error tone, default 2000.0Hz\n");
+       exit (0);
+    }
+
+    for (ii = 0; ii < TWOFIFTYSIX; ii++)
+       code[ii] = NULL;
+
+/* Load in the morse code code */
+    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'] = "--..";
+
+    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) '0'] = "-----";
+
+    /* Punctuation marks */
+    code[(int) '.'] = ".-.-.-";
+    code[(int) ','] = "--..--";
+    code[(int) '?'] = "..--..";
+    code[(int) '/'] = "-..-.";
+    code[(int) '-'] = "-....-";
+
+    if (allpunctuation) {
+    /* Not so commonly used punctuation marks */
+    code[(int) ')'] = "-.--.-";
+    code[(int) '\"'] = ".-..-.";
+    code[(int) '_'] = "..--.-";
+    code[(int) '\''] = ".----.";
+    code[(int) ':'] = "---...";
+    code[(int) ';'] = "-.-.-.";
+    code[(int) '$'] = "...-..-";
+    code[(int) '!'] = "-.-.--";
+    }
+
+    /* Commonly used procedure signs ("prosigns") */
+    code[(int) '+'] = ".-.-.";  /* <AR> end of message */
+    code[(int) '*'] = ".-...";  /* <AS> wait, stand-by */
+    code[(int) '='] = "-...-";  /* <BT> (double dash) pause, break for text */
+    code[(int) '('] = "-.--.";  /* <KN> over-specified station only */
+    code[(int) '%'] = "...-.-"; /* <SK> end of contact, known also as <VA> */
+
+    if (allprosigns) {
+    /* Not so commonly used procedure signs ("prosigns") */
+    code[(int) '^'] = ".-.-";   /* <AA> new line, the same as :a, ae */
+    code[(int) '#'] = "-...-.-";/* <BK> invite receiving station to transmit */
+    code[(int) '&'] = "-.-.-";  /* <KA> attention */
+    code[(int) '@'] = "...-.";  /* <SN> understood */
+    }
+
+    if (international) {
+    /* Not so commonly used international extensions (ISO 8859-1) */
+    code[(int) ((unsigned char)'\344')] = ".-.-";   /* :a (also for ae), the same as <AA> */
+    if (code[(int) '^'] != NULL) code[(int) '^'] = NULL;
+    code[(int) ((unsigned char)'\340')] = ".--.-";  /* `a, oa (danish a with ring over it) */
+    code[(int) ((unsigned char)'\347')] = "----";   /* ch (bar-ch) */
+    code[(int) ((unsigned char)'\360')] = "..--.";  /* -d (eth, overstrike d with -) */
+    code[(int) ((unsigned char)'\350')] = "..-..";  /* `e */
+    code[(int) ((unsigned char)'\361')] = "--.--";  /* ~n */
+    code[(int) ((unsigned char)'\366')] = "---.";   /* :o (also for oe) */
+    code[(int) ((unsigned char)'\374')] = "..--";   /* :u (also for ue) */
+    code[(int) ((unsigned char)'\376')] = ".--..";  /* ]p (thorn, overstrike ] with p) */
+    code[(int) ((unsigned char)'\247')] = ".-.-.."; /* paragraph */
+    }
+
+    for (ii = 0; ii < TWOFIFTYSIX; ii++)
+    {
+       /* Everything starts equally fresh */
+       randomripe[ii] = 0;
+       /* Start out assuming you know how everything sounds */
+       errorlog[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 = WORDS_PER_MINUTE;
+    fwords_per_minute = -1.;
+
+    /* DGHJKLMOPQUVWYZhjkouyz are still available */
+    while ((ch = getopt (argc, argv, "ABC:E:F:IM:N:R:STXabcdef:g:ilmn:p:qrstv:w:x:")) != EOF)
+       switch ((char) ch)
+       {
+       case 'A':
+           international = 1;
+           break;
+       case 'B':
+           allpunctuation = 1;
+           break;
+       case 'C':
+           for (ii = 0; ii < strlen(optarg); ii++)
+             {
+               *(optarg+ii) = tolower(*(optarg+ii));
+             }
+           for (ii = 0; ii < TWOFIFTYSIX; ii++)
+             {
+               if (code[ii] != NULL)
+                 {
+                   if (strchr(optarg, ii) != NULL)
+                     {
+                       randomfactor[ii] = RANDOMBASELEVEL - 1;
+                     } else {
+                       code[ii] = NULL;
+                       randomfactor[ii] = 0;
+                     }
+                 }
+             }
+           break;
+       case 'E':
+           testing = 1;
+           sscanf (optarg, "%d", &error_threshold);
+           if (error_threshold < error_floor)
+               error_floor = error_threshold;
+           break;
+       case 'F':
+           sscanf (optarg, "%f", &fwords_per_minute);
+           break;
+       case 'I':
+           typeaway = LETMESEE;
+           break;
+       case 'M':
+           testing = 1;
+           sscanf (optarg, "%d", &max_behindness);
+           if (max_behindness < 1)
+               max_behindness = 0;
+           break;
+       case 'N':
+           sscanf (optarg, "%d", &wordcount);
+           if (wordcount < 1) wordcount = -1;
+           break;
+       case 'R':
+           sscanf (optarg, "%d", &timeout);
+           timeout *= 60;
+           if (timeout < 1) timeout = -1;
+           break;
+       case 'S':
+           allprosigns = 1;
+           break;
+       case 'T':
+           testing = 1;
+           showtesting = 1;
+           break;
+       case 'X':
+           sscanf (optarg, "%f", &error_volume);
+           break;
+       case 'a':
+           wordsafter = 1;
+           break;
+       case 'b':
+           wordsbefore = 1;
+           break;
+       case 'c':
+           noticebad = 1;
+           break;
+       case 'd':
+           dynamicspeed = 1;
+           break;
+       case 'e':
+           fancyending = 0;
+           break;
+       case 'f':
+           sscanf (optarg, "%f", &frequency1);
+           break;
+       case 'g':
+           sscanf (optarg, "%f", &frequency2);
+           break;
+       case 'i':
+           typeaway = 1;
+           break;
+       case 'l':
+           showletters = 1;
+           break;
+       case 'm':
+           showmorse = 1;
+           break;
+       case 'n':
+           sscanf (optarg, "%d", &wordlen);
+           if (wordlen < 1) wordlen = 1;
+           if (wordlen > MAXWORDLEN) wordlen = MAXWORDLEN;
+           break;
+       case 'p':
+           charbychar = 1;
+           testing = 1;
+           sscanf (optarg, "%d", &tryagaincount);
+           break;
+       case 'q':
+           keepquiet = 1;
+           break;
+       case 'r':
+           randomletters = 1;
+           break;
+       case 's':
+           charbychar = 1;
+           testing = 1;
+           break;
+       case 't':
+           testing = 1;
+           break;
+       case 'v':
+           sscanf (optarg, "%f", &volume);
+           if (volume < 0.)
+               volume = 0.;
+           if (volume > 1.)
+               volume = 1.;
+           break;
+       case 'w':
+           sscanf (optarg, "%f", &words_per_minute);
+           break;
+       case 'x':
+           sscanf (optarg, "%f", &error_frequency);
+           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);
+    }
+
+    time(&starttime);
+
+    sigset (SIGINT, die);
+    sigset (SIGTERM, die);
+    sigset (SIGQUIT, die);
+    sigset (SIGTSTP, suspend);
+
+    if (testing || typeaway)
+    {
+       openterminal ();
+    }
+
+/*
+ * Do .25 seconds of silence initially to give the workstation time to
+ * get settled after the stress of starting this program and opening
+ * up everything.
+ */
+    tone (frequency, .25, 0.);
+    toneflush ();
+
+    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;
+
+           if (timeout > 0)
+             if (difftime(time(NULL),starttime) > (double)timeout)
+               break;
+       }
+    }
+    else if (randomletters)
+    {
+       srand48(time (NULL));
+       randexp = 1. / (1. - 1. / (float) (RANDWORDLEN));
+       while (1)
+       {
+           if ((wordcount == 0) || timeout == 0) break;
+
+           dowords (randomletter ());
+
+           if (wordlen == MAXWORDLEN)
+             {
+               /* 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) (
+                                  (((lrand48() >> 9) + (long) (time (NULL))) >> 4)
+                                  & 0x00FFFFFF);
+               randnum = randnum - randexp * (int) (randnum / randexp);
+               if ((randnum >= 1.) && (linepos != 0)) dowords ((int) ' ');
+             }
+       }
+    }
+    else
+    {
+       if (*argv)
+       {
+           firsttime = 1;
+
+           do
+           {
+               if (!firsttime)
+               {
+                   dowords ((int) ' ');
+               }
+               else
+                   firsttime = 0;
+
+               if ((wordcount == 0) || timeout == 0) break;
+
+               for (p = *argv; *p; ++p)
+                 {
+                   dowords ((int) *p);
+                   if ((wordcount == 0) || timeout == 0) break;
+                 }
+               if ((wordcount == 0) || timeout == 0) break;
+
+           } while (*++argv);
+       }
+       else
+       {
+           while ((ch = getchar ()) != EOF)
+             {
+               dowords (ch);
+               if ((wordcount == 0) || timeout == 0) break;
+             }
+       }
+    }
+
+    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);
+}
+
+static void
+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;
+
+static void
+dowords (int c)
+{
+static int      wordc = 0;
+static char     word[MAXWORDLEN+1];
+char           *wordp;
+int             ii;
+int             againcount = 0;
+int             are_we_repeating;
+
+/*
+ * Increment the line position counter.
+ * If a line gets too long, just cut it off by inserting a new line.
+ */
+    if (c != EOF && c != SILENTEOF && c != FREQU_TOGGLE) linepos++;
+    if (isspace (c) && ((linepos + wordlen) >= LINELENGTH)) c = '\n';
+    if (c == '\n') linepos = 0;
+
+    if (isspace (c) || c == EOF || c == SILENTEOF || c == FREQU_TOGGLE)
+    {
+       if (wordc > 0)
+       {
+           if (wordcount > 0) wordcount--;
+           if (timeout > 0)
+             if (difftime(time(NULL),starttime) > (double)timeout)
+               timeout = 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);
+               linepos = 0;
+           }
+
+/*
+ * Start treating the new word.
+ */
+           if (wordsbefore)
+           {
+               /* Try to keep your out-of-sync text from getting swirled in */
+               if (showtesting)
+                 {
+                   printf ("\n");
+                   linepos = 0;
+                 }
+
+               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:
+               if (testing && !tryingagain && !showletters &&
+                   error_threshold < MAX_ERROR_THRESHOLD &&
+                   errorlog[(int) *wordp] > error_threshold)
+               {
+                   toneflush ();
+                   /* Give them a quick hint */
+                   printf ("[%c]", *wordp);
+                   fflush (stdout);
+
+                   morse (*wordp);
+
+                   toneflush ();
+                   if (!showmorse)
+                   {
+                       /* Erase the hint */
+                       printf ("\b\b\b   \b\b\b");
+                       fflush (stdout);
+                   }
+               }
+               else
+               {
+                   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");
+                               linepos = 0;
+                               /*
+                                * 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 ();
+                       /*
+                        * Stop if we get more than max_behindness ahead.
+                        * max_behindness == 0 means don't worry about them,
+                        * they can be as far behind as they want and we
+                        * won't stop!
+                        */
+                       if (max_behindness > 0)
+                       {
+                           are_we_repeating = 0;
+                           while (behindness >= max_behindness)
+                           {
+#ifdef DEBUG
+                               fprintf (stderr, " (%d) ", behindness);
+#endif
+                               if (are_we_repeating)
+                               {
+                                   /*
+                                    * Pause for a bit so we don't loop too
+                                    * fast
+                                    */
+                                   tone (frequency, catchup_time, 0.);
+                               }
+                               else
+                               {
+                                   are_we_repeating = 1;
+                               }
+                               /* Finish playing whatever we're playing */
+                               toneflush ();
+                               /* And give them another chance */
+                               testterminal ();
+                           }
+                       }
+                   }
+               }
+           }
+
+           toneflush ();
+           if (testing)
+               testterminal ();
+
+           if (wordsafter)
+           {
+               printf (" (%s)", word);
+           }
+
+           if (wordsbefore || wordsafter || showmorse)
+             {
+               if (!showmorse && !testing && linepos)
+                 {
+                   if (wordsafter)
+                     linepos += 3;
+                   else
+                     printf (" ");
+                 } else {
+                   printf ("\n");
+                   linepos = 0;
+                 }
+             }
+           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;
+    }
+
+/*
+ * 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 == wordlen && !(isspace (c) || c == EOF || c == SILENTEOF || c == FREQU_TOGGLE))
+       dowords ((int) ' ');
+}
+
+/*
+ * Don't try to test the person DURING the call into morse!
+ */
+void
+morse (int c)
+{
+    if ((isalpha (c) && (code[tolower(c)] == NULL)) || ((code[(int) '%'] == NULL) && ((c == EOF) || (c == '\004'))))
+      c = ' ';
+
+    if (showletters)
+    {
+        if ((c == EOF) || (c == '\004'))
+         {
+           if (showmorse)
+             {
+               printf ("<SK>");
+             } else {
+               printf ("%%");
+             }
+         }
+       else if (c == '.' && showmorse)
+           printf ("<DOT>");
+       else if (c == '-' && showmorse)
+           printf ("<DASH>");
+       else if (c == '+' && showmorse)
+           printf ("<AR>");
+       else if (c == '*' && showmorse)
+           printf ("<AS>");
+       else if (c == '=' && showmorse)
+           printf ("<BT>");
+       else if (c == '(' && showmorse)
+           printf ("<KN>");
+       else if (c == '%' && showmorse)
+           printf ("<SK>");
+#ifdef ALLPROSIGNS
+       else if (c == '^' && (code[(int) '^'] != NULL) && showmorse)
+           printf ("<AA>");
+       else if (c == '#' && showmorse)
+           printf ("<BK>");
+       else if (c == '&' && showmorse)
+           printf ("<KA>");
+       else if (c == '@' && showmorse)
+           printf ("<SN>");
+#endif
+       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) || (c == '\004'))
+    {
+       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!
+ */
+static void
+show (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.
+ */
+static void
+testaddchar (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++;
+}
+
+static void
+youraddchar (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 ();
+    }
+}
+
+static void
+pollyou (void)
+{
+    int ii, num;
+    char *string;
+
+    num = readterminal (&string);
+
+    for (ii = 0; ii < num; ii++)
+       youraddchar (string[ii]);
+}
+
+static int
+testterminal (void)
+{
+int             testinc, yourinc;
+int             correctchar, yourchar, yourcharnocase;
+int             errorcount;
+int             resync;
+
+    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 (keepquiet > 1)
+             {
+               for (resync = testinc; resync < testlength; resync++)
+                 if ( yourcharnocase == teststring[(testpointer - testlength + 1 + resync + TESTBUFSZ) % TESTBUFSZ])
+                   {
+                     for (; testinc < resync; testinc++)
+                       {
+                         correctchar = teststring[(testpointer - testlength + 1 + testinc + TESTBUFSZ) % TESTBUFSZ];
+                         if (isspace (correctchar) || code[correctchar] == NULL)
+                           {
+                             if (showtesting) printf ("%c", correctchar);
+                           } else {
+                             printf("%s%c%s", enter_standout_mode, correctchar, exit_standout_mode);
+                           }
+                       }
+                     fflush (stdout);
+                     correctchar = teststring[(testpointer - testlength + 1 + testinc + TESTBUFSZ) % TESTBUFSZ];
+                     keepquiet = 1;
+                     break;
+                   }
+             }
+
+           if (yourcharnocase != correctchar)
+           {
+               errorcount++;
+               totalmisscount++;
+
+               /*
+                * Record that you are having trouble with these.
+                */
+               errorlog[correctchar]++;
+               if (code[yourcharnocase] != NULL &&
+                   errorlog[yourcharnocase] < MAX_ERROR_THRESHOLD)
+                   errorlog[yourcharnocase]++;
+
+               if (keepquiet > 0)
+                 {
+                   printf("%s%c%s", enter_standout_mode, correctchar, exit_standout_mode);
+                   fflush (stdout);
+                   keepquiet++;
+                 } else {
+                   /*
+                    * Scold them for their mistake.
+                    */
+                   printf ("\n");
+
+                   if (error_volume) {
+                       /* Beep using the tone generator */
+                       toneflush ();
+                       tone (error_frequency, 0.1, error_volume);
+                   } else
+                       /* Beep using control-G */
+                       printf ("\007");
+                   printf ("%c (%s) for %c (%s)\n",
+                           yourchar, code[yourcharnocase],
+                           correctchar, code[correctchar]);
+                   fflush (stdout);
+                   linepos = 0;
+                 }
+
+               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
+           {
+               /*
+                * Record that you got this right.
+                */
+               if (!tryingagain)
+               {
+                   totalhitcount++;
+                   if (errorlog[correctchar] > error_floor)
+                       errorlog[correctchar]--;
+               }
+
+               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");
+                       linepos = 0;
+                   }
+                   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;
+}
+
+
+/*----------------------------------------*/
+
+static void
+tone (float hertz, float duration, float amplitude)
+{
+    Beep ((int) (duration * 1000), (int) (amplitude * 100), (int) hertz);
+}
+
+
+static void
+toneflush (void)
+{
+    BeepWait ();
+}
+
+
+/*----------------------------------------*/
+
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <termio.h>
+struct termio   oldtermgtty;
+struct termio   termgtty;
+static char    *terminal = "/dev/tty";
+static int      termfd;
+static int      termopen = 0;
+static int      oldflgs, newflgs;
+
+static void
+openterminal (void)
+{
+    /* get parameters and open terminal */
+
+    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 (termfd, TCSETAW, &termgtty);
+    termopen = 1;
+}
+
+static int
+readterminal (char **string)
+{
+/* This must be declared static! */
+static char     line[TESTBUFSZ];
+int             n;
+
+    n = read (termfd, line, sizeof (line) - 1);
+
+    if (n > 0)
+    {
+       line[n] = '\0';
+       *string = line;
+    }
+    else
+       *string = NULL;
+
+    return n;
+}
+
+static void
+closeterminal (void)
+{
+    ioctl (termfd, TCSETAW, &oldtermgtty);
+    close (termfd);
+}
+
+static void
+die (void)
+{
+    cleanup ();
+    exit (1);
+}
+
+static void
+cleanup (void)
+{
+    if (termopen)
+       closeterminal ();
+    BeepCleanup ();
+}
+
+static void
+suspend (int sig)
+{
+    sigset (SIGTSTP, suspend);
+    cleanup ();
+    kill (getpid (), SIGSTOP);
+    if (termopen)
+       openterminal ();
+    BeepResume ();
+}
+
+
+/*----------------------------------------*/
+
+static int
+randomletter (void)
+{
+int             ii;
+int             sum, sum2;
+long            ranspot;
+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 = ((lrand48() >> 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;
+}
+
+static int
+rancomp (const void *elem1, const void *elem2)
+{
+    register int *e1 = (int *)elem1;
+    register int *e2 = (int *)elem2;
+    float           a, b;
+
+
+    a = (randomfactor[(*e1)] + (randomripe[(*e1)] / (float) RIPECOUNT));
+    b = (randomfactor[(*e2)] + (randomripe[(*e2)] / (float) RIPECOUNT));
+
+    if (a == b)
+       return 0;
+    else if (a > b)
+       return -1;
+    else
+       return 1;
+}
+
+static void
+report (void)
+{
+    int ii, jj, count;
+    float sum; 
+    int randomstr[TWOFIFTYSIX];
+
+    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);
+}