xref: /dragonfly/games/morse/morse.c (revision 9b5ae8ee)
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.6 2007/04/22 23:03:48 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:"
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 	int    prosign;
230 	char  *p, *codeset;
231 
232 	while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1)
233 		switch ((char) ch) {
234 		case 'd':
235 			device = optarg;
236 			break;
237 		case 'e':
238 			eflag = 1;
239 			setvbuf(stdout, 0, _IONBF, 0);
240 			break;
241 		case 'f':
242 			freq = atoi(optarg);
243 			break;
244 		case 'o':
245 			oflag = 1;
246 			/* FALLTHROUGH */
247 		case 'p':
248 			pflag = 1;
249 			break;
250 		case 'P':
251 			snddev = optarg;
252 			break;
253 		case 's':
254 			sflag = 1;
255 			break;
256 		case 'w':
257 			wpm = 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))) {
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 2.4:
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 / 2.4
280 		 */
281 		dot_clock = wpm / 2.4;		/* dots/sec */
282 		dot_clock = 1 / dot_clock;	/* duration of a dot */
283 		dot_clock = dot_clock / 2;	/* dot_clock runs at twice */
284 						/* the dot rate */
285 	}
286 	if (snddev == NULL) {
287 		if (oflag)
288 			snddev = "-";
289 		else /* only pflag */
290 			snddev = "/dev/dsp";
291 	}
292 
293 	if (pflag) {
294 		snd_chan_param param;
295 
296 		if (oflag && strcmp(snddev, "-") == 0)
297 			spkr = STDOUT_FILENO;
298 		else
299 			spkr = open(snddev, O_WRONLY, 0);
300 		if (spkr == -1)
301 			err(1, "%s", snddev);
302 		param.play_rate = DSP_RATE;
303 		param.play_format = AFMT_S16_NE;
304 		param.rec_rate = 0;
305 		param.rec_format = 0;
306 		if (!oflag && ioctl(spkr, AIOSFMT, &param) != 0)
307 			err(1, "%s: set format", snddev);
308 		alloc_soundbuf(&tone_dot, dot_clock, 1);
309 		alloc_soundbuf(&tone_dash, DASH_LEN * dot_clock, 1);
310 		alloc_soundbuf(&tone_silence, dot_clock, 0);
311 	} else
312 	if (device) {
313 		if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) {
314 			perror("open tty line");
315 			exit(1);
316 		}
317 		if (tcgetattr(line, &otty) == -1) {
318 			perror("tcgetattr() failed");
319 			exit(1);
320 		}
321 		ntty = otty;
322 		ntty.c_cflag |= CLOCAL;
323 		tcsetattr(line, TCSANOW, &ntty);
324 		lflags = fcntl(line, F_GETFL);
325 		lflags &= ~O_NONBLOCK;
326 		fcntl(line, F_SETFL, &lflags);
327 		ioctl(line, TIOCMGET, &lflags);
328 		lflags &= ~TIOCM_RTS;
329 		olflags = lflags;
330 		ioctl(line, TIOCMSET, &lflags);
331 		(void)signal(SIGHUP, sighandler);
332 		(void)signal(SIGINT, sighandler);
333 		(void)signal(SIGQUIT, sighandler);
334 		(void)signal(SIGTERM, sighandler);
335 	}
336 
337 	argc -= optind;
338 	argv += optind;
339 
340 	if (setlocale(LC_CTYPE, "") != NULL &&
341 	    *(codeset = nl_langinfo(CODESET)) != '\0') {
342 		if (strcmp(codeset, "KOI8-R") == 0)
343 			hightab = koi8rtab;
344 		else if (strcmp(codeset, "ISO8859-1") == 0 ||
345 			 strcmp(codeset, "ISO8859-15") == 0)
346 			hightab = iso8859tab;
347 	}
348 
349 	if (*argv) {
350 		do {
351 			prosign = 0;
352 			for (p = *argv; *p; ++p) {
353 				if (eflag)
354 					putchar(*p);
355 				if (*p == '<' || *p == '>') {
356 					prosign = *p == '<';
357 					continue;
358 				}
359 				if (strchr("> \r\n", *(p + 1)) != NULL)
360 					prosign = 0;
361 				morse(*p, prosign);
362 			}
363 			if (eflag)
364 				putchar(' ');
365 			morse(' ', 0);
366 		} while (*++argv);
367 	} else {
368 		prosign = 0;
369 		while ((ch = getchar()) != EOF) {
370 			if (eflag)
371 				putchar(ch);
372 			if (ch == '<') {
373 				prosign = 1;
374 				continue;
375 			}
376 			if (prosign) {
377 				int tch;
378 
379 				tch = getchar();
380 				if (strchr("> \r\n", tch) != NULL)
381 					prosign = 0;
382 				if (tch != '>')
383 					ungetc(tch, stdin);
384 			}
385 			morse(ch, prosign);
386 		}
387 	}
388 	if (device)
389 		tcsetattr(line, TCSANOW, &otty);
390 	exit(0);
391 }
392 
393 void
394 alloc_soundbuf(struct tone_data *tone, double len, int on)
395 {
396 	int samples, i;
397 
398 	samples = DSP_RATE * len;
399 	tone->len = samples * sizeof(*tone->data);
400 	tone->data = malloc(tone->len);
401 	if (tone->data == NULL)
402 		err(1, NULL);
403 	if (!on) {
404 		bzero(tone->data, tone->len);
405 		return;
406 	}
407 
408 	/*
409 	 * We create a sinus with the specified frequency and smooth
410 	 * the edges to reduce key clicks.
411 	 */
412 	for (i = 0; i < samples; i++) {
413 		double filter = 1;
414 
415 #define FILTER_SAMPLES 100
416 		if (i < FILTER_SAMPLES || i > samples - FILTER_SAMPLES) {
417 			/*
418 			 * Gauss window
419 			 */
420 #if 0
421 			int fi = i;
422 
423 			if (i > FILTER_SAMPLES)
424 				fi = samples - i;
425 			filter = exp(-0.5 *
426 				     pow((double)(fi - FILTER_SAMPLES) /
427 					 (0.4 * FILTER_SAMPLES), 2));
428 #else
429 			/*
430 			 * Triangle window
431 			 */
432 			if (i < FILTER_SAMPLES)
433 				filter = (double)i / FILTER_SAMPLES;
434 			else
435 				filter = (double)(samples - i) / FILTER_SAMPLES;
436 #endif
437 		}
438 		tone->data[i] = 32767 * sin((double)i / samples * len * freq * 2 * M_PI) *
439 		    filter;
440 	}
441 }
442 
443 void
444 morse(char c, int prosign)
445 {
446 	const struct morsetab *m;
447 
448 	if (isalpha((unsigned char)c))
449 		c = tolower((unsigned char)c);
450 	if ((c == '\r') || (c == '\n'))
451 		c = ' ';
452 	if (c == ' ') {
453 		if (pflag) {
454 			play(" ", 0);
455 			return;
456 		} else if (device) {
457 			ttyout(" ", 0);
458 			return;
459 		} else {
460 			show("", 0);
461 			return;
462 		}
463 	}
464 	for (m = ((unsigned char)c < 0x80? mtab: hightab);
465 	     m != NULL && m->inchar != '\0';
466 	     m++) {
467 		if (m->inchar == c) {
468 			if (pflag) {
469 				play(m->morse, prosign);
470 			} else if (device) {
471 				ttyout(m->morse, prosign);
472 			} else
473 				show(m->morse, prosign);
474 		}
475 	}
476 }
477 
478 void
479 show(const char *s, int prosign)
480 {
481 	if (sflag)
482 		printf(" %s", s);
483 	else
484 		for (; *s; ++s)
485 			printf(" %s", *s == '.' ? "dit" : "dah");
486 	if (!prosign)
487 		printf("\n");
488 }
489 
490 void
491 play(const char *s, int prosign)
492 {
493 	const char *c;
494 	int duration;
495 	struct tone_data *tone;
496 
497 	/*
498 	 * We don't need to usleep() here, as the sound device blocks.
499 	 */
500 	for (c = s; *c != '\0'; c++) {
501 		switch (*c) {
502 		case '.':
503 			duration = 1;
504 			tone = &tone_dot;
505 			break;
506 		case '-':
507 			duration = 1;
508 			tone = &tone_dash;
509 			break;
510 		case ' ':
511 			duration = WORD_SPACE;
512 			tone = &tone_silence;
513 			break;
514 		default:
515 			errx(1, "invalid morse digit");
516 		}
517 		while (duration-- > 0)
518 			write(spkr, tone->data, tone->len);
519 		write(spkr, tone_silence.data, tone_silence.len);
520 	}
521 	if (prosign)
522 		return;
523 	duration = CHAR_SPACE - 1;  /* we already waited 1 after the last symbol */
524 	while (duration-- > 0)
525 		write(spkr, tone_silence.data, tone_silence.len);
526 }
527 
528 void
529 ttyout(const char *s, int prosign)
530 {
531 	const char *c;
532 	int duration, on, lflags;
533 
534 	for (c = s; *c != '\0'; c++) {
535 		switch (*c) {
536 		case '.':
537 			on = 1;
538 			duration = dot_clock;
539 			break;
540 		case '-':
541 			on = 1;
542 			duration = dot_clock * DASH_LEN;
543 			break;
544 		case ' ':
545 			on = 0;
546 			duration = dot_clock * WORD_SPACE;
547 			break;
548 		default:
549 			on = 0;
550 			duration = 0;
551 		}
552 		if (on) {
553 			ioctl(line, TIOCMGET, &lflags);
554 			lflags |= TIOCM_RTS;
555 			ioctl(line, TIOCMSET, &lflags);
556 		}
557 		duration *= 1000000;
558 		if (duration)
559 			usleep(duration);
560 		ioctl(line, TIOCMGET, &lflags);
561 		lflags &= ~TIOCM_RTS;
562 		ioctl(line, TIOCMSET, &lflags);
563 		duration = dot_clock * 1000000;
564 		usleep(duration);
565 	}
566 	if (!prosign) {
567 		duration = dot_clock * (CHAR_SPACE - 1) * 1000000;
568 		usleep(duration);
569 	}
570 }
571 
572 void
573 sighandler(int signo)
574 {
575 
576 	ioctl(line, TIOCMSET, &olflags);
577 	tcsetattr(line, TCSANOW, &otty);
578 
579 	signal(signo, SIG_DFL);
580 	(void)kill(getpid(), signo);
581 }
582