Merge branch 'master' of ssh://crater.dragonflybsd.org/repository/git/dragonfly
[dragonfly.git] / games / morse / morse.c
blob31746adfebebe0e0f66a077fac3c8adf3251c93b
1 /*-
2 * Copyright (c) 1988, 1993
3 * The Regents of the University of California. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the University nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
29 * @(#) Copyright (c) 1988, 1993 The Regents of the University of California. All rights reserved.
30 * @(#)morse.c 8.1 (Berkeley) 5/31/93
31 * $FreeBSD: src/games/morse/morse.c,v 1.12.2.2 2002/03/12 17:45:15 phantom Exp $
32 * $DragonFly: src/games/morse/morse.c,v 1.8 2008/05/30 21:47:04 corecode Exp $
36 * Taught to send *real* morse by Lyndon Nerenberg (VE7TCP/VE6BBM)
37 * <lyndon@orthanc.com>
40 #include <sys/time.h>
41 #include <sys/soundcard.h>
43 #include <ctype.h>
44 #include <err.h>
45 #include <fcntl.h>
46 #include <langinfo.h>
47 #include <locale.h>
48 #include <math.h>
49 #include <signal.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <termios.h>
54 #include <unistd.h>
56 struct morsetab {
57 char inchar;
58 const char *morse;
61 static const struct morsetab mtab[] = {
63 /* letters */
65 {'a', ".-"},
66 {'b', "-..."},
67 {'c', "-.-."},
68 {'d', "-.."},
69 {'e', "."},
70 {'f', "..-."},
71 {'g', "--."},
72 {'h', "...."},
73 {'i', ".."},
74 {'j', ".---"},
75 {'k', "-.-"},
76 {'l', ".-.."},
77 {'m', "--"},
78 {'n', "-."},
79 {'o', "---"},
80 {'p', ".--."},
81 {'q', "--.-"},
82 {'r', ".-."},
83 {'s', "..."},
84 {'t', "-"},
85 {'u', "..-"},
86 {'v', "...-"},
87 {'w', ".--"},
88 {'x', "-..-"},
89 {'y', "-.--"},
90 {'z', "--.."},
92 /* digits */
94 {'0', "-----"},
95 {'1', ".----"},
96 {'2', "..---"},
97 {'3', "...--"},
98 {'4', "....-"},
99 {'5', "....."},
100 {'6', "-...."},
101 {'7', "--..."},
102 {'8', "---.."},
103 {'9', "----."},
105 /* punctuation */
107 {',', "--..--"},
108 {'.', ".-.-.-"},
109 {'?', "..--.."},
110 {'!', "-.-.--"}, /* KW */
111 {'/', "-..-."},
112 {'-', "-....-"},
113 {'_', "..--.."},
114 {'=', "-...-"}, /* BT */
115 {':', "---..."},
116 {';', "-.-.-."},
117 {'(', "-.--."}, /* KN */
118 {')', "-.--.-"},
119 {'$', "...-..-"},
120 {'+', ".-.-."}, /* AR */
121 {'\'', ".----."},
122 {'"', ".-..-."},
123 {'@', ".--.-."}, /* AC */
125 {'\0', ""}
129 static const struct morsetab iso8859tab[] = {
130 {'á', ".--.-"},
131 {'à', ".--.-"},
132 {'â', ".--.-"},
133 {'ä', ".-.-"},
134 {'ç', "-.-.."},
135 {'é', "..-.."},
136 {'è', "..-.."},
137 {'ê', "-..-."},
138 {'ö', "---."},
139 {'ü', "..--"},
141 {'\0', ""}
144 static const struct morsetab koi8rtab[] = {
146 * the cyrillic alphabet; you'll need a KOI8R font in order
147 * to see the actual characters
149 {'Á', ".-"}, /* a */
150 {'Â', "-..."}, /* be */
151 {'×', ".--"}, /* ve */
152 {'Ç', "--."}, /* ge */
153 {'Ä', "-.."}, /* de */
154 {'Å', "."}, /* ye */
155 {'£', "."}, /* yo, the same as ye */
156 {'Ö', "...-"}, /* she */
157 {'Ú', "--.."}, /* ze */
158 {'É', ".."}, /* i */
159 {'Ê', ".---"}, /* i kratkoye */
160 {'Ë', "-.-"}, /* ka */
161 {'Ì', ".-.."}, /* el */
162 {'Í', "--"}, /* em */
163 {'Î', "-."}, /* en */
164 {'Ï', "---"}, /* o */
165 {'Ð', ".--."}, /* pe */
166 {'Ò', ".-."}, /* er */
167 {'Ó', "..."}, /* es */
168 {'Ô', "-"}, /* te */
169 {'Õ', "..-"}, /* u */
170 {'Æ', "..-."}, /* ef */
171 {'È', "...."}, /* kha */
172 {'Ã', "-.-."}, /* ce */
173 {'Þ', "---."}, /* che */
174 {'Û', "----"}, /* sha */
175 {'Ý', "--.-"}, /* shcha */
176 {'Ù', "-.--"}, /* yi */
177 {'Ø', "-..-"}, /* myakhkij znak */
178 {'Ü', "..-.."}, /* ae */
179 {'À', "..--"}, /* yu */
180 {'Ñ', ".-.-"}, /* ya */
182 {'\0', ""}
185 struct tone_data {
186 int16_t *data;
187 size_t len;
190 void alloc_soundbuf(struct tone_data *, double, int);
191 void morse(char, int);
192 void show(const char *, int);
193 void play(const char *, int);
194 void ttyout(const char *, int);
195 void sighandler(int);
197 #define GETOPTOPTS "d:ef:opP:sw:W:"
198 #define USAGE \
199 "usage: morse [-s] [-e] [-p | -o] [-P device] [-d device] [-w speed] [-W speed] [-f frequency] [string ...]\n"
201 static int oflag, pflag, sflag, eflag;
202 static int wpm = 20; /* words per minute */
203 static int farnsworth = -1;
204 #define FREQUENCY 600
205 static int freq = FREQUENCY;
206 static char *device; /* for tty-controlled generator */
208 static struct tone_data tone_dot, tone_dash, tone_silence, tone_letter_silence;
209 #define DSP_RATE 44100
210 static const char *snddev = NULL;
212 #define DASH_LEN 3
213 #define CHAR_SPACE 3
214 #define WORD_SPACE (7 - CHAR_SPACE)
215 static float dot_clock, word_clock;
216 int spkr, line;
217 struct termios otty, ntty;
218 int olflags;
220 static const struct morsetab *hightab;
223 main(int argc, char **argv)
225 int ch, lflags;
226 int prosign;
227 char *p, *codeset;
229 while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1)
230 switch ((char) ch) {
231 case 'd':
232 device = optarg;
233 break;
234 case 'e':
235 eflag = 1;
236 setvbuf(stdout, 0, _IONBF, 0);
237 break;
238 case 'f':
239 freq = atoi(optarg);
240 break;
241 case 'o':
242 oflag = 1;
243 /* FALLTHROUGH */
244 case 'p':
245 pflag = 1;
246 break;
247 case 'P':
248 snddev = optarg;
249 break;
250 case 's':
251 sflag = 1;
252 break;
253 case 'w':
254 wpm = atoi(optarg);
255 break;
256 case 'W':
257 farnsworth = atoi(optarg);
258 break;
259 case '?':
260 default:
261 fputs(USAGE, stderr);
262 exit(1);
264 if (pflag + !!device + sflag > 1) {
265 fputs("morse: only one of -o, -p, -d and -s allowed\n", stderr);
266 exit(1);
268 if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (farnsworth > 60))) {
269 fputs("morse: insane speed\n", stderr);
270 exit(1);
272 if ((pflag || device) && (freq == 0))
273 freq = FREQUENCY;
274 if (pflag || device) {
276 * A note on how to get to this magic 1.2:
277 * x WPM = 50*x dits per minute (norm word "PARIS").
278 * dits per sec = dits per minute / 60, thus
279 * dits per sec = 50 * x / 60 = x / (60 / 50) = x / 1.2
281 dot_clock = wpm / 1.2; /* dots/sec */
282 dot_clock = 1 / dot_clock; /* duration of a dot */
284 word_clock = dot_clock;
287 * This is how to get to this formula:
288 * PARIS = 22 dit (symbols) + 9 symbol spaces = 31 symbol times
289 * + 19 space times.
291 * The symbol times are in dot_clock, so the spaces have to
292 * make up to reach the farnsworth time.
294 if (farnsworth > 0)
295 word_clock = (60.0 / farnsworth - 31 * dot_clock) / 19;
297 if (snddev == NULL) {
298 if (oflag)
299 snddev = "-";
300 else /* only pflag */
301 snddev = "/dev/dsp";
304 if (pflag) {
305 snd_chan_param param;
307 if (oflag && strcmp(snddev, "-") == 0)
308 spkr = STDOUT_FILENO;
309 else
310 spkr = open(snddev, O_WRONLY, 0);
311 if (spkr == -1)
312 err(1, "%s", snddev);
313 param.play_rate = DSP_RATE;
314 param.play_format = AFMT_S16_NE;
315 param.rec_rate = 0;
316 param.rec_format = 0;
317 if (!oflag && ioctl(spkr, AIOSFMT, &param) != 0)
318 err(1, "%s: set format", snddev);
319 alloc_soundbuf(&tone_dot, dot_clock, 1);
320 alloc_soundbuf(&tone_dash, DASH_LEN * dot_clock, 1);
321 alloc_soundbuf(&tone_silence, dot_clock, 0);
322 alloc_soundbuf(&tone_letter_silence, word_clock, 0);
323 } else
324 if (device) {
325 if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) {
326 perror("open tty line");
327 exit(1);
329 if (tcgetattr(line, &otty) == -1) {
330 perror("tcgetattr() failed");
331 exit(1);
333 ntty = otty;
334 ntty.c_cflag |= CLOCAL;
335 tcsetattr(line, TCSANOW, &ntty);
336 lflags = fcntl(line, F_GETFL);
337 lflags &= ~O_NONBLOCK;
338 fcntl(line, F_SETFL, &lflags);
339 ioctl(line, TIOCMGET, &lflags);
340 lflags &= ~TIOCM_RTS;
341 olflags = lflags;
342 ioctl(line, TIOCMSET, &lflags);
343 (void)signal(SIGHUP, sighandler);
344 (void)signal(SIGINT, sighandler);
345 (void)signal(SIGQUIT, sighandler);
346 (void)signal(SIGTERM, sighandler);
349 argc -= optind;
350 argv += optind;
352 if (setlocale(LC_CTYPE, "") != NULL &&
353 *(codeset = nl_langinfo(CODESET)) != '\0') {
354 if (strcmp(codeset, "KOI8-R") == 0)
355 hightab = koi8rtab;
356 else if (strcmp(codeset, "ISO8859-1") == 0 ||
357 strcmp(codeset, "ISO8859-15") == 0)
358 hightab = iso8859tab;
361 if (*argv) {
362 do {
363 prosign = 0;
364 for (p = *argv; *p; ++p) {
365 if (eflag)
366 putchar(*p);
367 if (*p == '<' || *p == '>') {
368 prosign = *p == '<';
369 continue;
371 if (strchr("> \r\n", *(p + 1)) != NULL)
372 prosign = 0;
373 morse(*p, prosign);
375 if (eflag)
376 putchar(' ');
377 morse(' ', 0);
378 } while (*++argv);
379 } else {
380 prosign = 0;
381 while ((ch = getchar()) != EOF) {
382 if (eflag)
383 putchar(ch);
384 if (ch == '<') {
385 prosign = 1;
386 continue;
388 if (prosign) {
389 int tch;
391 tch = getchar();
392 if (strchr("> \r\n", tch) != NULL)
393 prosign = 0;
394 if (tch != '>')
395 ungetc(tch, stdin);
397 morse(ch, prosign);
400 if (device)
401 tcsetattr(line, TCSANOW, &otty);
402 exit(0);
405 void
406 alloc_soundbuf(struct tone_data *tone, double len, int on)
408 int samples, i;
410 samples = DSP_RATE * len;
411 tone->len = samples * sizeof(*tone->data);
412 tone->data = malloc(tone->len);
413 if (tone->data == NULL)
414 err(1, NULL);
415 if (!on) {
416 bzero(tone->data, tone->len);
417 return;
421 * We create a sinus with the specified frequency and smooth
422 * the edges to reduce key clicks.
424 for (i = 0; i < samples; i++) {
425 double filter = 1;
427 #define FILTER_SAMPLES (DSP_RATE * 8 / 1000) /* 8 ms ramp time */
428 if (i < FILTER_SAMPLES || i > samples - FILTER_SAMPLES) {
429 int fi = i;
431 if (i > FILTER_SAMPLES)
432 fi = samples - i;
433 #if defined(TRIANGLE_FILTER)
435 * Triangle envelope
437 filter = (double)fi / FILTER_SAMPLES;
438 #elif defined(GAUSS_FILTER)
440 * Gauss envelope
442 filter = exp(-4.0 *
443 pow((double)(FILTER_SAMPLES - fi) /
444 FILTER_SAMPLES, 2));
445 #else
447 * Cosine envelope
449 filter = (1 + cos(M_PI * (FILTER_SAMPLES - fi) / FILTER_SAMPLES)) / 2;
450 #endif
452 tone->data[i] = 32767 * sin((double)i / samples * len * freq * 2 * M_PI) *
453 filter;
457 void
458 morse(char c, int prosign)
460 const struct morsetab *m;
462 if (isalpha((unsigned char)c))
463 c = tolower((unsigned char)c);
464 if ((c == '\r') || (c == '\n'))
465 c = ' ';
466 if (c == ' ') {
467 if (pflag) {
468 play(" ", 0);
469 return;
470 } else if (device) {
471 ttyout(" ", 0);
472 return;
473 } else {
474 show("", 0);
475 return;
478 for (m = ((unsigned char)c < 0x80? mtab: hightab);
479 m != NULL && m->inchar != '\0';
480 m++) {
481 if (m->inchar == c) {
482 if (pflag) {
483 play(m->morse, prosign);
484 } else if (device) {
485 ttyout(m->morse, prosign);
486 } else
487 show(m->morse, prosign);
492 void
493 show(const char *s, int prosign)
495 if (sflag)
496 printf(" %s", s);
497 else
498 for (; *s; ++s)
499 printf(" %s", *s == '.' ? "dit" : "dah");
500 if (!prosign)
501 printf("\n");
504 void
505 play(const char *s, int prosign)
507 const char *c;
508 int duration;
509 struct tone_data *tone;
512 * We don't need to usleep() here, as the sound device blocks.
514 for (c = s; *c != '\0'; c++) {
515 switch (*c) {
516 case '.':
517 duration = 1;
518 tone = &tone_dot;
519 break;
520 case '-':
521 duration = 1;
522 tone = &tone_dash;
523 break;
524 case ' ':
525 duration = WORD_SPACE;
526 tone = &tone_letter_silence;
527 break;
528 default:
529 errx(1, "invalid morse digit");
531 while (duration-- > 0)
532 write(spkr, tone->data, tone->len);
533 /* Only space within a symbol */
534 if (c[1] != '\0' || prosign)
535 write(spkr, tone_silence.data, tone_silence.len);
537 if (prosign)
538 return;
539 duration = CHAR_SPACE;
540 while (duration-- > 0)
541 write(spkr, tone_letter_silence.data, tone_letter_silence.len);
543 /* Sync out the audio data with other output */
544 if (!oflag)
545 ioctl(spkr, SNDCTL_DSP_SYNC, NULL);
548 void
549 ttyout(const char *s, int prosign)
551 const char *c;
552 int duration, on, lflags;
554 for (c = s; *c != '\0'; c++) {
555 switch (*c) {
556 case '.':
557 on = 1;
558 duration = dot_clock;
559 break;
560 case '-':
561 on = 1;
562 duration = dot_clock * DASH_LEN;
563 break;
564 case ' ':
565 on = 0;
566 duration = word_clock * WORD_SPACE;
567 break;
568 default:
569 on = 0;
570 duration = 0;
572 if (on) {
573 ioctl(line, TIOCMGET, &lflags);
574 lflags |= TIOCM_RTS;
575 ioctl(line, TIOCMSET, &lflags);
577 duration *= 1000000;
578 if (duration)
579 usleep(duration);
580 ioctl(line, TIOCMGET, &lflags);
581 lflags &= ~TIOCM_RTS;
582 ioctl(line, TIOCMSET, &lflags);
583 duration = dot_clock * 1000000;
584 /* Only space within a symbol */
585 if (c[1] != '\0' || prosign)
586 usleep(duration);
588 if (!prosign) {
589 duration = word_clock * CHAR_SPACE * 1000000;
590 usleep(duration);
594 void
595 sighandler(int signo)
598 ioctl(line, TIOCMSET, &olflags);
599 tcsetattr(line, TCSANOW, &otty);
601 signal(signo, SIG_DFL);
602 (void)kill(getpid(), signo);