MFC: Make apps using '#define _POSIX_C_SOURCE' compile.
[dragonfly.git] / games / morse / morse.c
blobeb79b5b6564f710d1d2969f961ac3799126e4799
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. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
33 * @(#) Copyright (c) 1988, 1993 The Regents of the University of California. All rights reserved.
34 * @(#)morse.c 8.1 (Berkeley) 5/31/93
35 * $FreeBSD: src/games/morse/morse.c,v 1.12.2.2 2002/03/12 17:45:15 phantom Exp $
36 * $DragonFly: src/games/morse/morse.c,v 1.8 2008/05/30 21:47:04 corecode Exp $
40 * Taught to send *real* morse by Lyndon Nerenberg (VE7TCP/VE6BBM)
41 * <lyndon@orthanc.com>
44 #include <sys/time.h>
45 #include <sys/soundcard.h>
47 #include <ctype.h>
48 #include <err.h>
49 #include <fcntl.h>
50 #include <langinfo.h>
51 #include <locale.h>
52 #include <math.h>
53 #include <signal.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <termios.h>
58 #include <unistd.h>
60 struct morsetab {
61 char inchar;
62 const char *morse;
65 static const struct morsetab mtab[] = {
67 /* letters */
69 {'a', ".-"},
70 {'b', "-..."},
71 {'c', "-.-."},
72 {'d', "-.."},
73 {'e', "."},
74 {'f', "..-."},
75 {'g', "--."},
76 {'h', "...."},
77 {'i', ".."},
78 {'j', ".---"},
79 {'k', "-.-"},
80 {'l', ".-.."},
81 {'m', "--"},
82 {'n', "-."},
83 {'o', "---"},
84 {'p', ".--."},
85 {'q', "--.-"},
86 {'r', ".-."},
87 {'s', "..."},
88 {'t', "-"},
89 {'u', "..-"},
90 {'v', "...-"},
91 {'w', ".--"},
92 {'x', "-..-"},
93 {'y', "-.--"},
94 {'z', "--.."},
96 /* digits */
98 {'0', "-----"},
99 {'1', ".----"},
100 {'2', "..---"},
101 {'3', "...--"},
102 {'4', "....-"},
103 {'5', "....."},
104 {'6', "-...."},
105 {'7', "--..."},
106 {'8', "---.."},
107 {'9', "----."},
109 /* punctuation */
111 {',', "--..--"},
112 {'.', ".-.-.-"},
113 {'?', "..--.."},
114 {'!', "-.-.--"}, /* KW */
115 {'/', "-..-."},
116 {'-', "-....-"},
117 {'_', "..--.."},
118 {'=', "-...-"}, /* BT */
119 {':', "---..."},
120 {';', "-.-.-."},
121 {'(', "-.--."}, /* KN */
122 {')', "-.--.-"},
123 {'$', "...-..-"},
124 {'+', ".-.-."}, /* AR */
125 {'\'', ".----."},
126 {'"', ".-..-."},
127 {'@', ".--.-."}, /* AC */
129 {'\0', ""}
133 static const struct morsetab iso8859tab[] = {
134 {'á', ".--.-"},
135 {'à', ".--.-"},
136 {'â', ".--.-"},
137 {'ä', ".-.-"},
138 {'ç', "-.-.."},
139 {'é', "..-.."},
140 {'è', "..-.."},
141 {'ê', "-..-."},
142 {'ö', "---."},
143 {'ü', "..--"},
145 {'\0', ""}
148 static const struct morsetab koi8rtab[] = {
150 * the cyrillic alphabet; you'll need a KOI8R font in order
151 * to see the actual characters
153 {'Á', ".-"}, /* a */
154 {'Â', "-..."}, /* be */
155 {'×', ".--"}, /* ve */
156 {'Ç', "--."}, /* ge */
157 {'Ä', "-.."}, /* de */
158 {'Å', "."}, /* ye */
159 {'£', "."}, /* yo, the same as ye */
160 {'Ö', "...-"}, /* she */
161 {'Ú', "--.."}, /* ze */
162 {'É', ".."}, /* i */
163 {'Ê', ".---"}, /* i kratkoye */
164 {'Ë', "-.-"}, /* ka */
165 {'Ì', ".-.."}, /* el */
166 {'Í', "--"}, /* em */
167 {'Î', "-."}, /* en */
168 {'Ï', "---"}, /* o */
169 {'Ð', ".--."}, /* pe */
170 {'Ò', ".-."}, /* er */
171 {'Ó', "..."}, /* es */
172 {'Ô', "-"}, /* te */
173 {'Õ', "..-"}, /* u */
174 {'Æ', "..-."}, /* ef */
175 {'È', "...."}, /* kha */
176 {'Ã', "-.-."}, /* ce */
177 {'Þ', "---."}, /* che */
178 {'Û', "----"}, /* sha */
179 {'Ý', "--.-"}, /* shcha */
180 {'Ù', "-.--"}, /* yi */
181 {'Ø', "-..-"}, /* myakhkij znak */
182 {'Ü', "..-.."}, /* ae */
183 {'À', "..--"}, /* yu */
184 {'Ñ', ".-.-"}, /* ya */
186 {'\0', ""}
189 struct tone_data {
190 int16_t *data;
191 size_t len;
194 void alloc_soundbuf(struct tone_data *, double, int);
195 void morse(char, int);
196 void show(const char *, int);
197 void play(const char *, int);
198 void ttyout(const char *, int);
199 void sighandler(int);
201 #define GETOPTOPTS "d:ef:opP:sw:W:"
202 #define USAGE \
203 "usage: morse [-s] [-e] [-p | -o] [-P device] [-d device] [-w speed] [-W speed] [-f frequency] [string ...]\n"
205 static int oflag, pflag, sflag, eflag;
206 static int wpm = 20; /* words per minute */
207 static int farnsworth = -1;
208 #define FREQUENCY 600
209 static int freq = FREQUENCY;
210 static char *device; /* for tty-controlled generator */
212 static struct tone_data tone_dot, tone_dash, tone_silence, tone_letter_silence;
213 #define DSP_RATE 44100
214 static const char *snddev = NULL;
216 #define DASH_LEN 3
217 #define CHAR_SPACE 3
218 #define WORD_SPACE (7 - CHAR_SPACE)
219 static float dot_clock, word_clock;
220 int spkr, line;
221 struct termios otty, ntty;
222 int olflags;
224 static const struct morsetab *hightab;
227 main(int argc, char **argv)
229 int ch, lflags;
230 int prosign;
231 char *p, *codeset;
233 while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1)
234 switch ((char) ch) {
235 case 'd':
236 device = optarg;
237 break;
238 case 'e':
239 eflag = 1;
240 setvbuf(stdout, 0, _IONBF, 0);
241 break;
242 case 'f':
243 freq = atoi(optarg);
244 break;
245 case 'o':
246 oflag = 1;
247 /* FALLTHROUGH */
248 case 'p':
249 pflag = 1;
250 break;
251 case 'P':
252 snddev = optarg;
253 break;
254 case 's':
255 sflag = 1;
256 break;
257 case 'w':
258 wpm = atoi(optarg);
259 break;
260 case 'W':
261 farnsworth = atoi(optarg);
262 break;
263 case '?':
264 default:
265 fputs(USAGE, stderr);
266 exit(1);
268 if (pflag + !!device + sflag > 1) {
269 fputs("morse: only one of -o, -p, -d and -s allowed\n", stderr);
270 exit(1);
272 if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (farnsworth > 60))) {
273 fputs("morse: insane speed\n", stderr);
274 exit(1);
276 if ((pflag || device) && (freq == 0))
277 freq = FREQUENCY;
278 if (pflag || device) {
280 * A note on how to get to this magic 1.2:
281 * x WPM = 50*x dits per minute (norm word "PARIS").
282 * dits per sec = dits per minute / 60, thus
283 * dits per sec = 50 * x / 60 = x / (60 / 50) = x / 1.2
285 dot_clock = wpm / 1.2; /* dots/sec */
286 dot_clock = 1 / dot_clock; /* duration of a dot */
288 word_clock = dot_clock;
291 * This is how to get to this formula:
292 * PARIS = 22 dit (symbols) + 9 symbol spaces = 31 symbol times
293 * + 19 space times.
295 * The symbol times are in dot_clock, so the spaces have to
296 * make up to reach the farnsworth time.
298 if (farnsworth > 0)
299 word_clock = (60.0 / farnsworth - 31 * dot_clock) / 19;
301 if (snddev == NULL) {
302 if (oflag)
303 snddev = "-";
304 else /* only pflag */
305 snddev = "/dev/dsp";
308 if (pflag) {
309 snd_chan_param param;
311 if (oflag && strcmp(snddev, "-") == 0)
312 spkr = STDOUT_FILENO;
313 else
314 spkr = open(snddev, O_WRONLY, 0);
315 if (spkr == -1)
316 err(1, "%s", snddev);
317 param.play_rate = DSP_RATE;
318 param.play_format = AFMT_S16_NE;
319 param.rec_rate = 0;
320 param.rec_format = 0;
321 if (!oflag && ioctl(spkr, AIOSFMT, &param) != 0)
322 err(1, "%s: set format", snddev);
323 alloc_soundbuf(&tone_dot, dot_clock, 1);
324 alloc_soundbuf(&tone_dash, DASH_LEN * dot_clock, 1);
325 alloc_soundbuf(&tone_silence, dot_clock, 0);
326 alloc_soundbuf(&tone_letter_silence, word_clock, 0);
327 } else
328 if (device) {
329 if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) {
330 perror("open tty line");
331 exit(1);
333 if (tcgetattr(line, &otty) == -1) {
334 perror("tcgetattr() failed");
335 exit(1);
337 ntty = otty;
338 ntty.c_cflag |= CLOCAL;
339 tcsetattr(line, TCSANOW, &ntty);
340 lflags = fcntl(line, F_GETFL);
341 lflags &= ~O_NONBLOCK;
342 fcntl(line, F_SETFL, &lflags);
343 ioctl(line, TIOCMGET, &lflags);
344 lflags &= ~TIOCM_RTS;
345 olflags = lflags;
346 ioctl(line, TIOCMSET, &lflags);
347 (void)signal(SIGHUP, sighandler);
348 (void)signal(SIGINT, sighandler);
349 (void)signal(SIGQUIT, sighandler);
350 (void)signal(SIGTERM, sighandler);
353 argc -= optind;
354 argv += optind;
356 if (setlocale(LC_CTYPE, "") != NULL &&
357 *(codeset = nl_langinfo(CODESET)) != '\0') {
358 if (strcmp(codeset, "KOI8-R") == 0)
359 hightab = koi8rtab;
360 else if (strcmp(codeset, "ISO8859-1") == 0 ||
361 strcmp(codeset, "ISO8859-15") == 0)
362 hightab = iso8859tab;
365 if (*argv) {
366 do {
367 prosign = 0;
368 for (p = *argv; *p; ++p) {
369 if (eflag)
370 putchar(*p);
371 if (*p == '<' || *p == '>') {
372 prosign = *p == '<';
373 continue;
375 if (strchr("> \r\n", *(p + 1)) != NULL)
376 prosign = 0;
377 morse(*p, prosign);
379 if (eflag)
380 putchar(' ');
381 morse(' ', 0);
382 } while (*++argv);
383 } else {
384 prosign = 0;
385 while ((ch = getchar()) != EOF) {
386 if (eflag)
387 putchar(ch);
388 if (ch == '<') {
389 prosign = 1;
390 continue;
392 if (prosign) {
393 int tch;
395 tch = getchar();
396 if (strchr("> \r\n", tch) != NULL)
397 prosign = 0;
398 if (tch != '>')
399 ungetc(tch, stdin);
401 morse(ch, prosign);
404 if (device)
405 tcsetattr(line, TCSANOW, &otty);
406 exit(0);
409 void
410 alloc_soundbuf(struct tone_data *tone, double len, int on)
412 int samples, i;
414 samples = DSP_RATE * len;
415 tone->len = samples * sizeof(*tone->data);
416 tone->data = malloc(tone->len);
417 if (tone->data == NULL)
418 err(1, NULL);
419 if (!on) {
420 bzero(tone->data, tone->len);
421 return;
425 * We create a sinus with the specified frequency and smooth
426 * the edges to reduce key clicks.
428 for (i = 0; i < samples; i++) {
429 double filter = 1;
431 #define FILTER_SAMPLES 100
432 if (i < FILTER_SAMPLES || i > samples - FILTER_SAMPLES) {
434 * Gauss window
436 #if 0
437 int fi = i;
439 if (i > FILTER_SAMPLES)
440 fi = samples - i;
441 filter = exp(-0.5 *
442 pow((double)(fi - FILTER_SAMPLES) /
443 (0.4 * FILTER_SAMPLES), 2));
444 #else
446 * Triangle window
448 if (i < FILTER_SAMPLES)
449 filter = (double)i / FILTER_SAMPLES;
450 else
451 filter = (double)(samples - i) / FILTER_SAMPLES;
452 #endif
454 tone->data[i] = 32767 * sin((double)i / samples * len * freq * 2 * M_PI) *
455 filter;
459 void
460 morse(char c, int prosign)
462 const struct morsetab *m;
464 if (isalpha((unsigned char)c))
465 c = tolower((unsigned char)c);
466 if ((c == '\r') || (c == '\n'))
467 c = ' ';
468 if (c == ' ') {
469 if (pflag) {
470 play(" ", 0);
471 return;
472 } else if (device) {
473 ttyout(" ", 0);
474 return;
475 } else {
476 show("", 0);
477 return;
480 for (m = ((unsigned char)c < 0x80? mtab: hightab);
481 m != NULL && m->inchar != '\0';
482 m++) {
483 if (m->inchar == c) {
484 if (pflag) {
485 play(m->morse, prosign);
486 } else if (device) {
487 ttyout(m->morse, prosign);
488 } else
489 show(m->morse, prosign);
494 void
495 show(const char *s, int prosign)
497 if (sflag)
498 printf(" %s", s);
499 else
500 for (; *s; ++s)
501 printf(" %s", *s == '.' ? "dit" : "dah");
502 if (!prosign)
503 printf("\n");
506 void
507 play(const char *s, int prosign)
509 const char *c;
510 int duration;
511 struct tone_data *tone;
514 * We don't need to usleep() here, as the sound device blocks.
516 for (c = s; *c != '\0'; c++) {
517 switch (*c) {
518 case '.':
519 duration = 1;
520 tone = &tone_dot;
521 break;
522 case '-':
523 duration = 1;
524 tone = &tone_dash;
525 break;
526 case ' ':
527 duration = WORD_SPACE;
528 tone = &tone_letter_silence;
529 break;
530 default:
531 errx(1, "invalid morse digit");
533 while (duration-- > 0)
534 write(spkr, tone->data, tone->len);
535 /* Only space within a symbol */
536 if (c[1] != '\0' || prosign)
537 write(spkr, tone_silence.data, tone_silence.len);
539 if (prosign)
540 return;
541 duration = CHAR_SPACE;
542 while (duration-- > 0)
543 write(spkr, tone_letter_silence.data, tone_letter_silence.len);
545 /* Sync out the audio data with other output */
546 if (!oflag)
547 ioctl(spkr, SNDCTL_DSP_SYNC, NULL);
550 void
551 ttyout(const char *s, int prosign)
553 const char *c;
554 int duration, on, lflags;
556 for (c = s; *c != '\0'; c++) {
557 switch (*c) {
558 case '.':
559 on = 1;
560 duration = dot_clock;
561 break;
562 case '-':
563 on = 1;
564 duration = dot_clock * DASH_LEN;
565 break;
566 case ' ':
567 on = 0;
568 duration = word_clock * WORD_SPACE;
569 break;
570 default:
571 on = 0;
572 duration = 0;
574 if (on) {
575 ioctl(line, TIOCMGET, &lflags);
576 lflags |= TIOCM_RTS;
577 ioctl(line, TIOCMSET, &lflags);
579 duration *= 1000000;
580 if (duration)
581 usleep(duration);
582 ioctl(line, TIOCMGET, &lflags);
583 lflags &= ~TIOCM_RTS;
584 ioctl(line, TIOCMSET, &lflags);
585 duration = dot_clock * 1000000;
586 /* Only space within a symbol */
587 if (c[1] != '\0' || prosign)
588 usleep(duration);
590 if (!prosign) {
591 duration = word_clock * CHAR_SPACE * 1000000;
592 usleep(duration);
596 void
597 sighandler(int signo)
600 ioctl(line, TIOCMSET, &olflags);
601 tcsetattr(line, TCSANOW, &otty);
603 signal(signo, SIG_DFL);
604 (void)kill(getpid(), signo);