Article 5020 of comp.lang.perl:
Xref: feenix.metronet.com alt.sources:1708 comp.lang.perl:5020
Path: feenix.metronet.com!spssig.spss.com!kuhub.cc.ukans.edu!wupost!howland.reston.ans.net!europa.eng.gtefsd.com!uunet!bloom-beacon.mit.edu!senator-bedfellow.mit.edu!news!jsc
Newsgroups: alt.sources,comp.lang.perl
Subject: Morse Code practice program
Message-ID: <JSC.93Aug15151424@monolith.mit.edu>
From: jsc@monolith.mit.edu (Jin S Choi)
Date: 15 Aug 1993 19:14:24 GMT
Distribution: world
Organization: Massachvsetts Institvte of Technology
NNTP-Posting-Host: monolith.mit.edu
Comments: Hyperbole mail buttons accepted, v3.07.
Lines: 330


I wrote this a while ago to practice for a ham license (which I haven't
gotten yet), and thought it might be of interest to someone out
there. Basically, it lets you enter morse at the keyboard and writes out
the letters.

It's written in perl with a short X11 program used for I/O. It needs to
be able to detect how long a key is held down, and this was the easiest
way I could think of to do it. The X program just throws up a window and
outputs a 'd' on a keypress event, and a 'u' on a keyrelease. It can be
replaced by any program which does the same thing.

If you find this interesting, let me know.

----------------------------------------------------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	morse.pl
#	keypress.c
# This archive created: Sun Aug 15 15:05:18 1993
export PATH; PATH=/bin:$PATH
if test -f 'morse.pl'
then
	echo shar: will not over-write existing file "'morse.pl'"
else
cat << \SHAR_EOF > 'morse.pl'
#!/usr/local/bin/perl
# morse code practice program
# Written by Jin S. Choi <jsc@mit.edu>.

require 'sys/syscall.ph';
require 'sys/time.ph';

$timeval = 'll';		# struct timeval
$tp = pack($timeval, 0, 0);	# pre-extend $tp to size of two longs

$SIG{'INT'} = cleanup;

if ($ENV{'DISPLAY'}) {
    system("xset -r");		# turn off autorepeat
}

close STDIN;
open(STDIN, "keypressX11 |") || die $!;
select(STDIN); $|++; select(STDOUT); $|++;

%morse = (
	  ".-", "a",
	  "-...", "b",
	  "-.-.", "c",
	  "-..", "d",
	  ".", "e",
	  "..-.", "f",
	  "--.", "g",
	  "....", "h",
	  "..", "i",
	  ".---", "j",
	  "-.-", "k",
	  ".-..", "l",
	  "--", "m",
	  "-.", "n",
	  "---", "o",
	  ".--.", "p",
	  "--.-", "q",
	  ".-.", "r",
	  "...", "s",
	  "-", "t",
	  "..-", "u",
	  "...-", "v",
	  ".--", "w",
	  "-..-", "x",
	  "-.--", "y",
	  "--..", "z",
	  ".-.-.-", ".",
	  "--..--", ",",
	  "..--..", "?",
	  "-..-.", "/",
	  ".-.-.", "+",
	  "-----", "0",
	  ".----", "1",
	  "..---", "2",
	  "...--", "3",
	  "....-", "4",
	  ".....", "5",
	  "-....", "6",
	  "--...", "7",
	  "---..", "8",
	  "----.", "9",
	  "-...-", "BT",
	  ".-.-.", "AR",
	  "...-.-", "SK",
	  );

&calibrate_lengths;
while (1) {
    if (&get_down > $c) {
	$letter .= '-';
    }
    else {
	$letter .= '.';
    }

    if (!&get_up($sc)) {	# at least interletter space
	print $morse{$letter};
	$letter = '';
	if (!&get_up(2*$sc)) {	# interword space
	    print " ";
	    &get_up;
	}
    }
}


sub key_ready {
    local($timeout) = @_;
    local($rin, $nfd);
    vec($rin, fileno(STDIN), 1) = 1;
    return $nfd = select($rin,undef,undef,$timeout);
}


sub gettimeofday {
    ## returns time in seconds
    local($sec, $usec);
    syscall(&SYS_gettimeofday, $tp, 0);
    ($sec, $usec) = unpack($timeval, $tp);
    $sec + $usec/1000000;
}

sub timediff {
    ## returns seconds since $mark
    &gettimeofday - $mark;
}

sub get_down {
    ## Returns time of the current keypress. $mark should have already
    ## been set by the keypress before this function is called. Sets
    ## $mark at key release.
    local($retval);
    &key_ready;
    &cleanup("inconsistency\n") if getc(STDIN) ne 'u';
    $retval = &timediff;
    $mark = &gettimeofday;
    $retval;
}

