morse.c revision 203479
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
34/*
35 * Taught to send *real* morse by Lyndon Nerenberg (VE6BBM)
36 * <lyndon@orthanc.ca>
37 */
38
39#ifndef lint
40static const char copyright[] =
41"@(#) Copyright (c) 1988, 1993\n\
42	The Regents of the University of California.  All rights reserved.\n";
43#endif /* not lint */
44
45#ifndef lint
46#if 0
47static char sccsid[] = "@(#)morse.c	8.1 (Berkeley) 5/31/93";
48#endif
49static const char rcsid[] =
50 "$FreeBSD: head/games/morse/morse.c 203479 2010-02-04 07:08:06Z imp $";
51#endif /* not lint */
52
53#include <sys/time.h>
54
55#include <ctype.h>
56#include <fcntl.h>
57#include <langinfo.h>
58#include <locale.h>
59#include <signal.h>
60#include <stdio.h>
61#include <stdlib.h>
62#include <string.h>
63#include <termios.h>
64#include <unistd.h>
65
66/* Always use the speaker, let the open fail if -p is selected */
67#define SPEAKER "/dev/speaker"
68
69#ifdef SPEAKER
70#include <dev/speaker/speaker.h>
71#endif
72
73struct morsetab {
74	char            inchar;
75	char           *morse;
76};
77
78static const struct morsetab mtab[] = {
79
80	/* letters */
81
82	{'a', ".-"},
83	{'b', "-..."},
84	{'c', "-.-."},
85	{'d', "-.."},
86	{'e', "."},
87	{'f', "..-."},
88	{'g', "--."},
89	{'h', "...."},
90	{'i', ".."},
91	{'j', ".---"},
92	{'k', "-.-"},
93	{'l', ".-.."},
94	{'m', "--"},
95	{'n', "-."},
96	{'o', "---"},
97	{'p', ".--."},
98	{'q', "--.-"},
99	{'r', ".-."},
100	{'s', "..."},
101	{'t', "-"},
102	{'u', "..-"},
103	{'v', "...-"},
104	{'w', ".--"},
105	{'x', "-..-"},
106	{'y', "-.--"},
107	{'z', "--.."},
108
109	/* digits */
110
111	{'0', "-----"},
112	{'1', ".----"},
113	{'2', "..---"},
114	{'3', "...--"},
115	{'4', "....-"},
116	{'5', "....."},
117	{'6', "-...."},
118	{'7', "--..."},
119	{'8', "---.."},
120	{'9', "----."},
121
122	/* punctuation */
123
124	{',', "--..--"},
125	{'.', ".-.-.-"},
126	{'"', ".-..-."},
127	{'!', "..--."},
128	{'?', "..--.."},
129	{'/', "-..-."},
130	{'-', "-....-"},
131	{'=', "-...-"},		/* BT */
132	{':', "---..."},
133	{';', "-.-.-."},
134	{'(', "-.--."},		/* KN */
135	{')', "-.--.-"},
136	{'$', "...-..-"},
137	{'+', ".-.-."},		/* AR */
138	{'@', ".--.-."},	/* AC */
139
140	/* prosigns without already assigned values */
141
142	{'#', ".-..."},		/* AS */
143	{'&', "...-.-"},	/* SK */
144	{'*', "...-."},		/* VE */
145	{'%', "-...-.-"},	/* BK */
146
147	{'\0', ""}
148};
149
150
151static const struct morsetab iso8859_1tab[] = {
152	{'�', ".--.-"},
153	{'�', ".--.-"},
154	{'�', ".--.-"},
155	{'�', ".-.-"},
156	{'�', "-.-.."},
157	{'�', "..-.."},
158	{'�', "..-.."},
159	{'�', "-..-."},
160	{'�', "---."},
161	{'�', "..--"},
162
163	{'\0', ""}
164};
165
166static const struct morsetab iso8859_7tab[] = {
167	/*
168	 * The greek alphabet; you'll need an 8859-7 font in order
169	 * to see the actual characters.
170	 * This table does not implement:
171	 * - the special sequences for the seven diphthongs,
172	 * - the punctuation differences.
173	 * Implementing these features would introduce too many
174	 * special-cases in the program's main loop.
175	 * The diphtong sequences are:
176	 * alpha iota		.-.-
177	 * alpha upsilon	..--
178	 * epsilon upsilon	---.
179	 * eta upsilon		...-
180	 * omikron iota		---..
181	 * omikron upsilon	..-
182	 * upsilon iota		.---
183	 * The different punctuation symbols are:
184	 * ;	..-.-
185	 * !	--..--
186	 */
187	{'�', ".-"},	/* alpha */
188	{'�', ".-"},	/* alpha with acute */
189	{'�', "-..."},	/* beta */
190	{'�', "--."},	/* gamma */
191	{'�', "-.."},	/* delta */
192	{'�', "."},	/* epsilon */
193	{'�', "."},	/* epsilon with acute */
194	{'�', "--.."},	/* zeta */
195	{'�', "...."},	/* eta */
196	{'�', "...."},	/* eta with acute */
197	{'�', "-.-."},	/* theta */
198	{'�', ".."},	/* iota */
199	{'�', ".."},	/* iota with acute */
200	{'�', ".."},	/* iota with diairesis */
201	{'�', ".."},	/* iota with acute and diairesis */
202	{'�', "-.-"},	/* kappa */
203	{'�', ".-.."},	/* lamda */
204	{'�', "--"},	/* mu */
205	{'�', "-."},	/* nu */
206	{'�', "-..-"},	/* xi */
207	{'�', "---"},	/* omicron */
208	{'�', "---"},	/* omicron with acute */
209	{'�', ".--."},	/* pi */
210	{'�', ".-."},	/* rho */
211	{'�', "..."},	/* sigma */
212	{'�', "..."},	/* final sigma */
213	{'�', "-"},	/* tau */
214	{'�', "-.--"},	/* upsilon */
215	{'�', "-.--"},	/* upsilon with acute */
216	{'�', "-.--"},	/* upsilon and diairesis */
217	{'�', "-.--"},	/* upsilon with acute and diairesis */
218	{'�', "..-."},	/* phi */
219	{'�', "----"},	/* chi */
220	{'�', "--.-"},	/* psi */
221	{'�', ".--"},	/* omega */
222	{'�', ".--"},	/* omega with acute */
223
224	{'\0', ""}
225};
226
227static const struct morsetab koi8rtab[] = {
228	/*
229	 * the cyrillic alphabet; you'll need a KOI8R font in order
230	 * to see the actual characters
231	 */
232	{'�', ".-"},		/* a */
233	{'�', "-..."},	/* be */
234	{'�', ".--"},	/* ve */
235	{'�', "--."},	/* ge */
236	{'�', "-.."},	/* de */
237	{'�', "."},		/* ye */
238	{'�', "."},         	/* yo, the same as ye */
239	{'�', "...-"},	/* she */
240	{'�', "--.."},	/* ze */
241	{'�', ".."},		/* i */
242	{'�', ".---"},	/* i kratkoye */
243	{'�', "-.-"},	/* ka */
244	{'�', ".-.."},	/* el */
245	{'�', "--"},		/* em */
246	{'�', "-."},		/* en */
247	{'�', "---"},	/* o */
248	{'�', ".--."},	/* pe */
249	{'�', ".-."},	/* er */
250	{'�', "..."},	/* es */
251	{'�', "-"},		/* te */
252	{'�', "..-"},	/* u */
253	{'�', "..-."},	/* ef */
254	{'�', "...."},	/* kha */
255	{'�', "-.-."},	/* ce */
256	{'�', "---."},	/* che */
257	{'�', "----"},	/* sha */
258	{'�', "--.-"},	/* shcha */
259	{'�', "-.--"},	/* yi */
260	{'�', "-..-"},	/* myakhkij znak */
261	{'�', "..-.."},	/* ae */
262	{'�', "..--"},	/* yu */
263	{'�', ".-.-"},	/* ya */
264
265	{'\0', ""}
266};
267
268void            show(const char *), play(const char *), morse(char);
269void		ttyout(const char *);
270void		sighandler(int);
271
272#define GETOPTOPTS "c:d:ef:lsw:"
273#define USAGE \
274"usage: morse [-els] [-d device] [-w speed] [-c speed] [-f frequency] [string ...]\n"
275
276static int      pflag, lflag, sflag, eflag;
277static int      wpm = 20;	/* effective words per minute */
278static int      cpm;		/* effective words per minute between
279				 * characters */
280#define FREQUENCY 600
281static int      freq = FREQUENCY;
282static char	*device;	/* for tty-controlled generator */
283
284#define DASH_LEN 3
285#define CHAR_SPACE 3
286#define WORD_SPACE (7 - CHAR_SPACE - 1)
287static float    dot_clock;
288static float    cdot_clock;
289int             spkr, line;
290struct termios	otty, ntty;
291int		olflags;
292
293#ifdef SPEAKER
294tone_t          sound;
295#undef GETOPTOPTS
296#define GETOPTOPTS "c:d:ef:lpsw:"
297#undef USAGE
298#define USAGE \
299"usage: morse [-elps] [-d device] [-w speed] [-c speed] [-f frequency] [string ...]\n"
300#endif
301
302static const struct morsetab *hightab;
303
304int
305main(int argc, char **argv)
306{
307	int    ch, lflags;
308	char  *p, *codeset;
309
310	while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1)
311		switch ((char) ch) {
312 		case 'c':
313 			cpm = atoi(optarg);
314 			break;
315		case 'd':
316			device = optarg;
317			break;
318		case 'e':
319			eflag = 1;
320			setvbuf(stdout, 0, _IONBF, 0);
321			break;
322		case 'f':
323			freq = atoi(optarg);
324			break;
325		case 'l':
326			lflag = 1;
327			break;
328#ifdef SPEAKER
329		case 'p':
330			pflag = 1;
331			break;
332#endif
333		case 's':
334			sflag = 1;
335			break;
336		case 'w':
337			wpm = atoi(optarg);
338			break;
339		case '?':
340		default:
341			fputs(USAGE, stderr);
342			exit(1);
343		}
344	if (sflag && lflag) {
345		fputs("morse: only one of -l and -s allowed\n", stderr);
346		exit(1);
347	}
348	if ((pflag || device) && (sflag || lflag)) {
349		fputs("morse: only one of -p, -d and -l, -s allowed\n", stderr);
350		exit(1);
351	}
352	if (cpm == 0)
353		cpm = wpm;
354	if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (cpm < 1) || (cpm > 60))) {
355		fputs("morse: insane speed\n", stderr);
356		exit(1);
357	}
358	if ((pflag || device) && (freq == 0))
359		freq = FREQUENCY;
360
361#ifdef SPEAKER
362	if (pflag) {
363		if ((spkr = open(SPEAKER, O_WRONLY, 0)) == -1) {
364			perror(SPEAKER);
365			exit(1);
366		}
367	} else
368#endif
369	if (device) {
370		if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) {
371			perror("open tty line");
372			exit(1);
373		}
374		if (tcgetattr(line, &otty) == -1) {
375			perror("tcgetattr() failed");
376			exit(1);
377		}
378		ntty = otty;
379		ntty.c_cflag |= CLOCAL;
380		tcsetattr(line, TCSANOW, &ntty);
381		lflags = fcntl(line, F_GETFL);
382		lflags &= ~O_NONBLOCK;
383		fcntl(line, F_SETFL, &lflags);
384		ioctl(line, TIOCMGET, &lflags);
385		lflags &= ~TIOCM_RTS;
386		olflags = lflags;
387		ioctl(line, TIOCMSET, &lflags);
388		(void)signal(SIGHUP, sighandler);
389		(void)signal(SIGINT, sighandler);
390		(void)signal(SIGQUIT, sighandler);
391		(void)signal(SIGTERM, sighandler);
392	}
393	if (pflag || device) {
394		dot_clock = wpm / 2.4;		/* dots/sec */
395		dot_clock = 1 / dot_clock;	/* duration of a dot */
396		dot_clock = dot_clock / 2;	/* dot_clock runs at twice */
397						/* the dot rate */
398		dot_clock = dot_clock * 100;	/* scale for ioctl */
399
400		cdot_clock = cpm / 2.4;		/* dots/sec */
401		cdot_clock = 1 / cdot_clock;	/* duration of a dot */
402		cdot_clock = cdot_clock / 2;	/* dot_clock runs at twice */
403						/* the dot rate */
404		cdot_clock = cdot_clock * 100;	/* scale for ioctl */
405	}
406
407	argc -= optind;
408	argv += optind;
409
410	if (setlocale(LC_CTYPE, "") != NULL &&
411	    *(codeset = nl_langinfo(CODESET)) != '\0') {
412		if (strcmp(codeset, "KOI8-R") == 0)
413			hightab = koi8rtab;
414		else if (strcmp(codeset, "ISO8859-1") == 0 ||
415			 strcmp(codeset, "ISO8859-15") == 0)
416			hightab = iso8859_1tab;
417		else if (strcmp(codeset, "ISO8859-7") == 0)
418			hightab = iso8859_7tab;
419	}
420
421	if (lflag)
422		printf("m");
423	if (*argv) {
424		do {
425			for (p = *argv; *p; ++p) {
426				if (eflag)
427					putchar(*p);
428				morse(*p);
429			}
430			if (eflag)
431				putchar(' ');
432			morse(' ');
433		} while (*++argv);
434	} else {
435		while ((ch = getchar()) != EOF) {
436			if (eflag)
437				putchar(ch);
438			morse(ch);
439		}
440	}
441	if (device)
442		tcsetattr(line, TCSANOW, &otty);
443	exit(0);
444}
445
446void
447morse(char c)
448{
449	const struct morsetab *m;
450
451	if (isalpha((unsigned char)c))
452		c = tolower((unsigned char)c);
453	if ((c == '\r') || (c == '\n'))
454		c = ' ';
455	if (c == ' ') {
456		if (pflag)
457			play(" ");
458		else if (device)
459			ttyout(" ");
460		else if (lflag)
461			printf("\n");
462		else
463			show("");
464		return;
465	}
466	for (m = ((unsigned char)c < 0x80? mtab: hightab);
467	     m != NULL && m->inchar != '\0';
468	     m++) {
469		if (m->inchar == c) {
470			if (pflag) {
471				play(m->morse);
472			} else if (device) {
473				ttyout(m->morse);
474			} else
475				show(m->morse);
476		}
477	}
478}
479
480void
481show(const char *s)
482{
483	if (lflag) {
484		printf("%s ", s);
485	} else if (sflag) {
486		printf(" %s\n", s);
487	} else {
488		for (; *s; ++s)
489			printf(" %s", *s == '.' ? *(s + 1) == '\0' ? "dit" :
490			    "di" : "dah");
491		printf("\n");
492	}
493}
494
495void
496play(const char *s)
497{
498#ifdef SPEAKER
499	const char *c;
500
501	for (c = s; *c != '\0'; c++) {
502		switch (*c) {
503		case '.':
504			sound.frequency = freq;
505			sound.duration = dot_clock;
506			break;
507		case '-':
508			sound.frequency = freq;
509			sound.duration = dot_clock * DASH_LEN;
510			break;
511		case ' ':
512			sound.frequency = 0;
513			sound.duration = cdot_clock * WORD_SPACE;
514			break;
515		default:
516			sound.duration = 0;
517		}
518		if (sound.duration) {
519			if (ioctl(spkr, SPKRTONE, &sound) == -1) {
520				perror("ioctl play");
521				exit(1);
522			}
523		}
524		sound.frequency = 0;
525		sound.duration = dot_clock;
526		if (ioctl(spkr, SPKRTONE, &sound) == -1) {
527			perror("ioctl rest");
528			exit(1);
529		}
530	}
531	sound.frequency = 0;
532	sound.duration = cdot_clock * CHAR_SPACE;
533	ioctl(spkr, SPKRTONE, &sound);
534#endif
535}
536
537void
538ttyout(const char *s)
539{
540	const char *c;
541	int duration, on, lflags;
542
543	for (c = s; *c != '\0'; c++) {
544		switch (*c) {
545		case '.':
546			on = 1;
547			duration = dot_clock;
548			break;
549		case '-':
550			on = 1;
551			duration = dot_clock * DASH_LEN;
552			break;
553		case ' ':
554			on = 0;
555			duration = cdot_clock * WORD_SPACE;
556			break;
557		default:
558			on = 0;
559			duration = 0;
560		}
561		if (on) {
562			ioctl(line, TIOCMGET, &lflags);
563			lflags |= TIOCM_RTS;
564			ioctl(line, TIOCMSET, &lflags);
565		}
566		duration *= 10000;
567		if (duration)
568			usleep(duration);
569		ioctl(line, TIOCMGET, &lflags);
570		lflags &= ~TIOCM_RTS;
571		ioctl(line, TIOCMSET, &lflags);
572		duration = dot_clock * 10000;
573		usleep(duration);
574	}
575	duration = cdot_clock * CHAR_SPACE * 10000;
576	usleep(duration);
577}
578
579void
580sighandler(int signo)
581{
582
583	ioctl(line, TIOCMSET, &olflags);
584	tcsetattr(line, TCSANOW, &otty);
585
586	signal(signo, SIG_DFL);
587	(void)kill(getpid(), signo);
588}
589