2
* Copyright (c) 1988 Regents of the University of California.
3
* Copyright (c) 1992 Joe Dellinger, University of Hawaii at Manoa
4
* Copyright (c) 2005 Eric S. Raymond.
6
* Redistribution and use in source and binary forms, with or without
7
* modification, are permitted provided that the following conditions
9
* 1. Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer.
11
* 2. Redistributions in binary form must reproduce the above copyright
12
* notice, this list of conditions and the following disclaimer in the
13
* documentation and/or other materials provided with the distribution.
14
* 3. Neither the name of the University nor the names of its contributors
15
* may be used to endorse or promote products derived from this software
16
* without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
19
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31
* A morse code practice utility. (Contains those characters that can appear
32
* on the FCC ham license exam.)
34
* Running "morse" without arguments or input gives self-doc.
36
* It doesn't keep PERFECT time, but it seems reasonably close
37
* for reasonable word speeds on my slow SUN IPC!
39
* This program has a long history, see the HISTORY file for details.
43
* Useful for seeing what the interleaved reading and writing loops are
48
* If you want to be overwhelmed with information about the probabilities
49
* of each letter being chosen.
58
#include <sys/types.h>
64
#define FREQUENCY 800.0
65
#define FREQUENCY2 602.0
67
#define ERROR_VOLUME 0.5
68
#define WORDS_PER_MINUTE 20.0
69
#define MAX_BEHINDNESS 0
70
#define ERROR_FREQUENCY 2000.0
72
static int whichfrequ = 0;
73
static float frequency1 = FREQUENCY;
74
static float frequency2 = FREQUENCY2;
75
static float frequency;
76
static float volume = VOLUME;
77
static float dot_time;
78
static float dash_time;
79
static float intra_char_time;
80
static float inter_char_time;
81
static float inter_word_time;
82
static float catchup_time;
83
static int showletters = 0;
84
static int showmorse = 0;
85
static int wordsbefore = 0;
86
static int wordsafter = 0;
87
static int fancyending = 1;
88
static int noticebad = 0;
89
static int testing = 0;
90
static int showtesting = 0;
91
static int keepquiet = 0;
92
static int dynamicspeed = 0;
93
static int charbychar = 0;
94
static int international = 0;
95
static int allprosigns = 0;
96
static int allpunctuation = 1;
97
static int tryagaincount = 1;
98
static float words_per_minute = WORDS_PER_MINUTE;
99
static float error_frequency = ERROR_FREQUENCY;
100
static float error_volume = ERROR_VOLUME;
101
static float fwords_per_minute;
102
static int randomletters = 0;
104
static int typeaway = 0;
106
static int totalhitcount = 0;
107
static int totalmisscount = 0;
108
static int helpmeflag = 0;
110
#define MAXWORDLEN 20
111
#define TESTBUFSZ ((MAXWORDLEN+1)*10)
112
static int wordlen = MAXWORDLEN;
113
static int wordcount = -1;
114
static time_t starttime;
115
static int timeout = -1;
116
static int testpointer = -1;
117
static int testlength = 0;
118
static int behindness = 0;
119
static int max_behindness = MAX_BEHINDNESS;
120
static char teststring[TESTBUFSZ];
121
static int yourpointer = -1;
122
static int yourlength = 0;
123
static char yourstring[TESTBUFSZ];
124
static int linepos = 0;
127
* How many times can a given character not be asked before
128
* kicking up the probability of asking that one by one randomfactor unit.
132
#define TWOFIFTYSIX 256
133
static char *(code[TWOFIFTYSIX]);
134
static int errorlog[TWOFIFTYSIX];
135
static int randomfactor[TWOFIFTYSIX];
136
static int randomripe[TWOFIFTYSIX];
137
static int testterminal (void);
138
static int randomletter (void);
141
* Pointers to termcap/terminfo "smso/so" (enter_standout_mode) and "rmso/se"
142
* (exit_standout_mode) terminal string capabilities (initialized respectively
143
* with ANSI sequences "\E[7m" to turn the inverse video mode on and "\E[0m"
144
* to turn all video attributes off).
146
static const char *enter_standout_mode = "\033[7m";
147
static const char *exit_standout_mode = "\033[0m";
150
* Value of (Wrong - Right), which, if exceeded, will cause the program
151
* to start prompting you. Above MAX_ERROR_THRESHOLD it will never prompt.
152
* Don't let the user bank too much credit for past correct answers;
153
* limit it by min(ERROR_FLOOR, error_threshold).
155
#define MAX_ERROR_THRESHOLD 1000
156
#define ERROR_FLOOR -3
157
static int error_threshold = MAX_ERROR_THRESHOLD;
158
static int error_floor = ERROR_FLOOR;
161
* How many characters behind before it decides you're having
162
* trouble keeping up.
166
#define TOOFARBEHIND 6
168
* If SLOWPOKE or more wpm ticks go by, then it decides you are having lots
169
* of trouble remembering this character, and need to be asked it more
173
/* You aren't slow -- you left and came back! */
174
#define SLOWPOKEMAX (50 * SLOWPOKE)
176
* If FASTPOKE or less wpm ticks go by, then it decides you are good at this
177
* character, and need to be asked it less often.
182
* These control how quickly the dynamicspeed option acts when you are
183
* fast or slow. Easier to slow down than speed up!
185
#define ERRORSLOWER 1.04
186
#define ALOTSLOWER 1.15
187
#define ALITTLESLOWER 1.02
188
#define ALITTLEFASTER 1.02
191
* How many inter_char_time's to give you to answer after the end of
192
* a word before considering that you are not keeping up.
193
* Maximum of 2.3, minimum of 0.
194
* The bigger the value, the easier it is to kick in the "automatic
195
* speedup" when using the "-d" option. The maximum means you have (almost)
196
* right up to the beginning of the next word to answer and still have it
197
* count as keeping up.
199
#define SPORTING_RATIO 1.5
202
* The bigger, the more evenly things start out.
203
* (Must be at least 2)
205
#define RANDOMBASELEVEL 7
207
* RANDOMINCWORSE scales how badly you are punished for being wrong
208
* or taking too long. RANDOMINCBETTER scales how you are rewarded for
209
* answering quickly or being right.
211
#define RANDOMINCWORSE 6
212
#define RANDOMINCBETTER 7
213
#define RANDOMMAX (30 * RANDOMBASELEVEL)
215
* The average length of a random word (chosen using exponential distribution).
216
* After implementing this I'm not so sure an exponential distribution
217
* actually models the distribution of real word lengths in English very well.
218
* It's not too bad, though, and the words themselves are all garbage anyway,
221
#define RANDWORDLEN 3.5
223
/* Put in a newline instead of a space when past this column */
224
#define LINELENGTH 78
226
/* An EOF without the EOF (%) sound */
228
/* Toggle tone frequency on control-G within input file */
229
#define FREQU_TOGGLE ((int)'\007')
232
* If you want the morse code to come out synchronized with the printing
233
* of dots and dashes with the -m option, then define this. The problem
234
* is that then the morse code sounds ratty on slower CPU's.
235
* John Shalamskas (KJ9U) suggested turning the precise morse-code printing
236
* synching off because he didn't like the resulting code quality!
240
static void new_words_per_minute (void);
241
static void dowords (int c);
242
static void morse (int c);
243
static void show (char *s);
244
static void testaddchar (char c);
245
static void youraddchar (char c);
246
static void pollyou (void);
247
static void tone (float hertz, float duration, float amplitude);
248
static void toneflush (void);
249
static void openterminal (void);
250
static int readterminal (char **string);
251
static void closeterminal (void);
252
static void report (void);
254
static void die (), suspend ();
255
static void cleanup ();
258
main (int argc, char **argv)
265
int firsttime, notdoneyet;
267
float randexp, randnum;
268
extern time_t time ();
269
struct sigaction handler;
271
if (argc == 1 && isatty (fileno (stdin)))
277
printf ("morse [options] < text_file\n");
278
printf ("morse [options] words words words\n");
279
printf ("morse [options] -r\n");
280
printf ("morse [options] -i\n");
281
printf ("Options:\n");
282
printf ("-i Play what you type.\n");
283
printf ("-I Like -i but don't turn off keyboard echoing.\n");
284
printf ("-r Generate random text. Starts out slanted towards easy\n");
285
printf (" letters, then slants towards ones you get wrong.\n");
286
printf ("-n NUM (default %d means random length)\n", MAXWORDLEN);
287
printf (" Make words (groups) NUM characters long.\n");
288
printf (" Valid values are between %d and %d.\n", 1, MAXWORDLEN);
289
printf ("-R NUM (default 0 means unlimited)\n");
290
printf (" Set the total time (in minutes) to generate text.\n");
291
printf ("-N NUM (default 0 means unlimited)\n");
292
printf (" Set the total number of words (groups) to generate.\n");
293
printf ("-C \'STRING\' (default all available characters)\n");
294
printf (" Select characters to send from this STRING only.\n");
295
printf ("-w words_per_minute (default %g)\n", WORDS_PER_MINUTE);
296
printf (" actual overall sending speed\n");
297
printf ("-F Farnsworth_character_words_per_minute\n");
298
printf (" If specified, characters are sent at this speed, with extra\n");
299
printf (" spaces inserted to bring the overall speed down to the -w\n");
300
printf (" value. Ignored if not higher than -w.\n");
301
printf ("-f frequency_in_hertz (default %g)\n", FREQUENCY);
302
printf ("-v volume (zero to one, rather nonlinear, default %g)\n",
304
printf ("-g alternate_frequency (default %g)\n", FREQUENCY2);
305
printf (" (toggles via control-G in input FILE at a word break)\n");
306
printf ("-e leave off the <SK> sound at the end\n");
307
printf ("-c complain about illegal characters instead of just ignoring them\n");
308
printf ("-b print each word before doing it\n");
309
printf ("-a print each word after doing it\n");
310
printf ("-l print each letter just before doing it\n");
311
printf ("-m print morse dots and dashes as they sound\n");
313
printf (" (this printing-intensive option slows the wpm down!)\n");
315
printf ("-t Type along with the morse, but don't see what\n");
316
printf (" you're typing (unless you make a mistake).\n");
317
printf (" You are allowed to get ahead as much as you want.\n");
318
printf (" If you get too far behind it will stop and resync with you.\n");
319
printf (" You can force it to resync at the next word end by hitting control-H.\n");
320
printf (" Hit ESC to see how you are doing, control-D to end.\n");
321
printf (" (The rightmost space in the printout marks where the average is.\n");
322
printf (" Farther left spaces separate off blocks of letters that are\n");
323
printf (" about twice as probable as the average to occur, three times, etc.)\n");
324
printf ("-T Like -t but see your characters (after they are played).\n");
325
printf ("-s Stop after each character and make sure you get it right. (implies -t)\n");
326
printf ("-q Quietly resyncs with your input (after you make a mistake).\n");
327
printf ("-p NUM (default 0)\n");
328
printf (" Make you get it right NUM times, for penance. (implies -s)\n");
329
printf (" (Yes, NUM = 0 means you can sin all you want.)\n");
330
printf ("-E NUM (default %d)\n", MAX_ERROR_THRESHOLD);
331
printf (" If your count of wrong answers minus right answers for a given character\n");
332
printf (" exceeds this, the program will start prompting you.\n");
333
printf (" If %d or above, it will never prompt. (implies -t)\n", MAX_ERROR_THRESHOLD);
334
printf ("-M NUM (default %d)\n", MAX_BEHINDNESS);
335
printf (" If you get more than this number of characters behind, pause until you\n");
336
printf (" do your next letter. (1 behind is normal, 0 behind means never pause.)\n");
337
printf (" (implies -t)\n");
338
printf ("-d Dynamically speed up or slow down depending on how you are doing.\n");
339
printf (" (if also -s, then -d _only speeds up_!)\n");
340
printf ("-A Add ISO 8850-1 (Latin-1) signs to test set.\n");
341
printf ("-B Add uncommon punctuation to test set.\n");
342
printf ("-S Add uncommon prosigns to test set.\n");
343
printf ("-X Set error volume. Defaults to %g.\n", ERROR_VOLUME);
344
printf (" Error volume 0 means use console speaker.\n");
345
printf ("-x Set frequency of error tone, default 2000.0Hz\n");
349
for (ii = 0; ii < TWOFIFTYSIX; ii++)
352
/* Load in the morse code code */
353
code[(int) 'a'] = ".-";
354
code[(int) 'b'] = "-...";
355
code[(int) 'c'] = "-.-.";
356
code[(int) 'd'] = "-..";
357
code[(int) 'e'] = ".";
358
code[(int) 'f'] = "..-.";
359
code[(int) 'g'] = "--.";
360
code[(int) 'h'] = "....";
361
code[(int) 'i'] = "..";
362
code[(int) 'j'] = ".---";
363
code[(int) 'k'] = "-.-";
364
code[(int) 'l'] = ".-..";
365
code[(int) 'm'] = "--";
366
code[(int) 'n'] = "-.";
367
code[(int) 'o'] = "---";
368
code[(int) 'p'] = ".--.";
369
code[(int) 'q'] = "--.-";
370
code[(int) 'r'] = ".-.";
371
code[(int) 's'] = "...";
372
code[(int) 't'] = "-";
373
code[(int) 'u'] = "..-";
374
code[(int) 'v'] = "...-";
375
code[(int) 'w'] = ".--";
376
code[(int) 'x'] = "-..-";
377
code[(int) 'y'] = "-.--";
378
code[(int) 'z'] = "--..";
380
code[(int) '1'] = ".----";
381
code[(int) '2'] = "..---";
382
code[(int) '3'] = "...--";
383
code[(int) '4'] = "....-";
384
code[(int) '5'] = ".....";
385
code[(int) '6'] = "-....";
386
code[(int) '7'] = "--...";
387
code[(int) '8'] = "---..";
388
code[(int) '9'] = "----.";
389
code[(int) '0'] = "-----";
391
/* Punctuation marks */
392
code[(int) '.'] = ".-.-.-";
393
code[(int) ','] = "--..--";
394
code[(int) '?'] = "..--..";
395
code[(int) '/'] = "-..-.";
396
code[(int) '-'] = "-....-";
398
if (allpunctuation) {
399
/* Not so commonly used punctuation marks */
400
code[(int) ')'] = "-.--.-";
401
code[(int) '\"'] = ".-..-.";
402
code[(int) '_'] = "..--.-";
403
code[(int) '\''] = ".----.";
404
code[(int) ':'] = "---...";
405
code[(int) ';'] = "-.-.-.";
406
code[(int) '$'] = "...-..-";
407
code[(int) '!'] = "-.-.--";
410
/* Commonly used procedure signs ("prosigns") */
411
code[(int) '+'] = ".-.-."; /* <AR> end of message */
412
code[(int) '*'] = ".-..."; /* <AS> wait, stand-by */
413
code[(int) '='] = "-...-"; /* <BT> (double dash) pause, break for text */
414
code[(int) '('] = "-.--."; /* <KN> over-specified station only */
415
code[(int) '%'] = "...-.-"; /* <SK> end of contact, known also as <VA> */
418
/* Not so commonly used procedure signs ("prosigns") */
419
code[(int) '^'] = ".-.-"; /* <AA> new line, the same as :a, ae */
420
code[(int) '#'] = "-...-.-";/* <BK> invite receiving station to transmit */
421
code[(int) '&'] = "-.-.-"; /* <KA> attention */
422
code[(int) '@'] = "...-."; /* <SN> understood */
426
/* Not so commonly used international extensions (ISO 8859-1) */
427
code[(int) ((unsigned char)'\344')] = ".-.-"; /* :a (also for ae), the same as <AA> */
428
if (code[(int) '^'] != NULL) code[(int) '^'] = NULL;
429
code[(int) ((unsigned char)'\340')] = ".--.-"; /* `a, oa (danish a with ring over it) */
430
code[(int) ((unsigned char)'\347')] = "----"; /* ch (bar-ch) */
431
code[(int) ((unsigned char)'\360')] = "..--."; /* -d (eth, overstrike d with -) */
432
code[(int) ((unsigned char)'\350')] = "..-.."; /* `e */
433
code[(int) ((unsigned char)'\361')] = "--.--"; /* ~n */
434
code[(int) ((unsigned char)'\366')] = "---."; /* :o (also for oe) */
435
code[(int) ((unsigned char)'\374')] = "..--"; /* :u (also for ue) */
436
code[(int) ((unsigned char)'\376')] = ".--.."; /* ]p (thorn, overstrike ] with p) */
437
code[(int) ((unsigned char)'\247')] = ".-.-.."; /* paragraph */
440
for (ii = 0; ii < TWOFIFTYSIX; ii++)
442
/* Everything starts equally fresh */
444
/* Start out assuming you know how everything sounds */
447
if (code[ii] == NULL)
449
/* Ensures these will never be chosen */
450
randomfactor[ii] = 0;
454
/* Start out favoring easy ones */
455
randomfactor[ii] = RANDOMBASELEVEL - strlen (code[ii]);
456
if (randomfactor[ii] < 1)
457
randomfactor[ii] = 1;
461
words_per_minute = WORDS_PER_MINUTE;
462
fwords_per_minute = -1.;
464
/* DGHJKLMOPQUVWYZhjkouyz are still available */
465
while ((ch = getopt (argc, argv, "ABC:E:F:IM:N:R:STXabcdef:g:ilmn:p:qrstv:w:x:")) != EOF)
475
for (ii = 0; ii < strlen(optarg); ii++)
477
*(optarg+ii) = tolower(*(optarg+ii));
479
for (ii = 0; ii < TWOFIFTYSIX; ii++)
481
if (code[ii] != NULL)
483
if (strchr(optarg, ii) != NULL)
485
randomfactor[ii] = RANDOMBASELEVEL - 1;
488
randomfactor[ii] = 0;
495
sscanf (optarg, "%d", &error_threshold);
496
if (error_threshold < error_floor)
497
error_floor = error_threshold;
500
sscanf (optarg, "%f", &fwords_per_minute);
507
sscanf (optarg, "%d", &max_behindness);
508
if (max_behindness < 1)
512
sscanf (optarg, "%d", &wordcount);
513
if (wordcount < 1) wordcount = -1;
516
sscanf (optarg, "%d", &timeout);
518
if (timeout < 1) timeout = -1;
528
sscanf (optarg, "%f", &error_volume);
546
sscanf (optarg, "%f", &frequency1);
549
sscanf (optarg, "%f", &frequency2);
561
sscanf (optarg, "%d", &wordlen);
562
if (wordlen < 1) wordlen = 1;
563
if (wordlen > MAXWORDLEN) wordlen = MAXWORDLEN;
568
sscanf (optarg, "%d", &tryagaincount);
584
sscanf (optarg, "%f", &volume);
591
sscanf (optarg, "%f", &words_per_minute);
594
sscanf (optarg, "%f", &error_frequency);
597
fprintf (stderr, "Type \"morse\" without arguments to get self-doc!\n");
604
if (fwords_per_minute <= 0.)
605
fwords_per_minute = words_per_minute;
606
new_words_per_minute ();
608
frequency = frequency1;
610
if (BeepInit () != 0)
612
fprintf (stderr, "Can't access speaker.\n");
618
handler.sa_handler = die;
619
sigemptyset(&handler.sa_mask);
620
handler.sa_flags = 0;
621
sigaction (SIGINT, &handler, NULL);
622
sigaction (SIGTERM, &handler, NULL);
623
sigaction (SIGQUIT, &handler, NULL);
624
handler.sa_handler = suspend;
625
sigaction (SIGTSTP, &handler, NULL);
627
if (testing || typeaway)
633
* Do 0.25 seconds of silence initially to give the workstation time to
634
* get settled after the stress of starting this program and opening
637
tone (frequency, 0.25, 0);
655
for (jj = 0; jj < yourlength; jj++)
657
yourchar = yourstring[(yourpointer - yourlength + 1 + jj + TESTBUFSZ) % TESTBUFSZ];
659
/* Control-D: finished */
660
if (yourchar == (int) '\004')
667
if (isspace (yourchar))
672
printf ("%c", yourchar);
676
tone (frequency, inter_word_time, 0.);
686
if (difftime(time(NULL),starttime) > (double)timeout)
690
else if (randomletters)
692
srand48(time (NULL));
693
randexp = 1. / (1. - 1. / (float) (RANDWORDLEN));
696
if ((wordcount == 0) || timeout == 0) break;
698
dowords (randomletter ());
700
if (wordlen == MAXWORDLEN)
702
/* Knock a few bits off the top so we're sure it won't overflow */
703
/* Shift a few bits because the lower bits stink */
704
/* Add in the time so it doesn't repeat from run to run */
706
(((lrand48() >> 9) + (long) (time (NULL))) >> 4)
708
randnum = randnum - randexp * (int) (randnum / randexp);
709
if ((randnum >= 1.) && (linepos != 0)) dowords ((int) ' ');
728
if ((wordcount == 0) || timeout == 0) break;
730
for (p = *argv; *p; ++p)
733
if ((wordcount == 0) || timeout == 0) break;
735
if ((wordcount == 0) || timeout == 0) break;
741
while ((ch = getchar ()) != EOF)
744
if ((wordcount == 0) || timeout == 0) break;
759
* WE'RE completely done, and YOU aren't! Force catch up. (Note if
760
* charbychar = YES we won't get here, since we're always caught up
761
* after each character as it comes out.)
763
while (testlength > 0)
765
tone (frequency, catchup_time, 0.);
771
/* Just to be sure! */
774
if (showmorse || wordsbefore || wordsafter || showletters || showtesting)
781
/* If you make any mistakes exit with a return code! */
783
return (totalmisscount > 0);
787
new_words_per_minute ()
789
float wtick, ftick, tick;
791
tick = 60. / (words_per_minute * 50);
794
* In the limit as wpm goes past fwpm, Farnsworth becomes kosher PARIS
796
if (fwords_per_minute <= words_per_minute)
797
ftick = 60. / (words_per_minute * 50);
799
ftick = 60. / (fwords_per_minute * 50);
801
wtick = (50. * tick - 31. * ftick) / 19.;
804
* This time is used when the computer is waiting on you to hit a key; it
805
* is useful to scale the granularity with the real overrall words per
806
* minute. This also serves as a measuring rod to see if you are
807
* responding "fast enough". If you are too slow, then obviously you are
808
* having trouble with that character, and should be given it more OFTEN.
814
* Things between characters and words go at the "remainder" speed,
815
* whatever space you need to make the sped-up Farnsworth characters come
816
* out with the correct overall words per minute.
818
inter_char_time = wtick * 3.;
819
inter_word_time = wtick * 7.;
821
/* Things within the character go at the Farnsworth speed */
822
intra_char_time = ftick;
824
dash_time = ftick * 3.;
827
static int tryingagain = 0, slowpoke = 0;
832
static int wordc = 0;
833
static char word[MAXWORDLEN+1];
837
int are_we_repeating;
840
* Increment the line position counter.
841
* If a line gets too long, just cut it off by inserting a new line.
843
if (c != EOF && c != SILENTEOF && c != FREQU_TOGGLE) linepos++;
844
if (isspace (c) && ((linepos + wordlen) >= LINELENGTH)) c = '\n';
845
if (c == '\n') linepos = 0;
847
if (isspace (c) || c == EOF || c == SILENTEOF || c == FREQU_TOGGLE)
851
if (wordcount > 0) wordcount--;
853
if (difftime(time(NULL),starttime) > (double)timeout)
859
* We have just read in a new complete word from the input, (hopefully)
860
* during the time of an inter-word space minus an inter-char space.
861
* Now let's go back and see what happened with the PREVIOUS word,
862
* the one that we had just finished playing. Did the user keep
866
fprintf (stderr, " [%d] ", behindness);
868
if (testing && dynamicspeed && !charbychar)
871
* (If charbychar then behindness is ALWAYS 0 at this
876
/* You're a speed demon! Speed up a bit, then! */
877
words_per_minute *= ALITTLEFASTER;
878
new_words_per_minute ();
880
else if (behindness > WAYBEHIND)
882
/* You're way behind! Slow way down. */
883
words_per_minute /= ALOTSLOWER;
884
new_words_per_minute ();
886
else if (behindness > BEHIND)
888
/* You're behind! Slow down a bit. */
889
words_per_minute /= ALITTLESLOWER;
890
new_words_per_minute ();
896
* If the user was WAY too far behind stop and catch up with
897
* the "new" word as the first one.
899
if (testing && (behindness > TOOFARBEHIND || helpmeflag))
902
printf ("\nOK, let's restart.\n");
904
printf ("\nYou are too far behind! Let's restart.\n");
909
/* Flush the keyboard buffer */
912
/* Forget the past */
918
/* Give the user a little rest. */
920
printf ("\nWPM now %d\n", (int) (words_per_minute + .5));
923
printf ("\nREADY?\n");
935
* Start treating the new word.
939
/* Try to keep your out-of-sync text from getting swirled in */
948
if (showmorse || showletters || wordsafter || showtesting)
951
for (ii = 0; ii < 16 - (wordc + 2); ii++)
960
if (testing && charbychar)
965
for (wordp = word; *wordp != '\0'; wordp++)
970
if (testing && !tryingagain && !showletters &&
971
error_threshold < MAX_ERROR_THRESHOLD &&
972
errorlog[(int) *wordp] > error_threshold)
975
/* Give them a quick hint */
976
printf ("[%c]", *wordp);
985
printf ("\b\b\b \b\b\b");
1001
while (behindness > 0)
1003
if (testterminal () && tryagaincount > 0)
1006
* OOPS! They got it WRONG! MAKE THEM TRY
1009
printf ("Try again.\n");
1012
* Yeah I know gotos are inelegant but I
1013
* don't feel like figuring out the "elegant"
1014
* way to do this right now.
1016
againcount = tryagaincount - 1;
1023
* They got it right, or they didn't answer
1029
* They are STILL thinking, the
1030
* slowpokes. Wait a bit before trying
1033
tone (frequency, catchup_time, 0.);
1035
* Keep track of how long they're taking
1038
if (slowpoke < SLOWPOKEMAX)
1042
else if (dynamicspeed && !slowpoke && !tryingagain)
1045
* They got it right without errors the
1046
* first time and we didn't have to wait
1047
* for them! A speed demon! Speed up a
1050
words_per_minute *= ALITTLEFASTER;
1051
new_words_per_minute ();
1056
/* Insufficient penance? */
1067
* Stop if we get more than max_behindness ahead.
1068
* max_behindness == 0 means don't worry about them,
1069
* they can be as far behind as they want and we
1072
if (max_behindness > 0)
1074
are_we_repeating = 0;
1075
while (behindness >= max_behindness)
1078
fprintf (stderr, " (%d) ", behindness);
1080
if (are_we_repeating)
1083
* Pause for a bit so we don't loop too
1086
tone (frequency, catchup_time, 0.);
1090
are_we_repeating = 1;
1092
/* Finish playing whatever we're playing */
1094
/* And give them another chance */
1108
printf (" (%s)", word);
1111
if (wordsbefore || wordsafter || showmorse)
1113
if (!showmorse && !testing && linepos)
1124
else if (showletters || showtesting)
1126
if (c != EOF && c != SILENTEOF && c != FREQU_TOGGLE)
1137
* WHEW! FINISHED QUEUEING THE WORD FOR PLAYING!
1138
* Now finish up all the other sundry details...
1141
/* Flush the output printing queue... */
1144
* Pause for a bit; this gives the user a sporting chance at
1145
* catching up with us.
1147
tone (frequency, SPORTING_RATIO * inter_char_time, 0.);
1149
/* Start sounding an inter-word space */
1150
tone (frequency, inter_word_time - SPORTING_RATIO * inter_char_time, 0.);
1152
/* While that silence is playing check if the user has caught up. */
1156
/* We finished this word; reset the word character count */
1159
else if (!(wordsbefore || wordsafter || showmorse)
1161
(showletters || showtesting))
1163
if (c != EOF && c != SILENTEOF && c != FREQU_TOGGLE)
1178
else if (c == SILENTEOF)
1182
else if (c == FREQU_TOGGLE)
1184
/* Switch to the other frequency */
1185
/* (Won't work from keyboard, only from a file.) */
1186
whichfrequ = 1 - whichfrequ;
1190
frequency = frequency2;
1194
frequency = frequency1;
1205
* If a word gets too long, just cut it off by inserting a space.
1206
* Just call ourselves with the character we wish we'd gotten...
1208
if (wordc == wordlen && !(isspace (c) || c == EOF || c == SILENTEOF || c == FREQU_TOGGLE))
1209
dowords ((int) ' ');
1213
* Don't try to test the person DURING the call into morse!
1218
if ((isalpha (c) && (code[tolower(c)] == NULL)) || ((code[(int) '%'] == NULL) && ((c == EOF) || (c == '\004'))))
1223
if ((c == EOF) || (c == '\004'))
1232
else if (c == '.' && showmorse)
1234
else if (c == '-' && showmorse)
1236
else if (c == '+' && showmorse)
1238
else if (c == '*' && showmorse)
1240
else if (c == '=' && showmorse)
1242
else if (c == '(' && showmorse)
1244
else if (c == '%' && showmorse)
1247
else if (c == '^' && (code[(int) '^'] != NULL) && showmorse)
1249
else if (c == '#' && showmorse)
1251
else if (c == '&' && showmorse)
1253
else if (c == '@' && showmorse)
1265
testaddchar (c - (isupper (c) ? 'A' : 'a') + 'a');
1266
show (code[c - (isupper (c) ? 'A' : 'a') + 'a']);
1268
else if ((c == EOF) || (c == '\004'))
1270
show (code[(int) '%']);
1272
else if (code[c] != NULL)
1280
/* Oops! This letter is junk! */
1289
/* Simulate a stumble */
1290
tone (frequency, 2. * inter_word_time, 0.);
1296
/* Wipe out what we just printed */
1308
/* And replace it with an error message */
1309
printf ("<UNKNOWN_CHARACTER>");
1313
/* Give the error call */
1317
tone (frequency, inter_word_time, 0.);
1325
tone (frequency, inter_char_time - intra_char_time, 0.);
1330
* Don't try to test the person WHILE doing dots and dashes!
1337
while ((c = *s++) != '\0')
1339
tone (frequency, intra_char_time, 0.);
1349
tone (frequency, dot_time, volume);
1352
tone (frequency, dash_time, volume);
1368
* This only gets passed valid characters: ones
1369
* that have a morse code associated with them
1370
* or ones for which isspace(c) is true.
1373
testaddchar (char c)
1375
testpointer = (testpointer + 1) % TESTBUFSZ;
1376
teststring[testpointer] = c;
1378
fprintf (stderr, " (%c,%d,%d) ", c, testlength, behindness);
1381
if (testlength > TESTBUFSZ)
1383
fprintf (stderr, "\n\nInput buffer queue overflow! Make TESTBUFSZ bigger!\n");
1384
fprintf (stderr, "(Or don't fall so far behind)\n");
1390
* Since you are never asked to type spaces (you can type them if
1391
* you want, but they are ignored) spaces in the input file don't
1392
* count against your "behindness".
1399
youraddchar (char c)
1401
yourpointer = (yourpointer + 1) % TESTBUFSZ;
1402
yourstring[yourpointer] = c;
1404
fprintf (stderr, " <%c,%d> ", c, yourlength);
1407
if (yourlength > TESTBUFSZ)
1409
fprintf (stderr, "\n\nKeyboard typeahead buffer queue overflow! Make TESTBUFSZ bigger!\n");
1410
fprintf (stderr, "(Or don't type so far ahead... how did you expect to get them right anyway?)\n");
1422
num = readterminal (&string);
1424
for (ii = 0; ii < num; ii++)
1425
youraddchar (string[ii]);
1431
int testinc, yourinc;
1432
int correctchar, yourchar, yourcharnocase;
1439
* There is nothing in the input file queue right now,
1440
* so we can't process any of your keystrokes.
1441
* Defer processing until we can catch up with YOU!
1443
if (testlength == 0)
1446
/* We're ready for you; but are you ready for us? */
1450
* Process your entries and the input queue entries in parallel
1452
if (yourlength > 0 && testlength > 0)
1454
for (testinc = 0, yourinc = 0;
1455
testinc < testlength && yourinc < yourlength;
1456
testinc++, yourinc++)
1458
correctchar = teststring[(testpointer - testlength + 1 + testinc + TESTBUFSZ) % TESTBUFSZ];
1461
* The latter half of this if shouldn't be necessary, but just in
1464
if (isspace (correctchar) || code[correctchar] == NULL)
1468
printf ("%c", correctchar);
1472
/* White space doesn't count for "behindness" */
1474
/* The _other_ pointer wasn't used; don't increment it. */
1477
/* Short circuit the loop */
1482
yourchar = yourstring[(yourpointer - yourlength + 1 + yourinc + TESTBUFSZ) % TESTBUFSZ];
1483
if (isalpha (yourchar))
1484
yourcharnocase = yourchar - (isupper (yourchar) ? 'A' : 'a') + 'a';
1486
yourcharnocase = yourchar;
1488
/* Did you type something rude? If so, just ignore it. */
1489
if (isspace (yourchar) || code[yourcharnocase] == NULL)
1491
/* ESCAPE: dump status info */
1492
/* Control-D: dump status info and then bye bye */
1493
/* Control-H: force restart */
1494
if (yourchar == '\033' || yourchar == (int) '\004')
1498
if (yourchar == (int) '\004')
1501
else if (yourchar == '\b')
1506
/* The _other_ pointer wasn't used; don't increment it. */
1508
/* Short circuit the loop */
1514
for (resync = testinc; resync < testlength; resync++)
1515
if ( yourcharnocase == teststring[(testpointer - testlength + 1 + resync + TESTBUFSZ) % TESTBUFSZ])
1517
for (; testinc < resync; testinc++)
1519
correctchar = teststring[(testpointer - testlength + 1 + testinc + TESTBUFSZ) % TESTBUFSZ];
1520
if (isspace (correctchar) || code[correctchar] == NULL)
1522
if (showtesting) printf ("%c", correctchar);
1524
printf("%s%c%s", enter_standout_mode, correctchar, exit_standout_mode);
1528
correctchar = teststring[(testpointer - testlength + 1 + testinc + TESTBUFSZ) % TESTBUFSZ];
1534
if (yourcharnocase != correctchar)
1540
* Record that you are having trouble with these.
1542
errorlog[correctchar]++;
1543
if (code[yourcharnocase] != NULL &&
1544
errorlog[yourcharnocase] < MAX_ERROR_THRESHOLD)
1545
errorlog[yourcharnocase]++;
1549
printf("%s%c%s", enter_standout_mode, correctchar, exit_standout_mode);
1554
* Scold them for their mistake.
1559
/* Beep using the tone generator */
1561
tone (error_frequency, 0.1, error_volume);
1563
/* Beep using control-G */
1565
printf ("%c (%s) for %c (%s)\n",
1566
yourchar, code[yourcharnocase],
1567
correctchar, code[correctchar]);
1574
/* Give them a bit of time to think about their error */
1575
tone (frequency, inter_word_time, 0.);
1578
if (dynamicspeed && !charbychar)
1581
* Slow down. Doesn't make sense to slow down for errors,
1582
* though, if you've got all the time you want to think
1585
words_per_minute /= ERRORSLOWER;
1586
new_words_per_minute ();
1588
if (randomletters && !tryingagain)
1591
* Ask ones that confused you more often!
1593
if (code[yourcharnocase] != NULL)
1595
randomfactor[yourcharnocase] += (3 * RANDOMINCWORSE / 2);
1596
if (randomfactor[yourcharnocase] > RANDOMMAX)
1597
randomfactor[yourcharnocase] = RANDOMMAX;
1600
randomfactor[correctchar] += RANDOMINCWORSE * 2;
1601
if (randomfactor[correctchar] > RANDOMMAX)
1602
randomfactor[correctchar] = RANDOMMAX;
1608
* Record that you got this right.
1613
if (errorlog[correctchar] > error_floor)
1614
errorlog[correctchar]--;
1619
printf ("%c", yourchar);
1623
if (randomletters && !tryingagain)
1625
if (slowpoke == SLOWPOKEMAX)
1627
printf ("\nNice to have you back again, I was getting bored!\n");
1630
else if (slowpoke >= SLOWPOKE * 3)
1633
* Did you take too long thinking about it? If so,
1634
* you probably need to be asked this one more
1637
randomfactor[correctchar] += (3 * RANDOMINCWORSE / 2);
1638
if (randomfactor[correctchar] > RANDOMMAX)
1639
randomfactor[correctchar] = RANDOMMAX;
1642
* Hits this slow shouldn't count! You were obviously
1643
* just guessing! (But it doesn't count as an error
1648
else if (slowpoke > FASTPOKE)
1650
randomfactor[correctchar] +=
1651
(slowpoke * RANDOMINCWORSE) / (2 * SLOWPOKE);
1652
if (randomfactor[correctchar] > RANDOMMAX)
1653
randomfactor[correctchar] = RANDOMMAX;
1655
else if (slowpoke <= (FASTPOKE / 2))
1658
* Ask ones that you quickly answer correctly less
1661
randomfactor[correctchar] -= (3 * RANDOMINCBETTER / 2);
1663
* Don't let randomfactor hit 0, or you'll NEVER be
1664
* asked this one AGAIN!
1666
if (randomfactor[correctchar] < 1)
1667
randomfactor[correctchar] = 1;
1669
else if (slowpoke <= FASTPOKE)
1671
randomfactor[correctchar] -= (RANDOMINCBETTER / 2);
1672
if (randomfactor[correctchar] < 1)
1673
randomfactor[correctchar] = 1;
1679
testlength -= testinc;
1680
behindness -= testinc;
1681
yourlength -= yourinc;
1685
* If there are some extra white space characters in the input queue
1686
* it's OK, we'll get to them next time or we'll clean them out at the
1693
/*----------------------------------------*/
1696
tone (float hertz, float duration, float amplitude)
1698
Beep ((int) (duration * 1000), (int) (amplitude * 100), (int) hertz);
1709
/*----------------------------------------*/
1711
#include <sys/ioctl.h>
1713
#include <termios.h>
1714
struct termios oldtermgtty;
1715
struct termios termgtty;
1716
static char *terminal = "/dev/tty";
1718
static int termopen = 0;
1719
static int oldflgs, newflgs;
1724
/* get parameters and open terminal */
1726
termfd = open (terminal, O_RDWR | O_NDELAY, 0);
1727
tcgetattr(termfd, &termgtty);
1728
oldtermgtty = termgtty;
1729
if (typeaway != LETMESEE)
1730
termgtty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
1731
termgtty.c_lflag &= ~ICANON;
1732
termgtty.c_cc[VMIN] = 1;
1733
termgtty.c_cc[VTIME] = 0;
1734
tcsetattr(termfd, TCSADRAIN, &termgtty);
1739
readterminal (char **string)
1741
/* This must be declared static! */
1742
static char line[TESTBUFSZ];
1745
n = read (termfd, line, sizeof (line) - 1);
1759
closeterminal (void)
1761
tcsetattr(termfd, TCSADRAIN, &oldtermgtty);
1783
struct sigaction handler;
1785
sigemptyset(&handler.sa_mask);
1786
handler.sa_flags = 0;
1787
handler.sa_handler = suspend;
1788
sigaction (SIGTSTP, &handler, NULL);
1790
kill (getpid (), SIGSTOP);
1797
/*----------------------------------------*/
1805
static int lasttime = -1;
1806
static long norepeat;
1809
* This keeps the not-so-random random number generator from ignoring
1810
* certain characters forever!
1812
norepeat = ((long) time (NULL) / 31) % 17291;
1815
* All the usable letters get one unit riper.
1817
for (ii = 0; ii < TWOFIFTYSIX; ii++)
1819
if (randomfactor[ii] > 0)
1822
fprintf (stderr, "%c: %d %d\n",
1823
(char) ii, randomfactor[ii], randomripe[ii]);
1830
for (ii = 0; ii < TWOFIFTYSIX; ii++)
1831
sum += (randomfactor[ii] + (int) (randomripe[ii] / RIPECOUNT));
1834
* The low bits of random aren't very random, I don't care WHAT
1835
* the manual claims.
1839
ranspot = ((lrand48() >> 4) % sum + norepeat) % sum;
1842
for (ii = 0; ii < TWOFIFTYSIX - 1; ii++)
1844
sum2 += (randomfactor[ii] + (int) (randomripe[ii] / RIPECOUNT));
1849
/* Do it again if you got the same as last time! */
1850
} while (ii == lasttime);
1852
/* This one is FRESH again. */
1854
/* Remember for next time. */
1861
rancomp (const void *elem1, const void *elem2)
1863
register int *e1 = (int *)elem1;
1864
register int *e2 = (int *)elem2;
1868
a = (randomfactor[(*e1)] + (randomripe[(*e1)] / (float) RIPECOUNT));
1869
b = (randomfactor[(*e2)] + (randomripe[(*e2)] / (float) RIPECOUNT));
1884
int randomstr[TWOFIFTYSIX];
1886
printf ("\nCurrent words per minute: %.1f\n", words_per_minute);
1888
printf ("Total hits %d, misses %d", totalhitcount, totalmisscount);
1889
if (totalmisscount > 0)
1890
printf (", hit per miss ratio %.1f\n", (float) totalhitcount / (float) totalmisscount);
1896
printf ("Most to least frequent choices:\n");
1899
for (ii = 0; ii < TWOFIFTYSIX; ii++)
1901
if (randomfactor[ii] > 0)
1903
sum += (randomfactor[ii] + (randomripe[ii] / (float) RIPECOUNT));
1904
randomstr[count] = ii;
1909
qsort ((char *) randomstr, count, sizeof (randomstr[0]), rancomp);
1911
for (ii = 0; ii < count; ii++)
1914
* Insert a space for each jump across an integer.
1915
* The normalization (count/sum) ensures that if all
1916
* letters were equally probable, they would all have value 1.
1917
* Since they are not generally equally probable, then 1 is just the average.
1918
* Thus the rightmost space in the printout marks where the average is.
1919
* Further left spaces separate off blocks of letters that are approximately
1920
* twice as probable as the average, three times, etc.
1926
(randomfactor[randomstr[ii - 1]] + (randomripe[randomstr[ii - 1]] / (float) RIPECOUNT))
1929
(randomfactor[randomstr[ii]] + (randomripe[randomstr[ii]] / (float) RIPECOUNT))
1934
printf ("%c", (char) randomstr[ii]);
1940
* So you don't get penalized for being "slow" after this.
1943
slowpoke = SLOWPOKEMAX + 1;