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 * $OpenBSD: morse.c,v 1.22 2016/03/07 12:07:56 mestre Exp $ 33 */ 34 35 /* 36 * Taught to send *real* morse by Lyndon Nerenberg (VE7TCP/VE6BBM) 37 * <lyndon@orthanc.ca> 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 static const char *digit[] = { 57 "-----", 58 ".----", 59 "..---", 60 "...--", 61 "....-", 62 ".....", 63 "-....", 64 "--...", 65 "---..", 66 "----.", 67 }; 68 69 static const char *alph[] = { 70 ".-", 71 "-...", 72 "-.-.", 73 "-..", 74 ".", 75 "..-.", 76 "--.", 77 "....", 78 "..", 79 ".---", 80 "-.-", 81 ".-..", 82 "--", 83 "-.", 84 "---", 85 ".--.", 86 "--.-", 87 ".-.", 88 "...", 89 "-", 90 "..-", 91 "...-", 92 ".--", 93 "-..-", 94 "-.--", 95 "--..", 96 }; 97 98 struct punc { 99 char c; 100 const char *morse; 101 }; 102 103 static const struct punc other[] = { 104 { 'e', "..-.." }, /* accented e - only decodes */ 105 { ',', "--..--" }, 106 { '.', ".-.-.-" }, 107 { '?', "..--.." }, 108 { '/', "-..-." }, 109 { '-', "-....-" }, 110 { ':', "---..." }, 111 { ';', "-.-.-." }, 112 { '(', "-.--." }, /* KN */ 113 { ')', "-.--.-" }, 114 { '"', ".-..-." }, 115 { '`', ".-..-." }, 116 { '\'', ".----." }, 117 { '+', ".-.-." }, /* AR \n\n\n */ 118 { '=', "-...-" }, /* BT \n\n */ 119 { '@', ".--.-." }, 120 { '\n', ".-.-" }, /* AA (will only decode) */ 121 { '\0', NULL } 122 }; 123 124 struct prosign { 125 const char *c; 126 const char *morse; 127 }; 128 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>", "...---..." }, 139 { NULL, NULL } 140 }; 141 142 struct morsetab { 143 char inchar; 144 const char *morse; 145 }; 146 147 static const struct morsetab mtab[] = { 148 149 /* letters */ 150 151 {'a', ".-"}, 152 {'b', "-..."}, 153 {'c', "-.-."}, 154 {'d', "-.."}, 155 {'e', "."}, 156 {'f', "..-."}, 157 {'g', "--."}, 158 {'h', "...."}, 159 {'i', ".."}, 160 {'j', ".---"}, 161 {'k', "-.-"}, 162 {'l', ".-.."}, 163 {'m', "--"}, 164 {'n', "-."}, 165 {'o', "---"}, 166 {'p', ".--."}, 167 {'q', "--.-"}, 168 {'r', ".-."}, 169 {'s', "..."}, 170 {'t', "-"}, 171 {'u', "..-"}, 172 {'v', "...-"}, 173 {'w', ".--"}, 174 {'x', "-..-"}, 175 {'y', "-.--"}, 176 {'z', "--.."}, 177 178 /* digits */ 179 180 {'0', "-----"}, 181 {'1', ".----"}, 182 {'2', "..---"}, 183 {'3', "...--"}, 184 {'4', "....-"}, 185 {'5', "....."}, 186 {'6', "-...."}, 187 {'7', "--..."}, 188 {'8', "---.."}, 189 {'9', "----."}, 190 191 /* punctuation */ 192 193 {',', "--..--"}, 194 {'.', ".-.-.-"}, 195 {'?', "..--.."}, 196 {'!', "-.-.--"}, /* KW */ 197 {'/', "-..-."}, 198 {'-', "-....-"}, 199 {'_', "..--.."}, 200 {'=', "-...-"}, /* BT */ 201 {':', "---..."}, 202 {';', "-.-.-."}, 203 {'(', "-.--."}, /* KN */ 204 {')', "-.--.-"}, 205 {'$', "...-..-"}, 206 {'+', ".-.-."}, /* AR */ 207 {'\'', ".----."}, 208 {'"', ".-..-."}, 209 {'@', ".--.-."}, /* AC */ 210 211 {'\0', ""} 212 }; 213 214 215 static const struct morsetab iso8859tab[] = { 216 {'�', ".--.-"}, 217 {'�', ".--.-"}, 218 {'�', ".--.-"}, 219 {'�', ".-.-"}, 220 {'�', "-.-.."}, 221 {'�', "..-.."}, 222 {'�', "..-.."}, 223 {'�', "-..-."}, 224 {'�', "---."}, 225 {'�', "..--"}, 226 227 {'\0', ""} 228 }; 229 230 static const struct morsetab koi8rtab[] = { 231 /* 232 * the cyrillic alphabet; you'll need a KOI8R font in order 233 * to see the actual characters 234 */ 235 {'�', ".-"}, /* a */ 236 {'�', "-..."}, /* be */ 237 {'�', ".--"}, /* ve */ 238 {'�', "--."}, /* ge */ 239 {'�', "-.."}, /* de */ 240 {'�', "."}, /* ye */ 241 {'�', "."}, /* yo, the same as ye */ 242 {'�', "...-"}, /* she */ 243 {'�', "--.."}, /* ze */ 244 {'�', ".."}, /* i */ 245 {'�', ".---"}, /* i kratkoye */ 246 {'�', "-.-"}, /* ka */ 247 {'�', ".-.."}, /* el */ 248 {'�', "--"}, /* em */ 249 {'�', "-."}, /* en */ 250 {'�', "---"}, /* o */ 251 {'�', ".--."}, /* pe */ 252 {'�', ".-."}, /* er */ 253 {'�', "..."}, /* es */ 254 {'�', "-"}, /* te */ 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 */ 267 268 {'\0', ""} 269 }; 270 271 struct tone_data { 272 int16_t *data; 273 size_t len; 274 }; 275 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); 283 284 #define GETOPTOPTS "d:ef:lopP:rsw:W:" 285 #define USAGE \ 286 "usage: morse [-r] [-els] [-p | -o] [-P device] [-d device] [-w speed] [-W speed] [-f frequency] [string ...]\n" 287 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 */ 294 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; 298 299 #define DASH_LEN 3 300 #define CHAR_SPACE 3 301 #define WORD_SPACE (7 - CHAR_SPACE) 302 static float dot_clock, word_clock; 303 int spkr, line; 304 struct termios otty, ntty; 305 int olflags; 306 307 static const struct morsetab *hightab; 308 309 int 310 main(int argc, char *argv[]) 311 { 312 int ch, lflags; 313 int prosign; 314 char *p, *codeset; 315 316 while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1) 317 switch ((char) ch) { 318 case 'd': 319 device = optarg; 320 break; 321 case 'e': 322 eflag = 1; 323 setvbuf(stdout, 0, _IONBF, 0); 324 break; 325 case 'f': 326 freq = atoi(optarg); 327 break; 328 case 'l': 329 lflag = 1; 330 break; 331 case 'o': 332 oflag = 1; 333 /* FALLTHROUGH */ 334 case 'p': 335 pflag = 1; 336 break; 337 case 'P': 338 snddev = optarg; 339 break; 340 case 'r': 341 rflag = 1; 342 break; 343 case 's': 344 sflag = 1; 345 break; 346 case 'w': 347 wpm = atoi(optarg); 348 break; 349 case 'W': 350 farnsworth = atoi(optarg); 351 break; 352 case '?': 353 default: 354 fputs(USAGE, stderr); 355 exit(1); 356 } 357 if (sflag && lflag) { 358 fputs("morse: only one of -l and -s allowed\n", stderr); 359 exit(1); 360 } 361 if (pflag + !!device + sflag + lflag > 1) { 362 fputs("morse: only one of -o, -p, -d and -l, -s allowed\n", stderr); 363 exit(1); 364 } 365 if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (farnsworth > 60))) { 366 fputs("morse: insane speed\n", stderr); 367 exit(1); 368 } 369 if ((pflag || device) && (freq == 0)) 370 freq = FREQUENCY; 371 if (pflag || device) { 372 /* 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 377 */ 378 dot_clock = wpm / 1.2; /* dots/sec */ 379 dot_clock = 1 / dot_clock; /* duration of a dot */ 380 381 word_clock = dot_clock; 382 383 /* 384 * This is how to get to this formula: 385 * PARIS = 22 dit (symbols) + 9 symbol spaces = 31 symbol times 386 * + 19 space times. 387 * 388 * The symbol times are in dot_clock, so the spaces have to 389 * make up to reach the farnsworth time. 390 */ 391 if (farnsworth > 0) 392 word_clock = (60.0 / farnsworth - 31 * dot_clock) / 19; 393 } 394 if (snddev == NULL) { 395 if (oflag) 396 snddev = "-"; 397 else /* only pflag */ 398 snddev = "/dev/dsp"; 399 } 400 401 if (pflag) { 402 snd_chan_param param; 403 404 if (oflag && strcmp(snddev, "-") == 0) 405 spkr = STDOUT_FILENO; 406 else 407 spkr = open(snddev, O_WRONLY, 0); 408 if (spkr == -1) 409 err(1, "%s", snddev); 410 param.play_rate = DSP_RATE; 411 param.play_format = AFMT_S16_NE; 412 param.rec_rate = 0; 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); 420 } else 421 if (device) { 422 if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) { 423 perror("open tty line"); 424 exit(1); 425 } 426 if (tcgetattr(line, &otty) == -1) { 427 perror("tcgetattr() failed"); 428 exit(1); 429 } 430 ntty = otty; 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; 438 olflags = lflags; 439 ioctl(line, TIOCMSET, &lflags); 440 signal(SIGHUP, sighandler); 441 signal(SIGINT, sighandler); 442 signal(SIGQUIT, sighandler); 443 signal(SIGTERM, sighandler); 444 } 445 446 argc -= optind; 447 argv += optind; 448 449 if (setlocale(LC_CTYPE, "") != NULL && 450 *(codeset = nl_langinfo(CODESET)) != '\0') { 451 if (strcmp(codeset, "KOI8-R") == 0) 452 hightab = koi8rtab; 453 else if (strcmp(codeset, "ISO8859-1") == 0 || 454 strcmp(codeset, "ISO8859-15") == 0) 455 hightab = iso8859tab; 456 } 457 458 if (rflag) { 459 if (*argv) { 460 do { 461 decode(*argv); 462 } while (*++argv); 463 } else { 464 char foo[10]; /* All morse chars shorter than this */ 465 int blank, i; 466 467 i = 0; 468 blank = 0; 469 while ((ch = getchar()) != EOF) { 470 if (ch == '-' || ch == '.') { 471 foo[i++] = ch; 472 if (i == 10) { 473 /* overrun means gibberish--print 'x' and 474 * advance */ 475 i = 0; 476 putchar('x'); 477 while ((ch = getchar()) != EOF && 478 (ch == '.' || ch == '-')) 479 ; 480 blank = 1; 481 } 482 } else if (i) { 483 foo[i] = '\0'; 484 decode(foo); 485 i = 0; 486 blank = 0; 487 } else if (isspace(ch)) { 488 if (blank) { 489 /* print whitespace for each double blank */ 490 putchar(' '); 491 blank = 0; 492 } else 493 blank = 1; 494 } 495 } 496 } 497 putchar('\n'); 498 exit(0); 499 } 500 501 if (lflag) 502 printf("m"); 503 if (*argv) { 504 do { 505 prosign = 0; 506 for (p = *argv; *p; ++p) { 507 if (eflag) 508 putchar(*p); 509 if (*p == '<' || *p == '>') { 510 prosign = *p == '<'; 511 continue; 512 } 513 if (strchr("> \r\n", *(p + 1)) != NULL) 514 prosign = 0; 515 morse(*p, prosign); 516 } 517 if (eflag) 518 putchar(' '); 519 morse(' ', 0); 520 } while (*++argv); 521 } else { 522 prosign = 0; 523 while ((ch = getchar()) != EOF) { 524 if (eflag) 525 putchar(ch); 526 if (ch == '<') { 527 prosign = 1; 528 continue; 529 } 530 if (prosign) { 531 int tch; 532 533 tch = getchar(); 534 if (strchr("> \r\n", tch) != NULL) 535 prosign = 0; 536 if (tch != '>') 537 ungetc(tch, stdin); 538 } 539 morse(ch, prosign); 540 } 541 } 542 if (device) 543 tcsetattr(line, TCSANOW, &otty); 544 exit(0); 545 } 546 547 static void 548 alloc_soundbuf(struct tone_data *tone, double len, int on) 549 { 550 int samples, i; 551 552 samples = DSP_RATE * len; 553 tone->len = samples * sizeof(*tone->data); 554 tone->data = malloc(tone->len); 555 if (tone->data == NULL) 556 err(1, NULL); 557 if (!on) { 558 bzero(tone->data, tone->len); 559 return; 560 } 561 562 /* 563 * We create a sinus with the specified frequency and smooth 564 * the edges to reduce key clicks. 565 */ 566 for (i = 0; i < samples; i++) { 567 double filter = 1; 568 569 #define FILTER_SAMPLES (DSP_RATE * 8 / 1000) /* 8 ms ramp time */ 570 if (i < FILTER_SAMPLES || i > samples - FILTER_SAMPLES) { 571 int fi = i; 572 573 if (i > FILTER_SAMPLES) 574 fi = samples - i; 575 #if defined(TRIANGLE_FILTER) 576 /* 577 * Triangle envelope 578 */ 579 filter = (double)fi / FILTER_SAMPLES; 580 #elif defined(GAUSS_FILTER) 581 /* 582 * Gauss envelope 583 */ 584 filter = exp(-4.0 * 585 pow((double)(FILTER_SAMPLES - fi) / 586 FILTER_SAMPLES, 2)); 587 #else 588 /* 589 * Cosine envelope 590 */ 591 filter = (1 + cos(M_PI * (FILTER_SAMPLES - fi) / FILTER_SAMPLES)) / 2; 592 #endif 593 } 594 tone->data[i] = 32767 * sin((double)i / samples * len * freq * 2 * M_PI) * 595 filter; 596 } 597 } 598 599 static void 600 morse(char c, int prosign) 601 { 602 const struct morsetab *m; 603 604 if (isalpha((unsigned char)c)) 605 c = tolower((unsigned char)c); 606 if ((c == '\r') || (c == '\n')) 607 c = ' '; 608 if (c == ' ') { 609 if (pflag) { 610 play(" ", 0); 611 return; 612 } else if (device) { 613 ttyout(" ", 0); 614 return; 615 } else if (lflag) { 616 printf("\n"); 617 } else { 618 show("", 0); 619 return; 620 } 621 } 622 for (m = ((unsigned char)c < 0x80? mtab: hightab); 623 m != NULL && m->inchar != '\0'; 624 m++) { 625 if (m->inchar == c) { 626 if (pflag) { 627 play(m->morse, prosign); 628 } else if (device) { 629 ttyout(m->morse, prosign); 630 } else 631 show(m->morse, prosign); 632 } 633 } 634 } 635 636 static void 637 decode(const char *s) 638 { 639 int i; 640 641 for (i = 0; i < 10; i++) 642 if (strcmp(digit[i], s) == 0) { 643 putchar('0' + i); 644 return; 645 } 646 647 for (i = 0; i < 26; i++) 648 if (strcmp(alph[i], s) == 0) { 649 putchar('A' + i); 650 return; 651 } 652 i = 0; 653 while (other[i].c) { 654 if (strcmp(other[i].morse, s) == 0) { 655 putchar(other[i].c); 656 return; 657 } 658 i++; 659 } 660 i = 0; 661 while (ps[i].c) { 662 /* put whitespace around prosigns */ 663 if (strcmp(ps[i].morse, s) == 0) { 664 printf(" %s ", ps[i].c); 665 return; 666 } 667 i++; 668 } 669 putchar('x'); /* line noise */ 670 } 671 672 static void 673 show(const char *s, int prosign) 674 { 675 if (lflag) { 676 printf("%s ", s); 677 return; 678 } else if (sflag) 679 printf(" %s", s); 680 else 681 for (; *s; ++s) 682 printf(" %s", *s == '.' ? "dit" : "dah"); 683 if (!prosign) 684 printf("\n"); 685 } 686 687 static void 688 play(const char *s, int prosign) 689 { 690 const char *c; 691 int duration; 692 struct tone_data *tone; 693 694 /* 695 * We don't need to usleep() here, as the sound device blocks. 696 */ 697 for (c = s; *c != '\0'; c++) { 698 switch (*c) { 699 case '.': 700 duration = 1; 701 tone = &tone_dot; 702 break; 703 case '-': 704 duration = 1; 705 tone = &tone_dash; 706 break; 707 case ' ': 708 duration = WORD_SPACE; 709 tone = &tone_letter_silence; 710 break; 711 default: 712 errx(1, "invalid morse digit"); 713 } 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); 719 } 720 if (prosign) 721 return; 722 duration = CHAR_SPACE; 723 while (duration-- > 0) 724 write(spkr, tone_letter_silence.data, tone_letter_silence.len); 725 726 /* Sync out the audio data with other output */ 727 if (!oflag) 728 ioctl(spkr, SNDCTL_DSP_SYNC, NULL); 729 } 730 731 static void 732 ttyout(const char *s, int prosign) 733 { 734 const char *c; 735 int duration, on, lflags; 736 737 for (c = s; *c != '\0'; c++) { 738 switch (*c) { 739 case '.': 740 on = 1; 741 duration = dot_clock; 742 break; 743 case '-': 744 on = 1; 745 duration = dot_clock * DASH_LEN; 746 break; 747 case ' ': 748 on = 0; 749 duration = word_clock * WORD_SPACE; 750 break; 751 default: 752 on = 0; 753 duration = 0; 754 } 755 if (on) { 756 ioctl(line, TIOCMGET, &lflags); 757 lflags |= TIOCM_RTS; 758 ioctl(line, TIOCMSET, &lflags); 759 } 760 duration *= 1000000; 761 if (duration) 762 usleep(duration); 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) 769 usleep(duration); 770 } 771 if (!prosign) { 772 duration = word_clock * CHAR_SPACE * 1000000; 773 usleep(duration); 774 } 775 } 776 777 static void 778 sighandler(int signo) 779 { 780 781 ioctl(line, TIOCMSET, &olflags); 782 tcsetattr(line, TCSANOW, &otty); 783 784 signal(signo, SIG_DFL); 785 kill(getpid(), signo); 786 } 787