periodic(8): Sync with FreeBSD current
[dragonfly.git] / games / morse / morse.c
blob21a9eee660d42e780d18d2791f3b663520ab4dce
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 * $OpenBSD: morse.c,v 1.22 2016/03/07 12:07:56 mestre Exp $
36 * Taught to send *real* morse by Lyndon Nerenberg (VE7TCP/VE6BBM)
37 * <lyndon@orthanc.ca>
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 static const char *digit[] = {
57 "-----",
58 ".----",
59 "..---",
60 "...--",
61 "....-",
62 ".....",
63 "-....",
64 "--...",
65 "---..",
66 "----.",
69 static const char *alph[] = {
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 "--..",
98 struct punc {
99 char c;
100 const char *morse;
103 static const struct punc other[] = {
104 { 'e', "..-.." }, /* accented e - only decodes */
105 { ',', "--..--" },
106 { '.', ".-.-.-" },
107 { '?', "..--.." },
108 { '/', "-..-." },
109 { '-', "-....-" },
110 { ':', "---..." },
111 { ';', "-.-.-." },
112 { '(', "-.--." }, /* KN */
113 { ')', "-.--.-" },
114 { '"', ".-..-." },
115 { '`', ".-..-." },
116 { '\'', ".----." },
117 { '+', ".-.-." }, /* AR \n\n\n */
118 { '=', "-...-" }, /* BT \n\n */
119 { '@', ".--.-." },
120 { '\n', ".-.-" }, /* AA (will only decode) */
121 { '\0', NULL }
124 struct prosign {
125 const char *c;
126 const char *morse;
129 static const struct prosign ps[] = {
130 { "<AS>", ".-..." }, /* wait */
131 { "<CL>", "-.-..-.." },
132 { "<CT>", "-.-.-" }, /* start */
133 { "<EE5>", "......" }, /* error */
134 { "<EE5>", "......." },
135 { "<EE5>", "........" },
136 { "<SK>", "...-.-" },
137 { "<SN>", "...-." }, /* understood */
138 { "<SOS>", "...---..." },
139 { NULL, NULL }
142 struct morsetab {
143 char inchar;
144 const char *morse;
147 static const struct morsetab mtab[] = {
149 /* letters */
151 {'a', ".-"},
152 {'b', "-..."},
153 {'c', "-.-."},
154 {'d', "-.."},
155 {'e', "."},
156 {'f', "..-."},
157 {'g', "--."},
158 {'h', "...."},
159 {'i', ".."},
160 {'j', ".---"},
161 {'k', "-.-"},
162 {'l', ".-.."},
163 {'m', "--"},
164 {'n', "-."},
165 {'o', "---"},
166 {'p', ".--."},
167 {'q', "--.-"},
168 {'r', ".-."},
169 {'s', "..."},
170 {'t', "-"},
171 {'u', "..-"},
172 {'v', "...-"},
173 {'w', ".--"},
174 {'x', "-..-"},
175 {'y', "-.--"},
176 {'z', "--.."},
178 /* digits */
180 {'0', "-----"},
181 {'1', ".----"},
182 {'2', "..---"},
183 {'3', "...--"},
184 {'4', "....-"},
185 {'5', "....."},
186 {'6', "-...."},
187 {'7', "--..."},
188 {'8', "---.."},
189 {'9', "----."},
191 /* punctuation */
193 {',', "--..--"},
194 {'.', ".-.-.-"},
195 {'?', "..--.."},
196 {'!', "-.-.--"}, /* KW */
197 {'/', "-..-."},
198 {'-', "-....-"},
199 {'_', "..--.."},
200 {'=', "-...-"}, /* BT */
201 {':', "---..."},
202 {';', "-.-.-."},
203 {'(', "-.--."}, /* KN */
204 {')', "-.--.-"},
205 {'$', "...-..-"},
206 {'+', ".-.-."}, /* AR */
207 {'\'', ".----."},
208 {'"', ".-..-."},
209 {'@', ".--.-."}, /* AC */
211 {'\0', ""}
215 static const struct morsetab iso8859tab[] = {
216 {'á', ".--.-"},
217 {'à', ".--.-"},
218 {'â', ".--.-"},
219 {'ä', ".-.-"},
220 {'ç', "-.-.."},
221 {'é', "..-.."},
222 {'è', "..-.."},
223 {'ê', "-..-."},
224 {'ö', "---."},
225 {'ü', "..--"},
227 {'\0', ""}
230 static const struct morsetab koi8rtab[] = {
232 * the cyrillic alphabet; you'll need a KOI8R font in order
233 * to see the actual characters
235 {'Á', ".-"}, /* a */
236 {'Â', "-..."}, /* be */
237 {'×', ".--"}, /* ve */
238 {'Ç', "--."}, /* ge */
239 {'Ä', "-.."}, /* de */
240 {'Å', "."}, /* ye */
241 {'£', "."}, /* yo, the same as ye */
242 {'Ö', "...-"}, /* she */
243 {'Ú', "--.."}, /* ze */
244 {'É', ".."}, /* i */
245 {'Ê', ".---"}, /* i kratkoye */
246 {'Ë', "-.-"}, /* ka */
247 {'Ì', ".-.."}, /* el */
248 {'Í', "--"}, /* em */
249 {'Î', "-."}, /* en */
250 {'Ï', "---"}, /* o */
251 {'Ð', ".--."}, /* pe */
252 {'Ò', ".-."}, /* er */
253 {'Ó', "..."}, /* es */
254 {'Ô', "-"}, /* te */
255 {'Õ', "..-"}, /* u */
256 {'Æ', "..-."}, /* ef */
257 {'È', "...."}, /* kha */
258 {'Ã', "-.-."}, /* ce */
259 {'Þ', "---."}, /* che */
260 {'Û', "----"}, /* sha */
261 {'Ý', "--.-"}, /* shcha */
262 {'Ù', "-.--"}, /* yi */
263 {'Ø', "-..-"}, /* myakhkij znak */
264 {'Ü', "..-.."}, /* ae */
265 {'À', "..--"}, /* yu */
266 {'Ñ', ".-.-"}, /* ya */
268 {'\0', ""}
271 struct tone_data {
272 int16_t *data;
273 size_t len;
276 static void alloc_soundbuf(struct tone_data *, double, int);
277 static void morse(char, int);
278 static void decode(const char *);
279 static void show(const char *, int);
280 static void play(const char *, int);
281 static void ttyout(const char *, int);
282 static void sighandler(int);
284 #define GETOPTOPTS "d:ef:lopP:rsw:W:"
285 #define USAGE \
286 "usage: morse [-r] [-els] [-p | -o] [-P device] [-d device] [-w speed] [-W speed] [-f frequency] [string ...]\n"
288 static int lflag, oflag, pflag, rflag, sflag, eflag;
289 static int wpm = 20; /* words per minute */
290 static int farnsworth = -1;
291 #define FREQUENCY 600
292 static int freq = FREQUENCY;
293 static char *device; /* for tty-controlled generator */
295 static struct tone_data tone_dot, tone_dash, tone_silence, tone_letter_silence;
296 #define DSP_RATE 44100
297 static const char *snddev = NULL;
299 #define DASH_LEN 3
300 #define CHAR_SPACE 3
301 #define WORD_SPACE (7 - CHAR_SPACE)
302 static float dot_clock, word_clock;
303 int spkr, line;
304 struct termios otty, ntty;
305 int olflags;
307 static const struct morsetab *hightab;
310 main(int argc, char *argv[])
312 int ch, lflags;
313 int prosign;
314 char *p, *codeset;
316 while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1)
317 switch ((char) ch) {
318 case 'd':
319 device = optarg;
320 break;
321 case 'e':
322 eflag = 1;
323 setvbuf(stdout, 0, _IONBF, 0);
324 break;
325 case 'f':
326 freq = atoi(optarg);
327 break;
328 case 'l':
329 lflag = 1;
330 break;
331 case 'o':
332 oflag = 1;
333 /* FALLTHROUGH */
334 case 'p':
335 pflag = 1;
336 break;
337 case 'P':
338 snddev = optarg;
339 break;
340 case 'r':
341 rflag = 1;
342 break;
343 case 's':
344 sflag = 1;
345 break;
346 case 'w':
347 wpm = atoi(optarg);
348 break;
349 case 'W':
350 farnsworth = atoi(optarg);
351 break;
352 case '?':
353 default:
354 fputs(USAGE, stderr);
355 exit(1);
357 if (sflag && lflag) {
358 fputs("morse: only one of -l and -s allowed\n", stderr);
359 exit(1);
361 if (pflag + !!device + sflag + lflag > 1) {
362 fputs("morse: only one of -o, -p, -d and -l, -s allowed\n", stderr);
363 exit(1);
365 if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (farnsworth > 60))) {
366 fputs("morse: insane speed\n", stderr);
367 exit(1);
369 if ((pflag || device) && (freq == 0))
370 freq = FREQUENCY;
371 if (pflag || device) {
373 * A note on how to get to this magic 1.2:
374 * x WPM = 50*x dits per minute (norm word "PARIS").
375 * dits per sec = dits per minute / 60, thus
376 * dits per sec = 50 * x / 60 = x / (60 / 50) = x / 1.2
378 dot_clock = wpm / 1.2; /* dots/sec */
379 dot_clock = 1 / dot_clock; /* duration of a dot */
381 word_clock = dot_clock;
384 * This is how to get to this formula:
385 * PARIS = 22 dit (symbols) + 9 symbol spaces = 31 symbol times
386 * + 19 space times.
388 * The symbol times are in dot_clock, so the spaces have to
389 * make up to reach the farnsworth time.
391 if (farnsworth > 0)
392 word_clock = (60.0 / farnsworth - 31 * dot_clock) / 19;
394 if (snddev == NULL) {
395 if (oflag)
396 snddev = "-";
397 else /* only pflag */
398 snddev = "/dev/dsp";
401 if (pflag) {
402 snd_chan_param param;
404 if (oflag && strcmp(snddev, "-") == 0)
405 spkr = STDOUT_FILENO;
406 else
407 spkr = open(snddev, O_WRONLY, 0);
408 if (spkr == -1)
409 err(1, "%s", snddev);
410 param.play_rate = DSP_RATE;
411 param.play_format = AFMT_S16_NE;
412 param.rec_rate = 0;
413 param.rec_format = 0;
414 if (!oflag && ioctl(spkr, AIOSFMT, &param) != 0)
415 err(1, "%s: set format", snddev);
416 alloc_soundbuf(&tone_dot, dot_clock, 1);
417 alloc_soundbuf(&tone_dash, DASH_LEN * dot_clock, 1);
418 alloc_soundbuf(&tone_silence, dot_clock, 0);
419 alloc_soundbuf(&tone_letter_silence, word_clock, 0);
420 } else
421 if (device) {
422 if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) {
423 perror("open tty line");
424 exit(1);
426 if (tcgetattr(line, &otty) == -1) {
427 perror("tcgetattr() failed");
428 exit(1);
430 ntty = otty;
431 ntty.c_cflag |= CLOCAL;
432 tcsetattr(line, TCSANOW, &ntty);
433 lflags = fcntl(line, F_GETFL);
434 lflags &= ~O_NONBLOCK;
435 fcntl(line, F_SETFL, &lflags);
436 ioctl(line, TIOCMGET, &lflags);
437 lflags &= ~TIOCM_RTS;
438 olflags = lflags;
439 ioctl(line, TIOCMSET, &lflags);
440 signal(SIGHUP, sighandler);
441 signal(SIGINT, sighandler);
442 signal(SIGQUIT, sighandler);
443 signal(SIGTERM, sighandler);
446 argc -= optind;
447 argv += optind;
449 if (setlocale(LC_CTYPE, "") != NULL &&
450 *(codeset = nl_langinfo(CODESET)) != '\0') {
451 if (strcmp(codeset, "KOI8-R") == 0)
452 hightab = koi8rtab;
453 else if (strcmp(codeset, "ISO8859-1") == 0 ||
454 strcmp(codeset, "ISO8859-15") == 0)
455 hightab = iso8859tab;
458 if (rflag) {
459 if (*argv) {
460 do {
461 decode(*argv);
462 } while (*++argv);
463 } else {
464 char foo[10]; /* All morse chars shorter than this */
465 int blank, i;
467 i = 0;
468 blank = 0;
469 while ((ch = getchar()) != EOF) {
470 if (ch == '-' || ch == '.') {
471 foo[i++] = ch;
472 if (i == 10) {
473 /* overrun means gibberish--print 'x' and
474 * advance */
475 i = 0;
476 putchar('x');
477 while ((ch = getchar()) != EOF &&
478 (ch == '.' || ch == '-'))
480 blank = 1;
482 } else if (i) {
483 foo[i] = '\0';
484 decode(foo);
485 i = 0;
486 blank = 0;
487 } else if (isspace(ch)) {
488 if (blank) {
489 /* print whitespace for each double blank */
490 putchar(' ');
491 blank = 0;
492 } else
493 blank = 1;
497 putchar('\n');
498 exit(0);
501 if (lflag)
502 printf("m");
503 if (*argv) {
504 do {
505 prosign = 0;
506 for (p = *argv; *p; ++p) {
507 if (eflag)
508 putchar(*p);
509 if (*p == '<' || *p == '>') {
510 prosign = *p == '<';
511 continue;
513 if (strchr("> \r\n", *(p + 1)) != NULL)
514 prosign = 0;
515 morse(*p, prosign);
517 if (eflag)
518 putchar(' ');
519 morse(' ', 0);
520 } while (*++argv);
521 } else {
522 prosign = 0;
523 while ((ch = getchar()) != EOF) {
524 if (eflag)
525 putchar(ch);
526 if (ch == '<') {
527 prosign = 1;
528 continue;
530 if (prosign) {
531 int tch;
533 tch = getchar();
534 if (strchr("> \r\n", tch) != NULL)
535 prosign = 0;
536 if (tch != '>')
537 ungetc(tch, stdin);
539 morse(ch, prosign);
542 if (device)
543 tcsetattr(line, TCSANOW, &otty);
544 exit(0);
547 static void
548 alloc_soundbuf(struct tone_data *tone, double len, int on)
550 int samples, i;
552 samples = DSP_RATE * len;
553 tone->len = samples * sizeof(*tone->data);
554 tone->data = malloc(tone->len);
555 if (tone->data == NULL)
556 err(1, NULL);
557 if (!on) {
558 bzero(tone->data, tone->len);
559 return;
563 * We create a sinus with the specified frequency and smooth
564 * the edges to reduce key clicks.
566 for (i = 0; i < samples; i++) {
567 double filter = 1;
569 #define FILTER_SAMPLES (DSP_RATE * 8 / 1000) /* 8 ms ramp time */
570 if (i < FILTER_SAMPLES || i > samples - FILTER_SAMPLES) {
571 int fi = i;
573 if (i > FILTER_SAMPLES)
574 fi = samples - i;
575 #if defined(TRIANGLE_FILTER)
577 * Triangle envelope
579 filter = (double)fi / FILTER_SAMPLES;
580 #elif defined(GAUSS_FILTER)
582 * Gauss envelope
584 filter = exp(-4.0 *
585 pow((double)(FILTER_SAMPLES - fi) /
586 FILTER_SAMPLES, 2));
587 #else
589 * Cosine envelope
591 filter = (1 + cos(M_PI * (FILTER_SAMPLES - fi) / FILTER_SAMPLES)) / 2;
592 #endif
594 tone->data[i] = 32767 * sin((double)i / samples * len * freq * 2 * M_PI) *
595 filter;
599 static void
600 morse(char c, int prosign)
602 const struct morsetab *m;
604 if (isalpha((unsigned char)c))
605 c = tolower((unsigned char)c);
606 if ((c == '\r') || (c == '\n'))
607 c = ' ';
608 if (c == ' ') {
609 if (pflag) {
610 play(" ", 0);
611 return;
612 } else if (device) {
613 ttyout(" ", 0);
614 return;
615 } else if (lflag) {
616 printf("\n");
617 } else {
618 show("", 0);
619 return;
622 for (m = ((unsigned char)c < 0x80? mtab: hightab);
623 m != NULL && m->inchar != '\0';
624 m++) {
625 if (m->inchar == c) {
626 if (pflag) {
627 play(m->morse, prosign);
628 } else if (device) {
629 ttyout(m->morse, prosign);
630 } else
631 show(m->morse, prosign);
636 static void
637 decode(const char *s)
639 int i;
641 for (i = 0; i < 10; i++)
642 if (strcmp(digit[i], s) == 0) {
643 putchar('0' + i);
644 return;
647 for (i = 0; i < 26; i++)
648 if (strcmp(alph[i], s) == 0) {
649 putchar('A' + i);
650 return;
652 i = 0;
653 while (other[i].c) {
654 if (strcmp(other[i].morse, s) == 0) {
655 putchar(other[i].c);
656 return;
658 i++;
660 i = 0;
661 while (ps[i].c) {
662 /* put whitespace around prosigns */
663 if (strcmp(ps[i].morse, s) == 0) {
664 printf(" %s ", ps[i].c);
665 return;
667 i++;
669 putchar('x'); /* line noise */
672 static void
673 show(const char *s, int prosign)
675 if (lflag) {
676 printf("%s ", s);
677 return;
678 } else if (sflag)
679 printf(" %s", s);
680 else
681 for (; *s; ++s)
682 printf(" %s", *s == '.' ? "dit" : "dah");
683 if (!prosign)
684 printf("\n");
687 static void
688 play(const char *s, int prosign)
690 const char *c;
691 int duration;
692 struct tone_data *tone;
695 * We don't need to usleep() here, as the sound device blocks.
697 for (c = s; *c != '\0'; c++) {
698 switch (*c) {
699 case '.':
700 duration = 1;
701 tone = &tone_dot;
702 break;
703 case '-':
704 duration = 1;
705 tone = &tone_dash;
706 break;
707 case ' ':
708 duration = WORD_SPACE;
709 tone = &tone_letter_silence;
710 break;
711 default:
712 errx(1, "invalid morse digit");
714 while (duration-- > 0)
715 write(spkr, tone->data, tone->len);
716 /* Only space within a symbol */
717 if (c[1] != '\0' || prosign)
718 write(spkr, tone_silence.data, tone_silence.len);
720 if (prosign)
721 return;
722 duration = CHAR_SPACE;
723 while (duration-- > 0)
724 write(spkr, tone_letter_silence.data, tone_letter_silence.len);
726 /* Sync out the audio data with other output */
727 if (!oflag)
728 ioctl(spkr, SNDCTL_DSP_SYNC, NULL);
731 static void
732 ttyout(const char *s, int prosign)
734 const char *c;
735 int duration, on, lflags;
737 for (c = s; *c != '\0'; c++) {
738 switch (*c) {
739 case '.':
740 on = 1;
741 duration = dot_clock;
742 break;
743 case '-':
744 on = 1;
745 duration = dot_clock * DASH_LEN;
746 break;
747 case ' ':
748 on = 0;
749 duration = word_clock * WORD_SPACE;
750 break;
751 default:
752 on = 0;
753 duration = 0;
755 if (on) {
756 ioctl(line, TIOCMGET, &lflags);
757 lflags |= TIOCM_RTS;
758 ioctl(line, TIOCMSET, &lflags);
760 duration *= 1000000;
761 if (duration)
762 usleep(duration);
763 ioctl(line, TIOCMGET, &lflags);
764 lflags &= ~TIOCM_RTS;
765 ioctl(line, TIOCMSET, &lflags);
766 duration = dot_clock * 1000000;
767 /* Only space within a symbol */
768 if (c[1] != '\0' || prosign)
769 usleep(duration);
771 if (!prosign) {
772 duration = word_clock * CHAR_SPACE * 1000000;
773 usleep(duration);
777 static void
778 sighandler(int signo)
781 ioctl(line, TIOCMSET, &olflags);
782 tcsetattr(line, TCSANOW, &otty);
784 signal(signo, SIG_DFL);
785 kill(getpid(), signo);