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