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