-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmorse.c
435 lines (407 loc) · 17.7 KB
/
morse.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
/*** morse - an automatic morse code teaching machine
/*
/* Copyright (c) 1978 Howard Cunningham all rights reserved
/* Copyright (c) 1988 Jim Wilson
/*
/* Revision History: 04/18/78 initial pascal version adapted from 8080
/* 12/04/81 corrected typo (div for mod in select)
/* 06/18/88 translated to C
/* 07/14/98 corrected accelerated character introduction
/* omitted in translation to C
/*
/* Reference: "A Fully Automatic Morse Code Teaching Machine",
/* QST, (May 1977), ARRL, Newington CT
*/
#include <stdio.h>
/*** tweekable parameters and magic numbers */
#define WPM 15 /* Initial codespeed in words-per-minute */
#define isdah(c) (c&1) /* Mask l.s.b. to test dot or dash */
#define TONE_ON 1162 /* Period in microseconds (1Khz) */
#define TONE_OFF 0 /* Special value for silent period */
#define NLINE 25 /* Number of lines on PC screen and */
#define LINLEN 72 /* number of characters on each line */
#define BARHT (NLINE-6) /* Maximum height of error rate bars */
#define GOOD 0 /* The best one can do */
#define BAD 255 /* The worst one can do */
#define DIT(wpm) (1395/(wpm)) /* Dit length for beep() - roughly in mS */
#define BRGRY 176 /* Bargraph character for inactive letters */
#define BRCHR 219 /* Bargraph character for active latters */
/*** display character attributes for various screen regions
*
* These are 8-bit values left-justified into a 16-bit field (as
* required by pc(), prc() in beep.s). For example, with a color-
* capable display in one of its text modes:
*
* FrgbIRGB 00000000
*
* where 'f' is set to flash (or blink) the character.
* 'rgb' are set to specify the presense of red,
* green and/or blue in the background.
* 'I' is set to "intensify" the character
* 'RGB' are set to specify the presense of red,
* green and/or blue in the forground.
*/
#define NORM 0x1E00 /* Bargraph, herald: Intensified Y/Blue */
#define HLGT 0x1F00 /* Active characters: intensified W/Blue */
#define GREY 0x1700 /* Inactive characters, W/Blue */
#define CWIN 0x0700 /* Code window, white on black */
/*** external functions from beep.s or the C runtime library */
extern srand(), rand(); /* seed, return random number (in C runtime) */
extern beep(); /* Sound tone for an interval (in beep.s) */
extern unsigned ticks(); /* Return mS since last call (in beep.s) */
extern char resp(); /* nonblocking keyboard input (in beep.s) */
extern unsigned tod(); /* DOS raw time-of-day counter (for srand()) */
unsigned num = 2; /* Characters introduced so far (start with 2) */
unsigned dit = DIT(20); /* Default to 20 wpm */
/*** Letter[] holds character codes and error info for each letter
/* MAXNUM is the largest size of the active alphabet.
/* The 0th element is not part of the alphabet. It is used to
/* store an overall error indication for convenient display.
*/
#define MAXNUM (sizeof(letter)/sizeof(struct lt)-1) /* maximum alphabet */
#define OVERALL letter[0] /* for easy graphing!! */
struct lt {char ascii; char morse; unsigned char error;} letter[] = {
{'*', 0, GOOD},
{'Q', 033, BAD}, {'7', 043, BAD}, {'Z', 023, BAD}, {'G', 013, BAD},
{'0', 077, BAD}, {'9', 057, BAD}, {'8', 047, BAD}, {'O', 017, BAD},
{'1', 076, BAD}, {'J', 036, BAD}, {'P', 026, BAD}, {'W', 016, BAD},
{'L', 022, BAD}, {'R', 012, BAD}, {'A', 006, BAD}, {'M', 007, BAD},
{'6', 041, BAD}, {'B', 021, BAD}, {'X', 031, BAD}, {'D', 011, BAD},
{'Y', 035, BAD}, {'C', 025, BAD}, {'K', 015, BAD}, {'N', 005, BAD},
{'2', 074, BAD}, {'3', 070, BAD}, {'F', 024, BAD}, {'U', 014, BAD},
{'4', 060, BAD}, {'5', 040, BAD}, {'V', 030, BAD}, {'H', 020, BAD},
{'S', 010, BAD}, {'I', 004, BAD}, {'T', 003, BAD}, {'E', 002, BAD},
};
/*** main - teach morse code
/*
/* This program repeatedly selects a letter and teaches it to the
/* student. The student can request an evaluation and/or terminate
/* the session.
*/
main(int argc, char *argv[]) {
herald(); /* Print program herald */
srand(tod()); /* seed random number generator */
bgs(); /* Show bargraph on screen */
do {
pms(CWIN, "\013\n\n\n\n"); /* Clear code window */
beep(TONE_OFF, 600); /* Take a breath before we start */
do ; while (teach(select())); /* teach letters */
} while (menu()); /* grade and perhaps quit */
}
/*** herald - emit program proclamation
/*
/* Herald() displays a copyright message and pauses long enough for
/* the student to copy down an important URL (if she wants).
*/
herald() {
pms(NORM, "\f\n\n\n\n\n\n\n\n"
"\230Morse Code Training Program\n"
"\222(c) 1998 Ward Cunningham and Jim Wilson\n"
"\222Permission granted to distribute freely\n"
"\227without profit or modification\n\n"
"\227See http://c2.com/~ward/morse/\n\n\n\n\n\n"
"\217Try to type the character before the computer.\n"
"\226Or, press Enter to take a break.");
beep(TONE_OFF, 3000);
}
/*** weight - return weighted sum of two values
/*
/* Weight() is passed two unsigned character parameters.
/* It returns a weighted average of the two values.
/*
/* Calling Sequence: average = weight(v1, v2);
/*
/* where (unsigned char) "average", "v1" and "v2" are related:
/*
/* average = .875 * v1 + .125 * v2
*/
unsigned weight(unsigned v1, unsigned v2) {
return ((7*v1 + v2 + 4) / 8); /* "+ 4" forces rounding */
}
/*** teach - teach a morse letter
/*
/* Teach() is passed an index to a letter. It sends the letter
/* in Morse on the PC's speaker and patiently waits for the
/* student to press the corresponding key. If too much time
/* is taken, teach() gives a hint and resends the character
/* until the student finally gets the answer right. The student
/* is graded on his performance.
/*
/* Teach() returns a flag indicating that the student wants more.
/*
/* Calling Sequence: if (teach(lesson)) ...;
/*
/* where (int) "lesson" is an index into the array letters[].
/* "..." only if the student want's another character.
*/
teach(int lesson) { /* letter[] index */
register struct lt *l; /* Pointer into letter[] */
unsigned time; /* time required for answer */
unsigned score; /* GOOD or BAD for this letter */
int guess, answer; /* student's answer, real solution */
static int column = 0; /* Characters remaining on line */
static unsigned give = 3500; /* Milliseconds to wait for answer */
l = &letter[lesson]; /* Point register at lesson */
answer = l->ascii; /* Get solution to problem */
score = GOOD; /* Assume the best */
do {
if (column <= 0) { /* If at end-of-line */
pc('\n'+CWIN); /* output newline and */
column = LINLEN; /* rearm counter */
}
send(l->morse); /* Send Morse character. */
while (guess = resp()) /* Flush typeahead, but */
if (guess == '\r') /* if student want's a break, */
return column = 0; /* grant the wish */
ticks(); time = 0; /* Reset stopwatch. */
do { /* Loop to wait for answer: */
guess = resp(); /* Get student response (if any) */
if (guess == answer) /* If correct answer, */
break; /* stop and update gradebook */
if (guess == '\r') /* If student wants break, reset column */
return column = 0; /* count (for next lesson), return */
time += ticks(); /* Add elapsed time */
} while (time <= give); /* Stop if student too slow */
if (guess != answer) /* If ever time out w.o. correct */
score = BAD; /* answer, deduct some points */
pc(answer+CWIN); /* Echo correct answer */
pc(' '+CWIN); column -= 2; /* and adorn it with a blank */
give = weight(give, 2*time);/* Update response time */
if (give>6000) give = 6000; /* (but limit to 5-6 S.) */
beep(TONE_OFF, 250); /* Wait a brief interval */
} while (guess != answer);
grade(lesson, score); /* Update gradebook */
/* Student has answered correctly, so we'll give her some more.
* Grade() has updated the overall and specific error estimates.
* If the overall rate is low, and no specific character is too
* bad, we will add another character to the training alphabet.
*/
if (OVERALL.error > BAD * 3/10) return 1;
for (l = &letter[num]; l > &OVERALL; l--)
if (l->error > BAD * 4/10) return 1;
add_ltr(); return 1;
}
/*** send - send charcter in morse code
/*
/* Send() is passed a morse coded letter (see reference).
/* It sends the character at speed "WPM" on the PC's speaker.
/*
/* Calling Sequence: send(morse);
/*
/* where (char) "morse" is a stop-bit-prepended morse character.
*/
send(char code) {
register element;
do {
element = dit;
if (isdah(code)) element *= 3;
beep(TONE_ON, element);
beep(TONE_OFF, dit);
} while ((code >>= 1) != 1);
}
/*** select - choose a letter from the current alphabet
/*
/* Select() returns the index of a letter chosen from the
/* currently active alphabet. The probability of chosing
/* a letter is proportional to the estimated error rate
/* for that letter.
/*
/* Calling Sequence: new_letter = select();
/*
/* where (int) "new_letter" is the index of the chosen element
/* from letter[].
*/
select()
{ register int sum; /* error probability accumulator */
register int l; /* working index into letter[] */
sum = 0; l = 1;
do {
sum += letter[l++].error + 1;
} while (l <= num);
sum = rand() % sum;
do {
sum -= letter[--l].error + 1;
} while (sum > 0);
return (l);
}
/*** menu - display 4-line menu, one-line prompt, and get choice
/*
/* Menu() displays a simple 4-line menu, and prompts for a selection.
/* It awaits a valid choice and acts on it. It returns nonzero or
/* zero when "C(ontinue)" or "Q(uit)" are selected (respectively).
/*
/* Calling Sequence: int menu();
*/
menu() {
pms(CWIN, /* NOTE: Change showspd(), below, if you change menu!!! */
"\nCharacter Code Speed:\203Practice Alphabet:\206Training:\n"
"\202S(low --- 10 wpm)\207A(dd another letter)\203C(ontinue training)\n"
"\202M(edium - 15 wpm)\207R(emove last letter)\203Q(uit program)\n"
"\202F(ast --- 20 wpm)\nYour choice? (SMFARCQ): ");
showspd(); /* Show codespeed (see NOTE, above) */
for (;;) switch (resp()) {
case 'S': dit = DIT(10); showspd(); break;
case 'M': dit = DIT(15); showspd(); break;
case 'F': dit = DIT(20); showspd(); break;
case 'A': add_ltr(); break;
case 'R': rem_ltr(); break;
case 'C': return 1;
case 'Q': return 0;
}
}
/*** showspd - show current codespeed on menu
/*
/* Showspd() erases column 19 on rows 21, 22, and 23 of the display,
/* and then rewrites a '<' in one of the erased positions to show
/* the code speed.
*/
showspd() {
register int i;
for (i = 21; i < 24; i++) prc(' '+CWIN, i, 19);
switch (dit) {
case DIT(10): i = 21; break;
case DIT(15): i = 22; break;
case DIT(20): i = 23; break;
}
prc('<'+CWIN, i, 19);
}
/*** pcs - put character and a space
/*
/* Pcs() is passed a character to display (converted to an integer). It
/* displays the character (via putchar()) and then emits a trailing blank.
/*
/* This dates back to the old BRB video terminal where displaying the
/* characters with intervening blanks made the upper-case much more
/* readable.
/*
/* Calling Sequence: pcs(int c);
*/
pcs(int c) { putchar(c); putchar(' '); }
/*** pms - put message to screen
/*
/* Pms() is passed a integer character "attribute" (that controls the
/* character color, blinking, etc.) and a string that can contain:
/*
/* 1. '\f' to clear the screen (using the attribute as fill) and
/* move the cursor to the leftmost column in the first line.
/* 2. '\n' to advance the cursor to first column of the next line
/* (scrolling if necessary).
/* 3. 0x80-flagged characters which represent sequences of blanks
/* (after stripping the leftmost bit).
/* 4. Normal ASCII characters (<= 0x7F) which will be displayed
/* on the screen.
/*
/* Calling Sequence: pms(int attribute, char *message);
*/
pms(int attr, char *msg) {
register int c;
register int i;
while (c = *msg++) { /* While more message left to display, */
if (c & 0x80) { /* If compressed blank, */
for (c &= 0x7F; c; c--)/* display a sequence of blanks */
pc(attr+' ');
} /* If other character, */
else pc(attr+c); /* Let the low-level code handle it */
}
}
/*** barht - convert score (in [0,BAD]) to bar graph height
/*
/* Barht() is passed an integer error rate in [0,BAD]. It linearly
/* scales this value to [0,BARHT], the number of illuminated lines
/* in a crude bar graph displayed in a column on the screen.
/*
/* Calling Sequence: int barht(int score);
*/
barht(int score) { return (score*BARHT + BAD/2)/BAD; }
/*** grade - update error estimates
/*
/* Grade() is passed an index to the current letter and GOOD
/* if the student got the right answer or BAD if she had to
/* be told the answer. Grade() updates the particular and
/* overall error rate estimates and revises the displayed
/* bargraph to display the change.
/*
/* Calling sequence: grade(ltr, grade);
/*
/* where (int) "ltr" is the index of the current letter
/* and (int) "grade" is GOOD or BAD.
*/
grade(unsigned ltr, unsigned g) { /* Index to lesson, test results */
register struct lt *l; /* Pointer to element being dated */
int old, new; /* Old, bar first display row */
l = &letter[ltr]; /* Aim register into letter[] */
ltr = (ltr-1)*2; /* Ltr = column in bargraph */
old = BARHT - barht(l->error); /* 1st row that now has a bar */
update(l, g); /* Update specific error rate */
if (update(&OVERALL, g) < BAD * 1/10) /* If overall error rate low, */
update(l, g); /* accelerate specific rate */
new = BARHT - barht(l->error); /* 1st row that SHOULD have bar */
while (new != old) { /* Until graph bar is right sized, */
if (new > old) /* If too tall, */
prc(' ' +NORM, old++, ltr); /* chop it down to size */
else /* If too short, */
prc(BRCHR+NORM, --old, ltr); /* build it up a bit */
}
}
/*** update - update error rate
/*
/* Update() is passed the address of an element of letter[], and a grade
/* (either GOOD or BAD). It updates the .error probability estimate
/* field, and returns the new value for this field.
/*
/* Calling sequence: int update(struct lt *ltr, int grade);
*/
update(struct lt *l, unsigned g) { return l->error = weight(l->error, g); }
/*** bgs - display (initial) bargraph on screen
/*
/* Bgs() erases the herald display (see herald(), above), and replaces
/* it with a crude bargraph of the student's error rate (i.e. the
/* letter[].error fields). Below each bar, the corresponding letter
/* (i.e. the letter[].ascii) is displayed. Active letters are dis-
/* tinguished by displaying their bargraph as a solid bar; inactive
/* letters' bars are "greyed".
/*
/* Calling Sequence: bgs();
*/
bgs() {
register int i = 0; /* Index into letter[] */
pc('\f'+NORM); /* Clear screen */
do drwbar(++i); /* Draw bargraph */
while (i < MAXNUM);
}
/*** add_ltr, rem_ltr - add letter/remove letter from training aphabet
/*
/* Add_ltr() checks "num", the number of letters in the training alphabet
/* and increases it (assuming it is not maxed out already). The appear-
/* ance of the new character (its bargraph bar) is redrawn as solid to
/* reflect its new status.
/*
/* Rem_ltr() decreases the number of letters, (but not less than 1) and
/* "greys" the bargraph appropriately.
/*
/* Calling Sequence: add_ltr(); ... rem_ltr();
*/
add_ltr() { if (num < MAXNUM) drwbar(++num); } /* Room to add? redraw bar */
rem_ltr() { if (num > 1) drwbar(num--); } /* More than 1? redraw bar */
/*** drwbar - draw bargraph bar
/*
/* Drw_bar() is passed an index into letter[] (>= 1). It draws a
/* a "greyed" or "solid" (depending upon whether the corresponding
/* letter is part of the current trainin alphabet) whose height is
/* proportional to letter[].error.
/*
/* Calling Sequence: drwbar(int index);
*/
drwbar(int c) { /* Column (nee index) for display */
register int r = letter[c].ascii; /* Letter, later its bargraph row */
int s = BARHT-barht(letter[c].error); /* Number of spaces ABOVE bar */
int bc = BRCHR; /* Bargraph char or greyed version */
if (c <= num) r += HLGT; /* If part of training set, highlite on */
else r += GREY, bc = BRGRY; /* Otherwise, deemphasize and grey its bar */
c = c-1 << 1; /* C = column for letter and its bar */
prc(r, BARHT, c); /* Display annotation */
r = BARHT; do { /* Loop to draw a bar (and space above) */
if (r-- == s) bc = ' '; /* If time to switch from draw to erase */
prc(bc+NORM, r, c); /* Put (char) at (screen) row, column */
} while (r); /* Until reach top of screen */
}