Merge branch 'vendor/NCURSES'
[dragonfly.git] / games / morse / morse.c
1 /*
2  * Copyright (c) 1988, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
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.
20  *
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.
32  *
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 $
37  */
38
39 /*
40  * Taught to send *real* morse by Lyndon Nerenberg (VE7TCP/VE6BBM)
41  * <lyndon@orthanc.com>
42  */
43
44 #include <sys/time.h>
45 #include <sys/soundcard.h>
46
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>
59
60 struct morsetab {
61         char            inchar;
62         const char      *morse;
63 };
64
65 static const struct morsetab mtab[] = {
66
67         /* letters */
68
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', "--.."},
95
96         /* digits */
97
98         {'0', "-----"},
99         {'1', ".----"},
100         {'2', "..---"},
101         {'3', "...--"},
102         {'4', "....-"},
103         {'5', "....."},
104         {'6', "-...."},
105         {'7', "--..."},
106         {'8', "---.."},
107         {'9', "----."},
108
109         /* punctuation */
110
111         {',', "--..--"},
112         {'.', ".-.-.-"},
113         {'?', "..--.."},
114         {'!', "-.-.--"},        /* KW */
115         {'/', "-..-."},
116         {'-', "-....-"},
117         {'_', "..--.."},
118         {'=', "-...-"},         /* BT */
119         {':', "---..."},
120         {';', "-.-.-."},
121         {'(', "-.--."},         /* KN */
122         {')', "-.--.-"},
123         {'$', "...-..-"},
124         {'+', ".-.-."},         /* AR */
125         {'\'', ".----."},
126         {'"', ".-..-."},
127         {'@', ".--.-."},        /* AC */
128
129         {'\0', ""}
130 };
131
132
133 static const struct morsetab iso8859tab[] = {
134         {'á', ".--.-"},
135         {'à', ".--.-"},
136         {'â', ".--.-"},
137         {'ä', ".-.-"},
138         {'ç', "-.-.."},
139         {'é', "..-.."},
140         {'è', "..-.."},
141         {'ê', "-..-."},
142         {'ö', "---."},
143         {'ü', "..--"},
144
145         {'\0', ""}
146 };
147
148 static const struct morsetab koi8rtab[] = {
149         /*
150          * the cyrillic alphabet; you'll need a KOI8R font in order
151          * to see the actual characters
152          */
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 */
185
186         {'\0', ""}
187 };
188
189 struct tone_data {
190         int16_t *data;
191         size_t  len;
192 };
193
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);
200
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"
204
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 */
211
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;
215
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;
223
224 static const struct morsetab *hightab;
225
226 int
227 main(int argc, char **argv)
228 {
229         int    ch, lflags;
230         int    prosign;
231         char  *p, *codeset;
232
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);
267                 }
268         if (pflag + !!device + sflag > 1) {
269                 fputs("morse: only one of -o, -p, -d and -s allowed\n", stderr);
270                 exit(1);
271         }
272         if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (farnsworth > 60))) {
273                 fputs("morse: insane speed\n", stderr);
274                 exit(1);
275         }
276         if ((pflag || device) && (freq == 0))
277                 freq = FREQUENCY;
278         if (pflag || device) {
279                 /*
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
284                  */
285                 dot_clock = wpm / 1.2;          /* dots/sec */
286                 dot_clock = 1 / dot_clock;      /* duration of a dot */
287
288                 word_clock = dot_clock;
289
290                 /*
291                  * This is how to get to this formula:
292                  * PARIS = 22 dit (symbols) + 9 symbol spaces = 31 symbol times
293                  *       + 19 space times.
294                  *
295                  * The symbol times are in dot_clock, so the spaces have to
296                  * make up to reach the farnsworth time.
297                  */
298                 if (farnsworth > 0)
299                         word_clock = (60.0 / farnsworth - 31 * dot_clock) / 19;
300         }
301         if (snddev == NULL) {
302                 if (oflag)
303                         snddev = "-";
304                 else /* only pflag */
305                         snddev = "/dev/dsp";
306         }
307
308         if (pflag) {
309                 snd_chan_param param;
310
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);
332                 }
333                 if (tcgetattr(line, &otty) == -1) {
334                         perror("tcgetattr() failed");
335                         exit(1);
336                 }
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);
351         }
352
353         argc -= optind;
354         argv += optind;
355
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;
363         }
364
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;
374                                 }
375                                 if (strchr("> \r\n", *(p + 1)) != NULL)
376                                         prosign = 0;
377                                 morse(*p, prosign);
378                         }
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;
391                         }
392                         if (prosign) {
393                                 int tch;
394
395                                 tch = getchar();
396                                 if (strchr("> \r\n", tch) != NULL)
397                                         prosign = 0;
398                                 if (tch != '>')
399                                         ungetc(tch, stdin);
400                         }
401                         morse(ch, prosign);
402                 }
403         }
404         if (device)
405                 tcsetattr(line, TCSANOW, &otty);
406         exit(0);
407 }
408
409 void
410 alloc_soundbuf(struct tone_data *tone, double len, int on)
411 {
412         int samples, i;
413
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;
422         }
423
424         /*
425          * We create a sinus with the specified frequency and smooth
426          * the edges to reduce key clicks.
427          */
428         for (i = 0; i < samples; i++) {
429                 double filter = 1;
430
431 #define FILTER_SAMPLES (DSP_RATE * 8 / 1000)    /* 8 ms ramp time */
432                 if (i < FILTER_SAMPLES || i > samples - FILTER_SAMPLES) {
433                         int fi = i;
434
435                         if (i > FILTER_SAMPLES)
436                                 fi = samples - i;
437 #if defined(TRIANGLE_FILTER)
438                         /*
439                          * Triangle envelope
440                          */
441                         filter = (double)fi / FILTER_SAMPLES;
442 #elif defined(GAUSS_FILTER)
443                         /*
444                          * Gauss envelope
445                          */
446                         filter = exp(-4.0 *
447                                      pow((double)(FILTER_SAMPLES - fi) /
448                                          FILTER_SAMPLES, 2));
449 #else
450                         /*
451                          * Cosine envelope
452                          */
453                         filter = (1 + cos(M_PI * (FILTER_SAMPLES - fi) / FILTER_SAMPLES)) / 2;
454 #endif
455                 }
456                 tone->data[i] = 32767 * sin((double)i / samples * len * freq * 2 * M_PI) *
457                     filter;
458         }
459 }
460
461 void
462 morse(char c, int prosign)
463 {
464         const struct morsetab *m;
465
466         if (isalpha((unsigned char)c))
467                 c = tolower((unsigned char)c);
468         if ((c == '\r') || (c == '\n'))
469                 c = ' ';
470         if (c == ' ') {
471                 if (pflag) {
472                         play(" ", 0);
473                         return;
474                 } else if (device) {
475                         ttyout(" ", 0);
476                         return;
477                 } else {
478                         show("", 0);
479                         return;
480                 }
481         }
482         for (m = ((unsigned char)c < 0x80? mtab: hightab);
483              m != NULL && m->inchar != '\0';
484              m++) {
485                 if (m->inchar == c) {
486                         if (pflag) {
487                                 play(m->morse, prosign);
488                         } else if (device) {
489                                 ttyout(m->morse, prosign);
490                         } else
491                                 show(m->morse, prosign);
492                 }
493         }
494 }
495
496 void
497 show(const char *s, int prosign)
498 {
499         if (sflag)
500                 printf(" %s", s);
501         else
502                 for (; *s; ++s)
503                         printf(" %s", *s == '.' ? "dit" : "dah");
504         if (!prosign)
505                 printf("\n");
506 }
507
508 void
509 play(const char *s, int prosign)
510 {
511         const char *c;
512         int duration;
513         struct tone_data *tone;
514
515         /*
516          * We don't need to usleep() here, as the sound device blocks.
517          */
518         for (c = s; *c != '\0'; c++) {
519                 switch (*c) {
520                 case '.':
521                         duration = 1;
522                         tone = &tone_dot;
523                         break;
524                 case '-':
525                         duration = 1;
526                         tone = &tone_dash;
527                         break;
528                 case ' ':
529                         duration = WORD_SPACE;
530                         tone = &tone_letter_silence;
531                         break;
532                 default:
533                         errx(1, "invalid morse digit");
534                 }
535                 while (duration-- > 0)
536                         write(spkr, tone->data, tone->len);
537                 /* Only space within a symbol */
538                 if (c[1] != '\0' || prosign)
539                         write(spkr, tone_silence.data, tone_silence.len);
540         }
541         if (prosign)
542                 return;
543         duration = CHAR_SPACE;
544         while (duration-- > 0)
545                 write(spkr, tone_letter_silence.data, tone_letter_silence.len);
546
547         /* Sync out the audio data with other output */
548         if (!oflag)
549                 ioctl(spkr, SNDCTL_DSP_SYNC, NULL);
550 }
551
552 void
553 ttyout(const char *s, int prosign)
554 {
555         const char *c;
556         int duration, on, lflags;
557
558         for (c = s; *c != '\0'; c++) {
559                 switch (*c) {
560                 case '.':
561                         on = 1;
562                         duration = dot_clock;
563                         break;
564                 case '-':
565                         on = 1;
566                         duration = dot_clock * DASH_LEN;
567                         break;
568                 case ' ':
569                         on = 0;
570                         duration = word_clock * WORD_SPACE;
571                         break;
572                 default:
573                         on = 0;
574                         duration = 0;
575                 }
576                 if (on) {
577                         ioctl(line, TIOCMGET, &lflags);
578                         lflags |= TIOCM_RTS;
579                         ioctl(line, TIOCMSET, &lflags);
580                 }
581                 duration *= 1000000;
582                 if (duration)
583                         usleep(duration);
584                 ioctl(line, TIOCMGET, &lflags);
585                 lflags &= ~TIOCM_RTS;
586                 ioctl(line, TIOCMSET, &lflags);
587                 duration = dot_clock * 1000000;
588                 /* Only space within a symbol */
589                 if (c[1] != '\0' || prosign)
590                         usleep(duration);
591         }
592         if (!prosign) {
593                 duration = word_clock * CHAR_SPACE * 1000000;
594                 usleep(duration);
595         }
596 }
597
598 void
599 sighandler(int signo)
600 {
601
602         ioctl(line, TIOCMSET, &olflags);
603         tcsetattr(line, TCSANOW, &otty);
604
605         signal(signo, SIG_DFL);
606         (void)kill(getpid(), signo);
607 }