Merge branch 'vendor/GMP'
[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. 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.
16  *
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.
28  *
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 $
33  */
34
35 /*
36  * Taught to send *real* morse by Lyndon Nerenberg (VE7TCP/VE6BBM)
37  * <lyndon@orthanc.com>
38  */
39
40 #include <sys/time.h>
41 #include <sys/soundcard.h>
42
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>
55
56 struct morsetab {
57         char            inchar;
58         const char      *morse;
59 };
60
61 static const struct morsetab mtab[] = {
62
63         /* letters */
64
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', "--.."},
91
92         /* digits */
93
94         {'0', "-----"},
95         {'1', ".----"},
96         {'2', "..---"},
97         {'3', "...--"},
98         {'4', "....-"},
99         {'5', "....."},
100         {'6', "-...."},
101         {'7', "--..."},
102         {'8', "---.."},
103         {'9', "----."},
104
105         /* punctuation */
106
107         {',', "--..--"},
108         {'.', ".-.-.-"},
109         {'?', "..--.."},
110         {'!', "-.-.--"},        /* KW */
111         {'/', "-..-."},
112         {'-', "-....-"},
113         {'_', "..--.."},
114         {'=', "-...-"},         /* BT */
115         {':', "---..."},
116         {';', "-.-.-."},
117         {'(', "-.--."},         /* KN */
118         {')', "-.--.-"},
119         {'$', "...-..-"},
120         {'+', ".-.-."},         /* AR */
121         {'\'', ".----."},
122         {'"', ".-..-."},
123         {'@', ".--.-."},        /* AC */
124
125         {'\0', ""}
126 };
127
128
129 static const struct morsetab iso8859tab[] = {
130         {'á', ".--.-"},
131         {'à', ".--.-"},
132         {'â', ".--.-"},
133         {'ä', ".-.-"},
134         {'ç', "-.-.."},
135         {'é', "..-.."},
136         {'è', "..-.."},
137         {'ê', "-..-."},
138         {'ö', "---."},
139         {'ü', "..--"},
140
141         {'\0', ""}
142 };
143
144 static const struct morsetab koi8rtab[] = {
145         /*
146          * the cyrillic alphabet; you'll need a KOI8R font in order
147          * to see the actual characters
148          */
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 */
181
182         {'\0', ""}
183 };
184
185 struct tone_data {
186         int16_t *data;
187         size_t  len;
188 };
189
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);
196
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"
200
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 */
207
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;
211
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;
219
220 static const struct morsetab *hightab;
221
222 int
223 main(int argc, char **argv)
224 {
225         int    ch, lflags;
226         int    prosign;
227         char  *p, *codeset;
228
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);
263                 }
264         if (pflag + !!device + sflag > 1) {
265                 fputs("morse: only one of -o, -p, -d and -s allowed\n", stderr);
266                 exit(1);
267         }
268         if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (farnsworth > 60))) {
269                 fputs("morse: insane speed\n", stderr);
270                 exit(1);
271         }
272         if ((pflag || device) && (freq == 0))
273                 freq = FREQUENCY;
274         if (pflag || device) {
275                 /*
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
280                  */
281                 dot_clock = wpm / 1.2;          /* dots/sec */
282                 dot_clock = 1 / dot_clock;      /* duration of a dot */
283
284                 word_clock = dot_clock;
285
286                 /*
287                  * This is how to get to this formula:
288                  * PARIS = 22 dit (symbols) + 9 symbol spaces = 31 symbol times
289                  *       + 19 space times.
290                  *
291                  * The symbol times are in dot_clock, so the spaces have to
292                  * make up to reach the farnsworth time.
293                  */
294                 if (farnsworth > 0)
295                         word_clock = (60.0 / farnsworth - 31 * dot_clock) / 19;
296         }
297         if (snddev == NULL) {
298                 if (oflag)
299                         snddev = "-";
300                 else /* only pflag */
301                         snddev = "/dev/dsp";
302         }
303
304         if (pflag) {
305                 snd_chan_param param;
306
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);
328                 }
329                 if (tcgetattr(line, &otty) == -1) {
330                         perror("tcgetattr() failed");
331                         exit(1);
332                 }
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);
347         }
348
349         argc -= optind;
350         argv += optind;
351
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;
359         }
360
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;
370                                 }
371                                 if (strchr("> \r\n", *(p + 1)) != NULL)
372                                         prosign = 0;
373                                 morse(*p, prosign);
374                         }
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;
387                         }
388                         if (prosign) {
389                                 int tch;
390
391                                 tch = getchar();
392                                 if (strchr("> \r\n", tch) != NULL)
393                                         prosign = 0;
394                                 if (tch != '>')
395                                         ungetc(tch, stdin);
396                         }
397                         morse(ch, prosign);
398                 }
399         }
400         if (device)
401                 tcsetattr(line, TCSANOW, &otty);
402         exit(0);
403 }
404
405 void
406 alloc_soundbuf(struct tone_data *tone, double len, int on)
407 {
408         int samples, i;
409
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;
418         }
419
420         /*
421          * We create a sinus with the specified frequency and smooth
422          * the edges to reduce key clicks.
423          */
424         for (i = 0; i < samples; i++) {
425                 double filter = 1;
426
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;
430
431                         if (i > FILTER_SAMPLES)
432                                 fi = samples - i;
433 #if defined(TRIANGLE_FILTER)
434                         /*
435                          * Triangle envelope
436                          */
437                         filter = (double)fi / FILTER_SAMPLES;
438 #elif defined(GAUSS_FILTER)
439                         /*
440                          * Gauss envelope
441                          */
442                         filter = exp(-4.0 *
443                                      pow((double)(FILTER_SAMPLES - fi) /
444                                          FILTER_SAMPLES, 2));
445 #else
446                         /*
447                          * Cosine envelope
448                          */
449                         filter = (1 + cos(M_PI * (FILTER_SAMPLES - fi) / FILTER_SAMPLES)) / 2;
450 #endif
451                 }
452                 tone->data[i] = 32767 * sin((double)i / samples * len * freq * 2 * M_PI) *
453                     filter;
454         }
455 }
456
457 void
458 morse(char c, int prosign)
459 {
460         const struct morsetab *m;
461
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;
476                 }
477         }
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);
488                 }
489         }
490 }
491
492 void
493 show(const char *s, int prosign)
494 {
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");
502 }
503
504 void
505 play(const char *s, int prosign)
506 {
507         const char *c;
508         int duration;
509         struct tone_data *tone;
510
511         /*
512          * We don't need to usleep() here, as the sound device blocks.
513          */
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");
530                 }
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);
536         }
537         if (prosign)
538                 return;
539         duration = CHAR_SPACE;
540         while (duration-- > 0)
541                 write(spkr, tone_letter_silence.data, tone_letter_silence.len);
542
543         /* Sync out the audio data with other output */
544         if (!oflag)
545                 ioctl(spkr, SNDCTL_DSP_SYNC, NULL);
546 }
547
548 void
549 ttyout(const char *s, int prosign)
550 {
551         const char *c;
552         int duration, on, lflags;
553
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;
571                 }
572                 if (on) {
573                         ioctl(line, TIOCMGET, &lflags);
574                         lflags |= TIOCM_RTS;
575                         ioctl(line, TIOCMSET, &lflags);
576                 }
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);
587         }
588         if (!prosign) {
589                 duration = word_clock * CHAR_SPACE * 1000000;
590                 usleep(duration);
591         }
592 }
593
594 void
595 sighandler(int signo)
596 {
597
598         ioctl(line, TIOCMSET, &olflags);
599         tcsetattr(line, TCSANOW, &otty);
600
601         signal(signo, SIG_DFL);
602         (void)kill(getpid(), signo);
603 }