2 * Copyright (c) 1988, 1993
3 * The Regents of the University of California. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
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 * $OpenBSD: morse.c,v 1.22 2016/03/07 12:07:56 mestre Exp $
36 * Taught to send *real* morse by Lyndon Nerenberg (VE7TCP/VE6BBM)
41 #include <sys/soundcard.h>
56 static const char *digit
[] = {
69 static const char *alph
[] = {
103 static const struct punc other
[] = {
104 { 'e', "..-.." }, /* accented e - only decodes */
112 { '(', "-.--." }, /* KN */
117 { '+', ".-.-." }, /* AR \n\n\n */
118 { '=', "-...-" }, /* BT \n\n */
120 { '\n', ".-.-" }, /* AA (will only decode) */
129 static const struct prosign ps
[] = {
130 { "<AS>", ".-..." }, /* wait */
131 { "<CL>", "-.-..-.." },
132 { "<CT>", "-.-.-" }, /* start */
133 { "<EE5>", "......" }, /* error */
134 { "<EE5>", "......." },
135 { "<EE5>", "........" },
136 { "<SK>", "...-.-" },
137 { "<SN>", "...-." }, /* understood */
138 { "<SOS>", "...---..." },
147 static const struct morsetab mtab
[] = {
196 {'!', "-.-.--"}, /* KW */
200 {'=', "-...-"}, /* BT */
203 {'(', "-.--."}, /* KN */
206 {'+', ".-.-."}, /* AR */
209 {'@', ".--.-."}, /* AC */
215 static const struct morsetab iso8859tab
[] = {
230 static const struct morsetab koi8rtab
[] = {
232 * the cyrillic alphabet; you'll need a KOI8R font in order
233 * to see the actual characters
236 {'Â', "-..."}, /* be */
237 {'×', ".--"}, /* ve */
238 {'Ç', "--."}, /* ge */
239 {'Ä', "-.."}, /* de */
241 {'£', "."}, /* yo, the same as ye */
242 {'Ö', "...-"}, /* she */
243 {'Ú', "--.."}, /* ze */
245 {'Ê', ".---"}, /* i kratkoye */
246 {'Ë', "-.-"}, /* ka */
247 {'Ì', ".-.."}, /* el */
248 {'Í', "--"}, /* em */
249 {'Î', "-."}, /* en */
250 {'Ï', "---"}, /* o */
251 {'Ð', ".--."}, /* pe */
252 {'Ò', ".-."}, /* er */
253 {'Ó', "..."}, /* es */
255 {'Õ', "..-"}, /* u */
256 {'Æ', "..-."}, /* ef */
257 {'È', "...."}, /* kha */
258 {'Ã', "-.-."}, /* ce */
259 {'Þ', "---."}, /* che */
260 {'Û', "----"}, /* sha */
261 {'Ý', "--.-"}, /* shcha */
262 {'Ù', "-.--"}, /* yi */
263 {'Ø', "-..-"}, /* myakhkij znak */
264 {'Ü', "..-.."}, /* ae */
265 {'À', "..--"}, /* yu */
266 {'Ñ', ".-.-"}, /* ya */
276 static void alloc_soundbuf(struct tone_data
*, double, int);
277 static void morse(char, int);
278 static void decode(const char *);
279 static void show(const char *, int);
280 static void play(const char *, int);
281 static void ttyout(const char *, int);
282 static void sighandler(int);
284 #define GETOPTOPTS "d:ef:lopP:rsw:W:"
286 "usage: morse [-r] [-els] [-p | -o] [-P device] [-d device] [-w speed] [-W speed] [-f frequency] [string ...]\n"
288 static int lflag
, oflag
, pflag
, rflag
, sflag
, eflag
;
289 static int wpm
= 20; /* words per minute */
290 static int farnsworth
= -1;
291 #define FREQUENCY 600
292 static int freq
= FREQUENCY
;
293 static char *device
; /* for tty-controlled generator */
295 static struct tone_data tone_dot
, tone_dash
, tone_silence
, tone_letter_silence
;
296 #define DSP_RATE 44100
297 static const char *snddev
= NULL
;
301 #define WORD_SPACE (7 - CHAR_SPACE)
302 static float dot_clock
, word_clock
;
304 struct termios otty
, ntty
;
307 static const struct morsetab
*hightab
;
310 main(int argc
, char *argv
[])
316 while ((ch
= getopt(argc
, argv
, GETOPTOPTS
)) != -1)
323 setvbuf(stdout
, 0, _IONBF
, 0);
350 farnsworth
= atoi(optarg
);
354 fputs(USAGE
, stderr
);
357 if (sflag
&& lflag
) {
358 fputs("morse: only one of -l and -s allowed\n", stderr
);
361 if (pflag
+ !!device
+ sflag
+ lflag
> 1) {
362 fputs("morse: only one of -o, -p, -d and -l, -s allowed\n", stderr
);
365 if ((pflag
|| device
) && ((wpm
< 1) || (wpm
> 60) || (farnsworth
> 60))) {
366 fputs("morse: insane speed\n", stderr
);
369 if ((pflag
|| device
) && (freq
== 0))
371 if (pflag
|| device
) {
373 * A note on how to get to this magic 1.2:
374 * x WPM = 50*x dits per minute (norm word "PARIS").
375 * dits per sec = dits per minute / 60, thus
376 * dits per sec = 50 * x / 60 = x / (60 / 50) = x / 1.2
378 dot_clock
= wpm
/ 1.2; /* dots/sec */
379 dot_clock
= 1 / dot_clock
; /* duration of a dot */
381 word_clock
= dot_clock
;
384 * This is how to get to this formula:
385 * PARIS = 22 dit (symbols) + 9 symbol spaces = 31 symbol times
388 * The symbol times are in dot_clock, so the spaces have to
389 * make up to reach the farnsworth time.
392 word_clock
= (60.0 / farnsworth
- 31 * dot_clock
) / 19;
394 if (snddev
== NULL
) {
397 else /* only pflag */
402 snd_chan_param param
;
404 if (oflag
&& strcmp(snddev
, "-") == 0)
405 spkr
= STDOUT_FILENO
;
407 spkr
= open(snddev
, O_WRONLY
, 0);
409 err(1, "%s", snddev
);
410 param
.play_rate
= DSP_RATE
;
411 param
.play_format
= AFMT_S16_NE
;
413 param
.rec_format
= 0;
414 if (!oflag
&& ioctl(spkr
, AIOSFMT
, ¶m
) != 0)
415 err(1, "%s: set format", snddev
);
416 alloc_soundbuf(&tone_dot
, dot_clock
, 1);
417 alloc_soundbuf(&tone_dash
, DASH_LEN
* dot_clock
, 1);
418 alloc_soundbuf(&tone_silence
, dot_clock
, 0);
419 alloc_soundbuf(&tone_letter_silence
, word_clock
, 0);
422 if ((line
= open(device
, O_WRONLY
| O_NONBLOCK
)) == -1) {
423 perror("open tty line");
426 if (tcgetattr(line
, &otty
) == -1) {
427 perror("tcgetattr() failed");
431 ntty
.c_cflag
|= CLOCAL
;
432 tcsetattr(line
, TCSANOW
, &ntty
);
433 lflags
= fcntl(line
, F_GETFL
);
434 lflags
&= ~O_NONBLOCK
;
435 fcntl(line
, F_SETFL
, &lflags
);
436 ioctl(line
, TIOCMGET
, &lflags
);
437 lflags
&= ~TIOCM_RTS
;
439 ioctl(line
, TIOCMSET
, &lflags
);
440 signal(SIGHUP
, sighandler
);
441 signal(SIGINT
, sighandler
);
442 signal(SIGQUIT
, sighandler
);
443 signal(SIGTERM
, sighandler
);
449 if (setlocale(LC_CTYPE
, "") != NULL
&&
450 *(codeset
= nl_langinfo(CODESET
)) != '\0') {
451 if (strcmp(codeset
, "KOI8-R") == 0)
453 else if (strcmp(codeset
, "ISO8859-1") == 0 ||
454 strcmp(codeset
, "ISO8859-15") == 0)
455 hightab
= iso8859tab
;
464 char foo
[10]; /* All morse chars shorter than this */
469 while ((ch
= getchar()) != EOF
) {
470 if (ch
== '-' || ch
== '.') {
473 /* overrun means gibberish--print 'x' and
477 while ((ch
= getchar()) != EOF
&&
478 (ch
== '.' || ch
== '-'))
487 } else if (isspace(ch
)) {
489 /* print whitespace for each double blank */
506 for (p
= *argv
; *p
; ++p
) {
509 if (*p
== '<' || *p
== '>') {
513 if (strchr("> \r\n", *(p
+ 1)) != NULL
)
523 while ((ch
= getchar()) != EOF
) {
534 if (strchr("> \r\n", tch
) != NULL
)
543 tcsetattr(line
, TCSANOW
, &otty
);
548 alloc_soundbuf(struct tone_data
*tone
, double len
, int on
)
552 samples
= DSP_RATE
* len
;
553 tone
->len
= samples
* sizeof(*tone
->data
);
554 tone
->data
= malloc(tone
->len
);
555 if (tone
->data
== NULL
)
558 bzero(tone
->data
, tone
->len
);
563 * We create a sinus with the specified frequency and smooth
564 * the edges to reduce key clicks.
566 for (i
= 0; i
< samples
; i
++) {
569 #define FILTER_SAMPLES (DSP_RATE * 8 / 1000) /* 8 ms ramp time */
570 if (i
< FILTER_SAMPLES
|| i
> samples
- FILTER_SAMPLES
) {
573 if (i
> FILTER_SAMPLES
)
575 #if defined(TRIANGLE_FILTER)
579 filter
= (double)fi
/ FILTER_SAMPLES
;
580 #elif defined(GAUSS_FILTER)
585 pow((double)(FILTER_SAMPLES
- fi
) /
591 filter
= (1 + cos(M_PI
* (FILTER_SAMPLES
- fi
) / FILTER_SAMPLES
)) / 2;
594 tone
->data
[i
] = 32767 * sin((double)i
/ samples
* len
* freq
* 2 * M_PI
) *
600 morse(char c
, int prosign
)
602 const struct morsetab
*m
;
604 if (isalpha((unsigned char)c
))
605 c
= tolower((unsigned char)c
);
606 if ((c
== '\r') || (c
== '\n'))
622 for (m
= ((unsigned char)c
< 0x80? mtab
: hightab
);
623 m
!= NULL
&& m
->inchar
!= '\0';
625 if (m
->inchar
== c
) {
627 play(m
->morse
, prosign
);
629 ttyout(m
->morse
, prosign
);
631 show(m
->morse
, prosign
);
637 decode(const char *s
)
641 for (i
= 0; i
< 10; i
++)
642 if (strcmp(digit
[i
], s
) == 0) {
647 for (i
= 0; i
< 26; i
++)
648 if (strcmp(alph
[i
], s
) == 0) {
654 if (strcmp(other
[i
].morse
, s
) == 0) {
662 /* put whitespace around prosigns */
663 if (strcmp(ps
[i
].morse
, s
) == 0) {
664 printf(" %s ", ps
[i
].c
);
669 putchar('x'); /* line noise */
673 show(const char *s
, int prosign
)
682 printf(" %s", *s
== '.' ? "dit" : "dah");
688 play(const char *s
, int prosign
)
692 struct tone_data
*tone
;
695 * We don't need to usleep() here, as the sound device blocks.
697 for (c
= s
; *c
!= '\0'; c
++) {
708 duration
= WORD_SPACE
;
709 tone
= &tone_letter_silence
;
712 errx(1, "invalid morse digit");
714 while (duration
-- > 0)
715 write(spkr
, tone
->data
, tone
->len
);
716 /* Only space within a symbol */
717 if (c
[1] != '\0' || prosign
)
718 write(spkr
, tone_silence
.data
, tone_silence
.len
);
722 duration
= CHAR_SPACE
;
723 while (duration
-- > 0)
724 write(spkr
, tone_letter_silence
.data
, tone_letter_silence
.len
);
726 /* Sync out the audio data with other output */
728 ioctl(spkr
, SNDCTL_DSP_SYNC
, NULL
);
732 ttyout(const char *s
, int prosign
)
735 int duration
, on
, lflags
;
737 for (c
= s
; *c
!= '\0'; c
++) {
741 duration
= dot_clock
;
745 duration
= dot_clock
* DASH_LEN
;
749 duration
= word_clock
* WORD_SPACE
;
756 ioctl(line
, TIOCMGET
, &lflags
);
758 ioctl(line
, TIOCMSET
, &lflags
);
763 ioctl(line
, TIOCMGET
, &lflags
);
764 lflags
&= ~TIOCM_RTS
;
765 ioctl(line
, TIOCMSET
, &lflags
);
766 duration
= dot_clock
* 1000000;
767 /* Only space within a symbol */
768 if (c
[1] != '\0' || prosign
)
772 duration
= word_clock
* CHAR_SPACE
* 1000000;
778 sighandler(int signo
)
781 ioctl(line
, TIOCMSET
, &olflags
);
782 tcsetattr(line
, TCSANOW
, &otty
);
784 signal(signo
, SIG_DFL
);
785 kill(getpid(), signo
);