From: Eric S. Raymond Date: Mon, 24 Jan 2005 20:15:54 +0000 (+0000) Subject: Move morse into its own subdirectory. X-Git-Tag: 2.0~3 X-Git-Url: http://thyrsus.com/gitweb/?a=commitdiff_plain;h=4ef0b7aac55de1956825b58a79ed3f5ad32d9be3;p=morse-classic.git Move morse into its own subdirectory. --- diff --git a/morse.d/Makefile b/morse.d/Makefile new file mode 100644 index 0000000..b0ab64d --- /dev/null +++ b/morse.d/Makefile @@ -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 index 0000000..25eb9cf --- /dev/null +++ b/morse.d/alarm.c @@ -0,0 +1,58 @@ +/* alarm.c -- wakeup calls */ + +/* +-- Implementation of alarm.h +*/ + +#include +#include +#include + +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 index 0000000..33a5fb1 --- /dev/null +++ b/morse.d/alarm.h @@ -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 index 0000000..7e1796b --- /dev/null +++ b/morse.d/beep.h @@ -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 index 0000000..add29bb --- /dev/null +++ b/morse.d/beepLinux.c @@ -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 + * + */ + +/* beepLinux.c -- 11/94 */ + +#include +#include +#include +#include +#include +#include +#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 index 0000000..6573539 --- /dev/null +++ b/morse.d/beepOSS.c @@ -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 +#include +#include +#include +#include +#include + +#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 index 0000000..dc9c444 --- /dev/null +++ b/morse.d/beepX11.c @@ -0,0 +1,81 @@ +/* beepX11.c -- seligman 5/92 */ + +/* +-- Implementation of beep.h for X11. +-- +-- Compile with the library "-lX11". +*/ + +#include +#include +#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 index 0000000..29af690 --- /dev/null +++ b/morse.d/morse.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#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 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) '+'] = ".-.-."; /* end of message */ + code[(int) '*'] = ".-..."; /* wait, stand-by */ + code[(int) '='] = "-...-"; /* (double dash) pause, break for text */ + code[(int) '('] = "-.--."; /* over-specified station only */ + code[(int) '%'] = "...-.-"; /* end of contact, known also as */ + + if (allprosigns) { + /* Not so commonly used procedure signs ("prosigns") */ + code[(int) '^'] = ".-.-"; /* new line, the same as :a, ae */ + code[(int) '#'] = "-...-.-";/* invite receiving station to transmit */ + code[(int) '&'] = "-.-.-"; /* attention */ + code[(int) '@'] = "...-."; /* understood */ + } + + if (international) { + /* Not so commonly used international extensions (ISO 8859-1) */ + code[(int) ((unsigned char)'\344')] = ".-.-"; /* :a (also for ae), the same as */ + 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 (""); + } else { + printf ("%%"); + } + } + else if (c == '.' && showmorse) + printf (""); + else if (c == '-' && showmorse) + printf (""); + else if (c == '+' && showmorse) + printf (""); + else if (c == '*' && showmorse) + printf (""); + else if (c == '=' && showmorse) + printf (""); + else if (c == '(' && showmorse) + printf (""); + else if (c == '%' && showmorse) + printf (""); +#ifdef ALLPROSIGNS + else if (c == '^' && (code[(int) '^'] != NULL) && showmorse) + printf (""); + else if (c == '#' && showmorse) + printf (""); + else if (c == '&' && showmorse) + printf (""); + else if (c == '@' && showmorse) + printf (""); +#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 (""); + 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 +#include +#include +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); +}