sub get_up {
    local($timeout) = @_;
    local($retval);
    return 0 unless &key_ready($timeout);
    &cleanup("inconsistency\n") if getc(STDIN) ne 'd';
    $retval = &timediff;
    $mark = &gettimeofday;
    $retval;
}

sub min {
    # returns the min of a list
    local($min) = $_[0];
    
    foreach (@_) {
	$min = $_ if $_ < $min;
    }
    $min;
}

sub max {
    # returns the min of a list
    local($max) = $_[0];
    
    foreach (@_) {
	$max = $_ if $_ > $max;
    }
    $max;
}

sub calibrate_lengths {
    ## find out what the user considers a dash or and a dot
    print "Enter the following: --- ... --- ...\n";
    &key_ready;
    &cleanup("inconsistency\n") if getc(STDIN) ne 'd';
    $mark = &gettimeofday;

    $dash[0] = &get_down;
    $shortspace[0] = &get_up;
    $dash[1] = &get_down;
    $shortspace[1] = &get_up;
    $dash[2] = &get_down;

    $longspace[0] = &get_up;

    $dot[0] = &get_down;
    $shortspace[2] = &get_up;
    $dot[1] = &get_down;
    $shortspace[3] = &get_up;
    $dot[2] = &get_down;
    
    $longspace[1] = &get_up;

    $dash[3] = &get_down;
    $shortspace[4] = &get_up;
    $dash[4] = &get_down;
    $shortspace[5] = &get_up;
    $dash[5] = &get_down;

    $longspace[2] = &get_up;

    $dot[3] = &get_down;
    $shortspace[6] = &get_up;
    $dot[4] = &get_down;
    $shortspace[7] = &get_up;
    $dot[5] = &get_down;

    &get_up;

    ## Take the longest dot and the shortest dash, make halfway point
    ## the criterion for deciding.
    $c = (&min(@dash) + &max(@dot)) / 2;
    
    ## same for the spaces
    $sc = (&min(@longspace) + &max(@shortspace)) / 2;
}

sub cleanup {
    local($msg) = @_;
    print $msg if $msg && $msg ne "INT";
    print "cleaning up\n";
    if ($ENV{'DISPLAY'}) {
	system("xset r");		# turn on autorepeat
    }
    exit;
}
    
SHAR_EOF
chmod +x 'morse.pl'
fi # end of overwriting check
if test -f 'keypress.c'
then
	echo shar: will not over-write existing file "'keypress.c'"
else
cat << \SHAR_EOF > 'keypress.c'
#include <stdio.h>
#include <X11/Xlib.h>

void close_window(void);
void event_loop(void);
void wait_until_visible(void);

Display *dpy;
Window win;
int screen_num;

void
main()
{
  /* connect to the X server */
  if ((dpy = XOpenDisplay(NULL)) == NULL) {
      fprintf(stderr, "screen: Cannot connect to the X server.\n");
      exit(1);
    }
  screen_num = DefaultScreen(dpy);
  
  /* create the window */
  win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy),
			    0, 0, 200, 200, 1,
			    BlackPixel(dpy, screen_num),
			    WhitePixel(dpy, screen_num));
  XSelectInput(dpy, win, KeyPressMask | KeyReleaseMask | ExposureMask);

  
  XMapWindow(dpy, win);
  wait_until_visible();
  event_loop();
  close_window();
}

void
event_loop(void)
{
  XEvent event;

  while (1) {
    XNextEvent(dpy, &event);
    switch(event.type) {
    case MappingNotify:
      XRefreshKeyboardMapping((XMappingEvent *) &event);
      break;
    case KeyPress:
      printf("d");
      fflush(stdout);
      break;
    case KeyRelease:
      printf("u");
      fflush(stdout);
      break;
    default:
      break;
    }
  }
}

void
close_window(void)
{
  XDestroyWindow(dpy, win);
  XCloseDisplay(dpy);
}

void
wait_until_visible(void)
{
  XEvent event;

  do
    XNextEvent(dpy, &event);
  while (event.type != Expose);
}
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0

--
Jin Choi    |NeXTmail, MIME accepted, RIPEM and PGP public keys available
jsc@mit.edu |by finger to monolith.mit.edu and key servers
MD5 of RIPEM Public Key: D262D5F296E23901E064103AB4359F75
	PGP fingerprint: A2 AB 40 DD E9 28 89 34  B0 BE 4E 09 2A 05 E0 2F 


