Add the possibility to dump the generated audio to a file instead of playing it.
[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.5 2007/04/22 22:04:20 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         {'/', "-..-."},
115         {'-', "-....-"},
116         {'=', "-...-"},         /* BT */
117         {':', "---..."},
118         {';', "-.-.-."},
119         {'(', "-.--."},         /* KN */
120         {')', "-.--.-"},
121         {'$', "...-..-"},
122         {'+', ".-.-."},         /* AR */
123
124         /* prosigns without already assigned values */
125
126         {'#', ".-..."},         /* AS */
127         {'@', "...-.-"},        /* SK */
128         {'*', "...-."},         /* VE */
129         {'%', "-...-.-"},       /* BK */
130
131         {'\0', ""}
132 };
133
134
135 static const struct morsetab iso8859tab[] = {
136         {'á', ".--.-"},
137         {'à', ".--.-"},
138         {'â', ".--.-"},
139         {'ä', ".-.-"},
140         {'ç', "-.-.."},
141         {'é', "..-.."},
142         {'è', "..-.."},
143         {'ê', "-..-."},
144         {'ö', "---."},
145         {'ü', "..--"},
146
147         {'\0', ""}
148 };
149
150 static const struct morsetab koi8rtab[] = {
151         /*
152          * the cyrillic alphabet; you'll need a KOI8R font in order
153          * to see the actual characters
154          */
155         {'Á', ".-"},            /* a */
156         {'Â', "-..."},  /* be */
157         {'×', ".--"},   /* ve */
158         {'Ç', "--."},   /* ge */
159         {'Ä', "-.."},   /* de */
160         {'Å', "."},             /* ye */
161         {'£', "."},             /* yo, the same as ye */
162         {'Ö', "...-"},  /* she */
163         {'Ú', "--.."},  /* ze */
164         {'É', ".."},            /* i */
165         {'Ê', ".---"},  /* i kratkoye */
166         {'Ë', "-.-"},   /* ka */
167         {'Ì', ".-.."},  /* el */
168         {'Í', "--"},            /* em */
169         {'Î', "-."},            /* en */
170         {'Ï', "---"},   /* o */
171         {'Ð', ".--."},  /* pe */
172         {'Ò', ".-."},   /* er */
173         {'Ó', "..."},   /* es */
174         {'Ô', "-"},             /* te */
175         {'Õ', "..-"},   /* u */
176         {'Æ', "..-."},  /* ef */
177         {'È', "...."},  /* kha */
178         {'Ã', "-.-."},  /* ce */
179         {'Þ', "---."},  /* che */
180         {'Û', "----"},  /* sha */
181         {'Ý', "--.-"},  /* shcha */
182         {'Ù', "-.--"},  /* yi */
183         {'Ø', "-..-"},  /* myakhkij znak */
184         {'Ü', "..-.."}, /* ae */
185         {'À', "..--"},  /* yu */
186         {'Ñ', ".-.-"},  /* ya */
187
188         {'\0', ""}
189 };
190
191 struct tone_data {
192         int16_t *data;
193         size_t  len;
194 };
195
196 void            alloc_soundbuf(struct tone_data *, double, int);
197 void            show(const char *), play(const char *), morse(char);
198 void            ttyout(const char *);
199 void            sighandler(int);
200
201 #define GETOPTOPTS "d:ef:opP:sw:"
202 #define USAGE \
203 "usage: morse [-s] [-e] [-p | -o] [-P device] [-d device] [-w speed] [-f frequency] [string ...]\n"
204
205 static int      oflag, pflag, sflag, eflag;
206 static int      wpm = 20;       /* words per minute */
207 #define FREQUENCY 600
208 static int      freq = FREQUENCY;
209 static char     *device;        /* for tty-controlled generator */
210
211 static struct tone_data tone_dot, tone_dash, tone_silence;
212 #define DSP_RATE 44100
213 static const char *snddev = NULL;
214
215 #define DASH_LEN 3
216 #define CHAR_SPACE 3
217 #define WORD_SPACE (7 - CHAR_SPACE)
218 static float    dot_clock;
219 int             spkr, line;
220 struct termios  otty, ntty;
221 int             olflags;
222
223 static const struct morsetab *hightab;
224
225 int
226 main(int argc, char **argv)
227 {
228         int    ch, lflags;
229         char  *p, *codeset;
230
231         while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1)
232                 switch ((char) ch) {
233                 case 'd':
234                         device = optarg;
235                         break;
236                 case 'e':
237                         eflag = 1;
238                         setvbuf(stdout, 0, _IONBF, 0);
239                         break;
240                 case 'f':
241                         freq = atoi(optarg);
242                         break;
243                 case 'o':
244                         oflag = 1;
245                         /* FALLTHROUGH */
246                 case 'p':
247                         pflag = 1;
248                         break;
249                 case 'P':
250                         snddev = optarg;
251                         break;
252                 case 's':
253                         sflag = 1;
254                         break;
255                 case 'w':
256                         wpm = atoi(optarg);
257                         break;
258                 case '?':
259                 default:
260                         fputs(USAGE, stderr);
261                         exit(1);
262                 }
263         if (pflag + !!device + sflag > 1) {
264                 fputs("morse: only one of -o, -p, -d and -s allowed\n", stderr);
265                 exit(1);
266         }
267         if ((pflag || device) && ((wpm < 1) || (wpm > 60))) {
268                 fputs("morse: insane speed\n", stderr);
269                 exit(1);
270         }
271         if ((pflag || device) && (freq == 0))
272                 freq = FREQUENCY;
273         if (pflag || device) {
274                 /*
275                  * A note on how to get to this magic 2.4:
276                  * x WPM = 50*x dits per minute (norm word "PARIS").
277                  * dits per sec = dits per minute / 60, thus
278                  * dits per sec = 50 * x / 60 = x / (60 / 50) = x / 2.4
279                  */
280                 dot_clock = wpm / 2.4;          /* dots/sec */
281                 dot_clock = 1 / dot_clock;      /* duration of a dot */
282                 dot_clock = dot_clock / 2;      /* dot_clock runs at twice */
283                                                 /* the dot rate */
284         }
285         if (snddev == NULL) {
286                 if (oflag)
287                         snddev = "-";
288                 else /* only pflag */
289                         snddev = "/dev/dsp";
290         }
291
292         if (pflag) {
293                 snd_chan_param param;
294
295                 if (oflag && strcmp(snddev, "-") == 0)
296                         spkr = STDOUT_FILENO;
297                 else
298                         spkr = open(snddev, O_WRONLY, 0);
299                 if (spkr == -1)
300                         err(1, "%s", snddev);
301                 param.play_rate = DSP_RATE;
302                 param.play_format = AFMT_S16_NE;
303                 param.rec_rate = 0;
304                 param.rec_format = 0;
305                 if (!oflag && ioctl(spkr, AIOSFMT, &param) != 0)
306                         err(1, "%s: set format", snddev);
307                 alloc_soundbuf(&tone_dot, dot_clock, 1);
308                 alloc_soundbuf(&tone_dash, DASH_LEN * dot_clock, 1);
309                 alloc_soundbuf(&tone_silence, dot_clock, 0);
310         } else
311         if (device) {
312                 if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) {
313                         perror("open tty line");
314                         exit(1);
315                 }
316                 if (tcgetattr(line, &otty) == -1) {
317                         perror("tcgetattr() failed");
318                         exit(1);
319                 }
320                 ntty = otty;
321                 ntty.c_cflag |= CLOCAL;
322                 tcsetattr(line, TCSANOW, &ntty);
323                 lflags = fcntl(line, F_GETFL);
324                 lflags &= ~O_NONBLOCK;
325                 fcntl(line, F_SETFL, &lflags);
326                 ioctl(line, TIOCMGET, &lflags);
327                 lflags &= ~TIOCM_RTS;
328                 olflags = lflags;
329                 ioctl(line, TIOCMSET, &lflags);
330                 (void)signal(SIGHUP, sighandler);
331                 (void)signal(SIGINT, sighandler);
332                 (void)signal(SIGQUIT, sighandler);
333                 (void)signal(SIGTERM, sighandler);
334         }
335
336         argc -= optind;
337         argv += optind;
338
339         if (setlocale(LC_CTYPE, "") != NULL &&
340             *(codeset = nl_langinfo(CODESET)) != '\0') {
341                 if (strcmp(codeset, "KOI8-R") == 0)
342                         hightab = koi8rtab;
343                 else if (strcmp(codeset, "ISO8859-1") == 0 ||
344                          strcmp(codeset, "ISO8859-15") == 0)
345                         hightab = iso8859tab;
346         }
347
348         if (*argv) {
349                 do {
350                         for (p = *argv; *p; ++p) {
351                                 if (eflag)
352                                         putchar(*p);
353                                 morse(*p);
354                         }
355                         if (eflag)
356                                 putchar(' ');
357                         morse(' ');
358                 } while (*++argv);
359         } else {
360                 while ((ch = getchar()) != EOF) {
361                         if (eflag)
362                                 putchar(ch);
363                         morse(ch);
364                 }
365         }
366         if (device)
367                 tcsetattr(line, TCSANOW, &otty);
368         exit(0);
369 }
370
371 void
372 alloc_soundbuf(struct tone_data *tone, double len, int on)
373 {
374         int samples, i;
375
376         samples = DSP_RATE * len;
377         tone->len = samples * sizeof(*tone->data);
378         tone->data = malloc(tone->len);
379         if (tone->data == NULL)
380                 err(1, NULL);
381         if (!on) {
382                 bzero(tone->data, tone->len);
383                 return;
384         }
385
386         /*
387          * We create a sinus with the specified frequency and smooth
388          * the edges to reduce key clicks.
389          */
390         for (i = 0; i < samples; i++) {
391                 double filter = 1;
392
393 #define FILTER_SAMPLES 100
394                 if (i < FILTER_SAMPLES || i > samples - FILTER_SAMPLES) {
395                         /*
396                          * Gauss window
397                          */
398 #if 0
399                         int fi = i;
400
401                         if (i > FILTER_SAMPLES)
402                                 fi = samples - i;
403                         filter = exp(-0.5 *
404                                      pow((double)(fi - FILTER_SAMPLES) /
405                                          (0.4 * FILTER_SAMPLES), 2));
406 #else
407                         /*
408                          * Triangle window
409                          */
410                         if (i < FILTER_SAMPLES)
411                                 filter = (double)i / FILTER_SAMPLES;
412                         else
413                                 filter = (double)(samples - i) / FILTER_SAMPLES;
414 #endif
415                 }
416                 tone->data[i] = 32767 * sin((double)i / samples * len * freq * 2 * M_PI) *
417                     filter;
418         }
419 }
420
421 void
422 morse(char c)
423 {
424         const struct morsetab *m;
425
426         if (isalpha((unsigned char)c))
427                 c = tolower((unsigned char)c);
428         if ((c == '\r') || (c == '\n'))
429                 c = ' ';
430         if (c == ' ') {
431                 if (pflag) {
432                         play(" ");
433                         return;
434                 } else if (device) {
435                         ttyout(" ");
436                         return;
437                 } else {
438                         show("");
439                         return;
440                 }
441         }
442         for (m = ((unsigned char)c < 0x80? mtab: hightab);
443              m != NULL && m->inchar != '\0';
444              m++) {
445                 if (m->inchar == c) {
446                         if (pflag) {
447                                 play(m->morse);
448                         } else if (device) {
449                                 ttyout(m->morse);
450                         } else
451                                 show(m->morse);
452                 }
453         }
454 }
455
456 void
457 show(const char *s)
458 {
459         if (sflag)
460                 printf(" %s", s);
461         else
462                 for (; *s; ++s)
463                         printf(" %s", *s == '.' ? "dit" : "dah");
464         printf("\n");
465 }
466
467 void
468 play(const char *s)
469 {
470         const char *c;
471         int duration;
472         struct tone_data *tone;
473
474         /*
475          * We don't need to usleep() here, as the sound device blocks.
476          */
477         for (c = s; *c != '\0'; c++) {
478                 switch (*c) {
479                 case '.':
480                         duration = 1;
481                         tone = &tone_dot;
482                         break;
483                 case '-':
484                         duration = 1;
485                         tone = &tone_dash;
486                         break;
487                 case ' ':
488                         duration = WORD_SPACE;
489                         tone = &tone_silence;
490                         break;
491                 default:
492                         errx(1, "invalid morse digit");
493                 }
494                 while (duration-- > 0)
495                         write(spkr, tone->data, tone->len);
496                 write(spkr, tone_silence.data, tone_silence.len);
497         }
498         duration = CHAR_SPACE - 1;  /* we already waited 1 after the last symbol */
499         while (duration-- > 0)
500                 write(spkr, tone_silence.data, tone_silence.len);
501 }
502
503 void
504 ttyout(const char *s)
505 {
506         const char *c;
507         int duration, on, lflags;
508
509         for (c = s; *c != '\0'; c++) {
510                 switch (*c) {
511                 case '.':
512                         on = 1;
513                         duration = dot_clock;
514                         break;
515                 case '-':
516                         on = 1;
517                         duration = dot_clock * DASH_LEN;
518                         break;
519                 case ' ':
520                         on = 0;
521                         duration = dot_clock * WORD_SPACE;
522                         break;
523                 default:
524                         on = 0;
525                         duration = 0;
526                 }
527                 if (on) {
528                         ioctl(line, TIOCMGET, &lflags);
529                         lflags |= TIOCM_RTS;
530                         ioctl(line, TIOCMSET, &lflags);
531                 }
532                 duration *= 1000000;
533                 if (duration)
534                         usleep(duration);
535                 ioctl(line, TIOCMGET, &lflags);
536                 lflags &= ~TIOCM_RTS;
537                 ioctl(line, TIOCMSET, &lflags);
538                 duration = dot_clock * 1000000;
539                 usleep(duration);
540         }
541         duration = dot_clock * CHAR_SPACE * 1000000;
542         usleep(duration);
543 }
544
545 void
546 sighandler(int signo)
547 {
548
549         ioctl(line, TIOCMSET, &olflags);
550         tcsetattr(line, TCSANOW, &otty);
551
552         signal(signo, SIG_DFL);
553         (void)kill(getpid(), signo);
554 }