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 */ 33 34 /* 35 * Taught to send *real* morse by Lyndon Nerenberg (VE7TCP/VE6BBM) 36 * <lyndon@orthanc.com> 37 */ 38 39 #include <sys/time.h> 40 #include <sys/soundcard.h> 41 42 #include <ctype.h> 43 #include <err.h> 44 #include <fcntl.h> 45 #include <langinfo.h> 46 #include <locale.h> 47 #include <math.h> 48 #include <signal.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <termios.h> 53 #include <unistd.h> 54 55 struct morsetab { 56 char inchar; 57 const char *morse; 58 }; 59 60 static const struct morsetab mtab[] = { 61 62 /* letters */ 63 64 {'a', ".-"}, 65 {'b', "-..."}, 66 {'c', "-.-."}, 67 {'d', "-.."}, 68 {'e', "."}, 69 {'f', "..-."}, 70 {'g', "--."}, 71 {'h', "...."}, 72 {'i', ".."}, 73 {'j', ".---"}, 74 {'k', "-.-"}, 75 {'l', ".-.."}, 76 {'m', "--"}, 77 {'n', "-."}, 78 {'o', "---"}, 79 {'p', ".--."}, 80 {'q', "--.-"}, 81 {'r', ".-."}, 82 {'s', "..."}, 83 {'t', "-"}, 84 {'u', "..-"}, 85 {'v', "...-"}, 86 {'w', ".--"}, 87 {'x', "-..-"}, 88 {'y', "-.--"}, 89 {'z', "--.."}, 90 91 /* digits */ 92 93 {'0', "-----"}, 94 {'1', ".----"}, 95 {'2', "..---"}, 96 {'3', "...--"}, 97 {'4', "....-"}, 98 {'5', "....."}, 99 {'6', "-...."}, 100 {'7', "--..."}, 101 {'8', "---.."}, 102 {'9', "----."}, 103 104 /* punctuation */ 105 106 {',', "--..--"}, 107 {'.', ".-.-.-"}, 108 {'?', "..--.."}, 109 {'!', "-.-.--"}, /* KW */ 110 {'/', "-..-."}, 111 {'-', "-....-"}, 112 {'_', "..--.."}, 113 {'=', "-...-"}, /* BT */ 114 {':', "---..."}, 115 {';', "-.-.-."}, 116 {'(', "-.--."}, /* KN */ 117 {')', "-.--.-"}, 118 {'$', "...-..-"}, 119 {'+', ".-.-."}, /* AR */ 120 {'\'', ".----."}, 121 {'"', ".-..-."}, 122 {'@', ".--.-."}, /* AC */ 123 124 {'\0', ""} 125 }; 126 127 128 static const struct morsetab iso8859tab[] = { 129 {'�', ".--.-"}, 130 {'�', ".--.-"}, 131 {'�', ".--.-"}, 132 {'�', ".-.-"}, 133 {'�', "-.-.."}, 134 {'�', "..-.."}, 135 {'�', "..-.."}, 136 {'�', "-..-."}, 137 {'�', "---."}, 138 {'�', "..--"}, 139 140 {'\0', ""} 141 }; 142 143 static const struct morsetab koi8rtab[] = { 144 /* 145 * the cyrillic alphabet; you'll need a KOI8R font in order 146 * to see the actual characters 147 */ 148 {'�', ".-"}, /* a */ 149 {'�', "-..."}, /* be */ 150 {'�', ".--"}, /* ve */ 151 {'�', "--."}, /* ge */ 152 {'�', "-.."}, /* de */ 153 {'�', "."}, /* ye */ 154 {'�', "."}, /* yo, the same as ye */ 155 {'�', "...-"}, /* she */ 156 {'�', "--.."}, /* ze */ 157 {'�', ".."}, /* i */ 158 {'�', ".---"}, /* i kratkoye */ 159 {'�', "-.-"}, /* ka */ 160 {'�', ".-.."}, /* el */ 161 {'�', "--"}, /* em */ 162 {'�', "-."}, /* en */ 163 {'�', "---"}, /* o */ 164 {'�', ".--."}, /* pe */ 165 {'�', ".-."}, /* er */ 166 {'�', "..."}, /* es */ 167 {'�', "-"}, /* te */ 168 {'�', "..-"}, /* u */ 169 {'�', "..-."}, /* ef */ 170 {'�', "...."}, /* kha */ 171 {'�', "-.-."}, /* ce */ 172 {'�', "---."}, /* che */ 173 {'�', "----"}, /* sha */ 174 {'�', "--.-"}, /* shcha */ 175 {'�', "-.--"}, /* yi */ 176 {'�', "-..-"}, /* myakhkij znak */ 177 {'�', "..-.."}, /* ae */ 178 {'�', "..--"}, /* yu */ 179 {'�', ".-.-"}, /* ya */ 180 181 {'\0', ""} 182 }; 183 184 struct tone_data { 185 int16_t *data; 186 size_t len; 187 }; 188 189 void alloc_soundbuf(struct tone_data *, double, int); 190 void morse(char, int); 191 void show(const char *, int); 192 void play(const char *, int); 193 void ttyout(const char *, int); 194 void sighandler(int); 195 196 #define GETOPTOPTS "d:ef:lopP:sw:W:" 197 #define USAGE \ 198 "usage: morse [-els] [-p | -o] [-P device] [-d device] [-w speed] [-W speed] [-f frequency] [string ...]\n" 199 200 static int lflag, oflag, pflag, sflag, eflag; 201 static int wpm = 20; /* words per minute */ 202 static int farnsworth = -1; 203 #define FREQUENCY 600 204 static int freq = FREQUENCY; 205 static char *device; /* for tty-controlled generator */ 206 207 static struct tone_data tone_dot, tone_dash, tone_silence, tone_letter_silence; 208 #define DSP_RATE 44100 209 static const char *snddev = NULL; 210 211 #define DASH_LEN 3 212 #define CHAR_SPACE 3 213 #define WORD_SPACE (7 - CHAR_SPACE) 214 static float dot_clock, word_clock; 215 int spkr, line; 216 struct termios otty, ntty; 217 int olflags; 218 219 static const struct morsetab *hightab; 220 221 int 222 main(int argc, char **argv) 223 { 224 int ch, lflags; 225 int prosign; 226 char *p, *codeset; 227 228 while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1) 229 switch ((char) ch) { 230 case 'd': 231 device = optarg; 232 break; 233 case 'e': 234 eflag = 1; 235 setvbuf(stdout, 0, _IONBF, 0); 236 break; 237 case 'f': 238 freq = atoi(optarg); 239 break; 240 case 'l': 241 lflag = 1; 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 'W': 259 farnsworth = atoi(optarg); 260 break; 261 case '?': 262 default: 263 fputs(USAGE, stderr); 264 exit(1); 265 } 266 if (sflag && lflag) { 267 fputs("morse: only one of -l and -s allowed\n", stderr); 268 exit(1); 269 } 270 if (pflag + !!device + sflag + lflag > 1) { 271 fputs("morse: only one of -o, -p, -d and -l, -s allowed\n", stderr); 272 exit(1); 273 } 274 if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (farnsworth > 60))) { 275 fputs("morse: insane speed\n", stderr); 276 exit(1); 277 } 278 if ((pflag || device) && (freq == 0)) 279 freq = FREQUENCY; 280 if (pflag || device) { 281 /* 282 * A note on how to get to this magic 1.2: 283 * x WPM = 50*x dits per minute (norm word "PARIS"). 284 * dits per sec = dits per minute / 60, thus 285 * dits per sec = 50 * x / 60 = x / (60 / 50) = x / 1.2 286 */ 287 dot_clock = wpm / 1.2; /* dots/sec */ 288 dot_clock = 1 / dot_clock; /* duration of a dot */ 289 290 word_clock = dot_clock; 291 292 /* 293 * This is how to get to this formula: 294 * PARIS = 22 dit (symbols) + 9 symbol spaces = 31 symbol times 295 * + 19 space times. 296 * 297 * The symbol times are in dot_clock, so the spaces have to 298 * make up to reach the farnsworth time. 299 */ 300 if (farnsworth > 0) 301 word_clock = (60.0 / farnsworth - 31 * dot_clock) / 19; 302 } 303 if (snddev == NULL) { 304 if (oflag) 305 snddev = "-"; 306 else /* only pflag */ 307 snddev = "/dev/dsp"; 308 } 309 310 if (pflag) { 311 snd_chan_param param; 312 313 if (oflag && strcmp(snddev, "-") == 0) 314 spkr = STDOUT_FILENO; 315 else 316 spkr = open(snddev, O_WRONLY, 0); 317 if (spkr == -1) 318 err(1, "%s", snddev); 319 param.play_rate = DSP_RATE; 320 param.play_format = AFMT_S16_NE; 321 param.rec_rate = 0; 322 param.rec_format = 0; 323 if (!oflag && ioctl(spkr, AIOSFMT, ¶m) != 0) 324 err(1, "%s: set format", snddev); 325 alloc_soundbuf(&tone_dot, dot_clock, 1); 326 alloc_soundbuf(&tone_dash, DASH_LEN * dot_clock, 1); 327 alloc_soundbuf(&tone_silence, dot_clock, 0); 328 alloc_soundbuf(&tone_letter_silence, word_clock, 0); 329 } else 330 if (device) { 331 if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) { 332 perror("open tty line"); 333 exit(1); 334 } 335 if (tcgetattr(line, &otty) == -1) { 336 perror("tcgetattr() failed"); 337 exit(1); 338 } 339 ntty = otty; 340 ntty.c_cflag |= CLOCAL; 341 tcsetattr(line, TCSANOW, &ntty); 342 lflags = fcntl(line, F_GETFL); 343 lflags &= ~O_NONBLOCK; 344 fcntl(line, F_SETFL, &lflags); 345 ioctl(line, TIOCMGET, &lflags); 346 lflags &= ~TIOCM_RTS; 347 olflags = lflags; 348 ioctl(line, TIOCMSET, &lflags); 349 (void)signal(SIGHUP, sighandler); 350 (void)signal(SIGINT, sighandler); 351 (void)signal(SIGQUIT, sighandler); 352 (void)signal(SIGTERM, sighandler); 353 } 354 355 argc -= optind; 356 argv += optind; 357 358 if (setlocale(LC_CTYPE, "") != NULL && 359 *(codeset = nl_langinfo(CODESET)) != '\0') { 360 if (strcmp(codeset, "KOI8-R") == 0) 361 hightab = koi8rtab; 362 else if (strcmp(codeset, "ISO8859-1") == 0 || 363 strcmp(codeset, "ISO8859-15") == 0) 364 hightab = iso8859tab; 365 } 366 367 if (lflag) 368 printf("m"); 369 if (*argv) { 370 do { 371 prosign = 0; 372 for (p = *argv; *p; ++p) { 373 if (eflag) 374 putchar(*p); 375 if (*p == '<' || *p == '>') { 376 prosign = *p == '<'; 377 continue; 378 } 379 if (strchr("> \r\n", *(p + 1)) != NULL) 380 prosign = 0; 381 morse(*p, prosign); 382 } 383 if (eflag) 384 putchar(' '); 385 morse(' ', 0); 386 } while (*++argv); 387 } else { 388 prosign = 0; 389 while ((ch = getchar()) != EOF) { 390 if (eflag) 391 putchar(ch); 392 if (ch == '<') { 393 prosign = 1; 394 continue; 395 } 396 if (prosign) { 397 int tch; 398 399 tch = getchar(); 400 if (strchr("> \r\n", tch) != NULL) 401 prosign = 0; 402 if (tch != '>') 403 ungetc(tch, stdin); 404 } 405 morse(ch, prosign); 406 } 407 } 408 if (device) 409 tcsetattr(line, TCSANOW, &otty); 410 exit(0); 411 } 412 413 void 414 alloc_soundbuf(struct tone_data *tone, double len, int on) 415 { 416 int samples, i; 417 418 samples = DSP_RATE * len; 419 tone->len = samples * sizeof(*tone->data); 420 tone->data = malloc(tone->len); 421 if (tone->data == NULL) 422 err(1, NULL); 423 if (!on) { 424 bzero(tone->data, tone->len); 425 return; 426 } 427 428 /* 429 * We create a sinus with the specified frequency and smooth 430 * the edges to reduce key clicks. 431 */ 432 for (i = 0; i < samples; i++) { 433 double filter = 1; 434 435 #define FILTER_SAMPLES (DSP_RATE * 8 / 1000) /* 8 ms ramp time */ 436 if (i < FILTER_SAMPLES || i > samples - FILTER_SAMPLES) { 437 int fi = i; 438 439 if (i > FILTER_SAMPLES) 440 fi = samples - i; 441 #if defined(TRIANGLE_FILTER) 442 /* 443 * Triangle envelope 444 */ 445 filter = (double)fi / FILTER_SAMPLES; 446 #elif defined(GAUSS_FILTER) 447 /* 448 * Gauss envelope 449 */ 450 filter = exp(-4.0 * 451 pow((double)(FILTER_SAMPLES - fi) / 452 FILTER_SAMPLES, 2)); 453 #else 454 /* 455 * Cosine envelope 456 */ 457 filter = (1 + cos(M_PI * (FILTER_SAMPLES - fi) / FILTER_SAMPLES)) / 2; 458 #endif 459 } 460 tone->data[i] = 32767 * sin((double)i / samples * len * freq * 2 * M_PI) * 461 filter; 462 } 463 } 464 465 void 466 morse(char c, int prosign) 467 { 468 const struct morsetab *m; 469 470 if (isalpha((unsigned char)c)) 471 c = tolower((unsigned char)c); 472 if ((c == '\r') || (c == '\n')) 473 c = ' '; 474 if (c == ' ') { 475 if (pflag) { 476 play(" ", 0); 477 return; 478 } else if (device) { 479 ttyout(" ", 0); 480 return; 481 } else if (lflag) { 482 printf("\n"); 483 } else { 484 show("", 0); 485 return; 486 } 487 } 488 for (m = ((unsigned char)c < 0x80? mtab: hightab); 489 m != NULL && m->inchar != '\0'; 490 m++) { 491 if (m->inchar == c) { 492 if (pflag) { 493 play(m->morse, prosign); 494 } else if (device) { 495 ttyout(m->morse, prosign); 496 } else 497 show(m->morse, prosign); 498 } 499 } 500 } 501 502 void 503 show(const char *s, int prosign) 504 { 505 if (lflag) { 506 printf("%s ", s); 507 return; 508 } else if (sflag) 509 printf(" %s", s); 510 else 511 for (; *s; ++s) 512 printf(" %s", *s == '.' ? "dit" : "dah"); 513 if (!prosign) 514 printf("\n"); 515 } 516 517 void 518 play(const char *s, int prosign) 519 { 520 const char *c; 521 int duration; 522 struct tone_data *tone; 523 524 /* 525 * We don't need to usleep() here, as the sound device blocks. 526 */ 527 for (c = s; *c != '\0'; c++) { 528 switch (*c) { 529 case '.': 530 duration = 1; 531 tone = &tone_dot; 532 break; 533 case '-': 534 duration = 1; 535 tone = &tone_dash; 536 break; 537 case ' ': 538 duration = WORD_SPACE; 539 tone = &tone_letter_silence; 540 break; 541 default: 542 errx(1, "invalid morse digit"); 543 } 544 while (duration-- > 0) 545 write(spkr, tone->data, tone->len); 546 /* Only space within a symbol */ 547 if (c[1] != '\0' || prosign) 548 write(spkr, tone_silence.data, tone_silence.len); 549 } 550 if (prosign) 551 return; 552 duration = CHAR_SPACE; 553 while (duration-- > 0) 554 write(spkr, tone_letter_silence.data, tone_letter_silence.len); 555 556 /* Sync out the audio data with other output */ 557 if (!oflag) 558 ioctl(spkr, SNDCTL_DSP_SYNC, NULL); 559 } 560 561 void 562 ttyout(const char *s, int prosign) 563 { 564 const char *c; 565 int duration, on, lflags; 566 567 for (c = s; *c != '\0'; c++) { 568 switch (*c) { 569 case '.': 570 on = 1; 571 duration = dot_clock; 572 break; 573 case '-': 574 on = 1; 575 duration = dot_clock * DASH_LEN; 576 break; 577 case ' ': 578 on = 0; 579 duration = word_clock * WORD_SPACE; 580 break; 581 default: 582 on = 0; 583 duration = 0; 584 } 585 if (on) { 586 ioctl(line, TIOCMGET, &lflags); 587 lflags |= TIOCM_RTS; 588 ioctl(line, TIOCMSET, &lflags); 589 } 590 duration *= 1000000; 591 if (duration) 592 usleep(duration); 593 ioctl(line, TIOCMGET, &lflags); 594 lflags &= ~TIOCM_RTS; 595 ioctl(line, TIOCMSET, &lflags); 596 duration = dot_clock * 1000000; 597 /* Only space within a symbol */ 598 if (c[1] != '\0' || prosign) 599 usleep(duration); 600 } 601 if (!prosign) { 602 duration = word_clock * CHAR_SPACE * 1000000; 603 usleep(duration); 604 } 605 } 606 607 void 608 sighandler(int signo) 609 { 610 611 ioctl(line, TIOCMSET, &olflags); 612 tcsetattr(line, TCSANOW, &otty); 613 614 signal(signo, SIG_DFL); 615 (void)kill(getpid(), signo); 616 } 617