char version[]="$Id: bip.c,v 1.10 1996/03/31 23:25:24 skubi Exp skubi $";
/*
 bip.c      Marcin Skubiszewski (c) 1996-1999


                       HOW TO COMPILE bip

bip.c is expected to compile with ANSI C compilers, with C++
compilers, and with K&R (old style) C compilers.  With the latter, say
-DOLDCC on command line.

Under Linux and under systems derived from System V Release 4 (SVR4,
for short), bip.c can work with or without Xwindows.

    - If you do not have Xwindows, say "-Dlinux -DNOX" when compiling
(this holds even if your system is SVR4, not linux). The resulting
executable will always beep on the console of the machine where it
runs, and will not use Xwindows. It will work regardless of
whether a given machine has Xwindows installed.

    - If you have Xwindows installed, you can either proceed as above,
or say -lX11 at the end of the command line when compiling bip.c. In
this case, the executable will use Xwindows or the local console,
according to whether variable DISPLAY is set or not at execution time;
but it may not run at all o machines which do not have Xwindows
installed.

Under systems other than Linux and SVR4, bip requires Xwindows; say
-lX11 when compiling it.


bip has been tested on the following systems:

SunOS 4.1.3              gcc -O2 bip.c -lX11 -o bip
                         or: cc -DOLDCC -O bip-oldcc.c -lX11 -o bip

Solaris 2.5
(i.e. SunOS 5.5)         gcc -O -I /usr/openwin/include bip.c -lX11 -o bip

Linux Debian 2.1         gcc -O2 bip.c -L/usr/X11R6/lib -lX11 -o bip
                         or: gcc -O2 bip.c -DNOX -o bip

DEC Alpha OSF/1          gcc -O2 bip.c -lX11 -o bip

ESIX (a SVR4 system)     gcc -o bip bip.c -Dlinux -DNOX -L/usr/ucblib -lucb -lc


                AND WHAT IF IT DOES NOT COMPILE ?

Porting
-------

Look above to see whether bip.c has been tested on your system. If
not, some porting may be needed. Just do it, usually it's simple.

If your system is not Linux and you do not have (and have no simple
way to get) Xwindows, bip.c will not run or compile: bip.c uses the X
Windows libX11.a programming interface in order to send beeps. You
must find an interface in your system which allows you to send beeps
of arbitrary durations, and modify bip.c to use this interface instead
of Xwindows. This is harder than porting bip.c to a new platform which
has Xwindows.

Whe you do porting (of any kind), you can ask me for help, I will be glad
to provide it.


General thoughts
----------------

Check the correctness of your installation of the system. Upgrade if
appropriate (eg., fetch the newest installation of linux).


Specific solutions
------------------

If errno.h (or a file included from within errno.h) cannot be found,
just replace the line "#include <errno.h>" with "extern int errno;".



                        GENERAL DESCRIPTION

A program to learn Morse code. Sends random groups, words from a
user-specified list, or user-provided text.

"bip -H" gives the list of options; "bip -h" gives copyright information

General description:

This program contains the Morse code as in section 30.39 of the ARRL
Handbook 1995, national characters excepted.  The encoding is in
global variable morseCode, and its is straightforward to change or
augment it.

When sending externally provided text or words, the full encoding is used.
A newline is treated as whitespace, a blank line is treated as a
paragraph (AL).

When sending random groups, only the specified set of characters is used. By
default, the character set used in French ham exams is in use. See the
description of options in order to change the set. Examples:

bip s=eish5       - only send characters e i s h 5
bip su            - use the USA ham exam charset
bip s+bbbd        - cause b to accur 4 times more frequently than normal,
                    and d twice more frequently
bip cw13 "s=.,?/+SK AS="
                  - send American non-alphanumeric chars & prosigns at 13 WPM
hostname | gawk 'BEGIN{FS="\.";}; {print $1;}' | bip q F- d70
                  - this should be in your .login (DISPLAY must first
		    be set as appropriate)

By default, the program sends random groups of five characters. Option
g<n> causes the groups to contain <n> characters instead. Options g<n>
and G<m> used together provide for variable-length groups with [n..m]
chars.

Option F- causes stdin to be sent instead of random groups.
Option F<filename> causes <filename> to be sent.
Options W- and W<filename> consider stdin (resp. <filename>) as a list
of blank-and-or-newline separated words, and send these words in a
random order.

Speed: options dw and cw (and the more complicated options
d, c, w, ww) determine sending speed.  I highly recommend the Farhneit
method: even if you are a complete beginner, set a fast character
speed ("dw15": 15 WPM or "d60": 60 ms, which is roughly equiv.), and
make the spacing between characters as big as necessary (eg. "c1500":
1.5 s of silence between consecutive chars, or "cw4": 4 WPM overall
speed).

Tone and volume: option p sets the tone (it takes an argument in
Hz). Not all machines allow you to change tone. Option P sets the
volume (it takes an integer argument between 0 and 100: the % of the
maximal possible volume).

Jitter: option j determines jitter, between 0 and 1 (the more jitter,
the harder it is to copy). "j0" means no jitter (the default), "j0.3"
is strong jitter.

Examples of options:

     bip dw12        speed 12 WPM
     bip dw12 cw5    Fahrein system: each character sent at 12 WPM,
                     overall speed 5 WPM
     bip g3 G10      sends random groups of length chosen
                     randomly between 3 and 10
     stty cbreak; bip q F- dw15; stty cooked
                     send what you type on the keyboard


                  HOW TO START LEARNING CODE

You can start with learning e i s h 5, individual characters sent at
14 WPM, overall speed 3 WPM. This is accomplished by "bip s=eish5 dw14
cw3". Then, increase overall speed progressively up to 7 WPM, i.e. to
"bip s=eish5 dw14 cw7" (you do not change the character speed).

Then, you learn similarly t m o 0. Then, you mix in the
previously-learnt characters: "bip s=eish5tmo0tmo0tmo0tmo0tmo0tmo0
dw14 cw3" (t m o 0 appear most of the time, the other characters
appear six times less frequently); or "bip s=eish5tmo0 dw14 cw3" (all
characters equiprobable).

And so on, until you know the entire charset. Then, with the entire
charset, you work speed beyond 7 WPM. For example, to practice for
American Extra Class, use "bip su cw18". When practicing for an exam,
you may use words, eg.  "bip cw18 W/usr/dict/words" or variable-length
groups, eg.  "bip su cw18 g1 G10".

If, at any stage while learning, you observe that you have special
problems with some characters, you increase the relative frequency of
these characters until the problem disappears.  Eg. if you can't
correctly copy b, l and AS, use "bip su 's+bbbbllllAS AS AS AS'.

Be prepared to spend lots of time. For example, learning code for the
French exam (10 WPM, groups & cleartext, 1 error/minute allowed) takes
up to 100 hours.

bip.c is (C) 1996-1999 by Marcin Skubiszewski F8AQR
<skubi@skubi.net>. bip.c is distributed under the GNU General Public
License. The license is available at
http://www.gnu.org/copyleft/gpl.html

In order to distribute bip.c on terms other than the GNU General
Public License, you need to ask the author for permission.

Contributor: Geoffrey S. Mendelson (N3OWJ), gsm@mendelson.com (testing
on SVR4).

*/

#ifdef OLDCC
#define const
#define P(a) ()
#else
#define P(a) a
#endif
#if (defined(__linux__) || defined(__linux)) && !defined(linux)
#define linux 1
#endif

#if defined(NOX) && !defined(linux)
   It does not make sense to define NOX if you are not under Linux.
   NOX means that the X11 beeping interface is not used, and this program
   has an alternative for this interface only under Linux. 
#endif
   
char *usageText[] = {
    "                             USAGE",
    "",
    "Type \"bip h\" for information about authorship and copyright.",
    "",
    "bip is a Morse code teaching program for Unix-like systems.",
    "",
    "Command-line options:",
    "(times are in milliseconds, except as stated otherwise; default values",
    "are given in square brackets)",
    "",
    "  d<integer>: set dot duration [50 ms]",
    "  dw<float>: set dot duration so that the resulting character speed",
    "      will be approximately <float>",
    /*"  D<integer>: dash duration [3 * dot duration]",
      "      (under normal circumstancies, do not use option D)",*/
    "  c<integer>: spacing between characters [dash duration]",
    "  cw<float>: set spacing between characters so as to obtain overall",
    "      speed of <float>, without changing speed of individual characters",
    "  w<integer>: spacing between words [2 * dash duration]",
    "  ww<integer>: set spacing between words so as to obtain overall speed",
    "      of <float>, without changing speed of individual words",
    "  j<float>: jitter: every time will be randomly shortened or made",
    "      longer by up to <float> proportion of its normal duration; the more",
    "      jitter, the harder it is to copy code [0]",
    "  g<integer>: minimal length of a random group of characters [5]",
    "  G<integer>: maximal length of a random group of characters",
    "      [minimal length]",
    "  F<filename>: send contents of <filename> instead of random chars",
    "  F-: send stdin instead of random chars",
    "  W<filename>: consider <filename> as a list of words; send these words",
    "      in random order",
    "  W-: same as above, except that stdin is read instead of <filename>",
    "  n<integer>: max number of characters to output [unlimited]",
    "  H: gives this message",
    "  h: gives authorship and copyright information",
    "  sf: character set as for the French ham exam [this is the default]",
    "  su: character set as for the USA ham exam",
    "  s=<string>: use <string> as character set",
    "  s+<string>: add <string> to the character set",
    "    If a character is present many times in the charset resulting from these",
    "    options, it will appear with a frequency proportional to the number",
    "    of times it is present",
    "  p: bell pitch (frequency in Hz)",
    "  P: bell percent (% of maximal possible volume)",
    "  v: verbose",
    "  q: quiet",
    0
};

char *sharewareText[] = {
    "",
    "            AUTHORSHIP AND COPYRIGHT INFORMATION",
    "",
    "Type \"bip H\" for more information about this program.",
    "",
"bip.c is (C) 1996-1999 by Marcin Skubiszewski F8AQR <skubi@skubi.net>.",
"bip.c is distributed under the GNU General Public License. The license",
"is available at http://www.gnu.org/copyleft/gpl.html",
"",
"In order to distribute bip.c on terms other than the GNU General",
"Public License, you need to ask the author for permission.",
"",
"Contributor: Geoffrey S. Mendelson (N3OWJ), gsm@mendelson.com (testing",
"on SVR4).",
    0
};

#include <stdlib.h>
#include <stdio.h>
#ifndef NOX
#include <X11/Xlib.h>
#endif
#ifdef linux
#include <sys/types.h>
#include <sys/kd.h>
#include <fcntl.h>
#endif
/* This is for DEC cxx */
#if defined(__LANGUAGE_C) && !defined(__LANGUAGE_C__)
#define __LANGUAGE_C__
#endif
#include <signal.h>
#include <sys/time.h>
#include <string.h>
#include <errno.h>

#ifndef NOX
XKeyboardState ks;
XKeyboardControl kc;
#endif

unsigned int prevdur;
int pitch = 1500, prevpitch, pitchSet=0;
#if defined(linux)
/* This patches around a bug in at least one linux machine */
int percent=-1, prevpercent, percentSet=1;
#else
int percent, prevpercent, percentSet=0;
#endif
unsigned bellMask = 0;

#ifndef NOX
char *dispName=0;
Display *d = 0;
#endif
int console = 0;

int charsDone=0;
int blankDone=1;
struct timeval startTime;

char charsetFrance[] ="abcdefghijklmnopqrstuvwxyz1234567890.,?/+'SK AS";
char charsetUSA[] = "abcdefghijklmnopqrstuvwxyz1234567890.,?/+SK AS=";

/*
 * These values may be modified by command-line arguments
 */

int verbosity=0; /* 0: normal; 2: debugging; 1: verbose; -1: quiet */
int dotdur=50, dotdurSet=0;
double dotdurW; int dotdurWSet=0;
double jitter=0; int jitterSet=0;
int charSpace=150, charSpaceSet=0;
double charSpaceW; int charSpaceWSet=0;
int wordSpace=300, wordSpaceSet=0;
double wordSpaceW; int wordSpaceWSet=0;
int groupMin=5, groupMinSet=0;
int groupMax=5, groupMaxSet=0;
int totalChars=-1, totalCharsSet=0;
const char *filename = 0; int filenameSet = 0;
const char *Wfilename = 0; int WfilenameSet = 0; 
char *charset = 0; /* charset stays 0 until set */

/* In the line above, replace charsetFrance with charsetUSA if you want to
use by default the charset used in US ham exams */


FILE *f = 0;
int expectedDelay=13000;

extern char *morseCode[];
extern char *sharewareText[];
extern char *usageText[];

/* A fast function for allocating small chunks of memory */

#ifndef OLDCC
char *fastAlloc (int size)
#else
char *fastAlloc (size)
    int size;
#endif
{
    static char *block=0;
    static int remains=0;
    char *result;
    if (size <= 0 || size > 2048) {
	fprintf (stderr,
		 "Internal error: fastAlloc: wrong size requested %d\n",
		 size);
    }
    if (remains < size) {
	block = (char *)malloc (12000);
	if (block == 0) {
	    fprintf (stderr, "Memory exhausted\n");
	    exit(1);
	}
	remains = 12000;
    }
    result = block;
    block += size;
    remains -= size;
    return result;
}

typedef struct _charDesc {
    unsigned short elts; /* LSB first; 0 dot, 1 dash */
    char length; /* number of elements */
    char name [9];
    char thisChar;
    char time; /* Time of this char in dots, assuming 1 dash = 3 dots */
} charDesc;

charDesc charTable[256];
int nextGroupEnd=0;

typedef struct _prosignNode {
    struct _prosignNode *deeper;
    charDesc *desc;
} prosignNode;

prosignNode pros[26];

/* For debugging */

#ifndef OLDCC
void listPros(prosignNode *n, int level)
#else
void listPros(n, level)
    prosignNode *n; int level;
#endif
{
    int i;
    for (i=0; i<26; i++) {
	if (n[i].deeper || n[i].desc) {
	    int j;
	    for (j=0; j<level; j++)
		putchar (' ');
	    printf ("%c: %s\n",
		    i+'A', n[i].desc ? n[i].desc->name : "<none>");
	    if (n[i].deeper) {
		listPros(n[i].deeper, level+1);
	    }
	}
    }
}


#ifndef OLDCC
void prosInit (prosignNode *p)
#else
void prosInit(p)
    prosignNode *p;
#endif
{
    int i;
    for (i=0; i<26; i++) {
	p[i].deeper=0;
	p[i].desc=0;
    }
}

#ifndef OLDCC
void registerPros (charDesc *desc)
#else
void registerPros (desc)
    charDesc *desc;
#endif
{
    int l = strlen (desc->name);
    int i;
    prosignNode *p = pros + (desc->name[0] - 'A');
    if (desc->name[0] < 'A' || desc->name[0] > 'Z') {
      fprintf (stderr,
	       "Invalid prosign %s (only capital letters are allowed)\n",
	       desc->name);
      exit(1);
    }
    if (verbosity >= 2) {
	printf ("register %s %d %d\n", desc->name, desc->name[0] - 'A',
		desc->name[1] - 'A');
    }
    for (i=1; i<l; i++) {
      if (desc->name[i] < 'A' || desc->name[i] > 'Z') {
	fprintf (stderr,
		 "Invalid prosign %s (only capital letters are allowed)\n",
		 desc->name);
	exit(1);
      }
	if (p->deeper == 0) {
	    p->deeper = (prosignNode *) malloc (26 * sizeof (prosignNode));
	    if (p->deeper == 0) {
		fprintf (stderr, "Memory exhausted (prosignNode)\n");
		exit(1);
	    }
	    prosInit (p->deeper);
	}
	p = p->deeper + (desc->name[i] - 'A');
    }
    p->desc = desc;
}

void finish P((int xCode));
#ifdef __SUNPRO_CC
void finishH P((int sig, ...)) {finish (0);}
void nothing P((int sig, ...)) {}
#else
void finishH P((int sig)) {finish (0);}
void nothing P((int sig)) {}
#endif

void finish P((int));
void usage P((void));
void shareware P((void));
void readChars P((void));

void sendChar P((struct timeval *t, charDesc *desc));
int sendBip P((struct timeval *refTime, int bipTime, int toleranceTime));
void advance P((struct timeval *refTime, int spaceTime));

void printBlurb P((FILE *f, char **t));

int argc; char **argv;

#ifndef OLDCC
charDesc *charAndProsDecode (char c, int *again, int *pending)
#else
charDesc *charAndProsDecode (c, again, pending)
     char c;
     int *again;
     int *pending;
#endif
{
    static prosignNode *n = 0;
    static char accumCapitals[80];
    static int numAccumCapitals=0;

    charDesc *d; /* The value to be returned */

    if (c < 'A' || c > 'Z') {
	if (n) {
	    if (n->desc) {
		d = n->desc;
		n=0; numAccumCapitals=0;
		*again = (c != ' ');
	    } else {
		/* The preceding string of capitals is not a prosign */
		int i;
		d = charTable + (accumCapitals[0] + 'a' - 'A');
		*again = 1;
		for (i=0; i<numAccumCapitals-1; i++) {
		    accumCapitals[i] = accumCapitals[i+1];
		}
		numAccumCapitals--;
		if (numAccumCapitals == 0)
		    n=0;
	    }
	} else {
	    d = charTable + (unsigned char)c;
	    *again = 0;
	}
    } else { /* it is a capital */
	if (n) {
	    if (n->deeper) {
		n = n->deeper + (c-'A');
		d = 0; *again = 0;
		accumCapitals[numAccumCapitals] = c;
		numAccumCapitals++;
	    } else {
		/* The capitals read so far do not form a prosign or the
		   beginning of a prosign */
		int i;
		d = charTable + (accumCapitals[0] + 'a' - 'A');
		*again = 1;
		for (i=0; i<numAccumCapitals-1; i++) {
		    accumCapitals[i] = accumCapitals[i+1];
		}
		numAccumCapitals--;
		if (numAccumCapitals == 0)
		    n=0;
	    }
	} else {
	    n = pros + (c - 'A');
	    d = 0; *again = 0;
	    accumCapitals[0] = c;
	    numAccumCapitals = 1;
	}
    }
    if (verbosity >= 2) {
	printf ( "[<= %s %p %d] ", (d ? d->name : "(none)"), d,
		 *again);
	fflush(stdout);
    }
    if (pending)
	*pending = n ? 1 : 0;
    return d;
}

#define wordlistNum 7000
typedef struct _wordlist {
    char *car[wordlistNum];
    struct _wordlist *cdr;
} wordlist;

wordlist *words = 0;
int wordNum=0;

#ifndef OLDCC
char *wordSelect(int n)
#else
char *wordSelect(n)
    int n;
#endif
{
    wordlist *w = words;
    while (n >= wordlistNum) {
	n -= wordlistNum;
	w = w->cdr;
    }
    return w->car[n];
}

char *wordSelectRandom P((void))
{
    return wordSelect (((unsigned)random()>>3) % wordNum);
}

/* Builds a wordlist and deposits it in words; sets wordNum accodringly */

#ifndef OLDCC
void buildWordlist (FILE *wf)
#else
void buildWordlist (wf)
     FILE *wf;
#endif
{
    char inProgress[256];
    char *IinProgress = inProgress;
    int again=0, pending=0, tmp, recoded;
    charDesc *cd;
    wordlist **curWordlist = &words;
    int curNum = wordNum;
    while (curNum >= wordlistNum) {
	curNum -= wordlistNum;
	curWordlist = &(*curWordlist)->cdr;
    }
    for (;;) {
	if (again == 0) {
	    tmp = getc(wf);
	    if (tmp == EOF) {
		if (errno) {
		    perror ("getchar");
		    finish(0);
		}
		if (pending) {
		    tmp=' ';
		}
	    }
	}
	if (tmp != EOF) {
	    cd = charAndProsDecode (tmp, &again, &pending);
	    if (cd == 0)
		continue;
	    recoded = cd->thisChar; /* previously long time */
	}
	if (tmp == EOF || recoded == ' ' || recoded == '\t'
	    || recoded == '\n' || recoded == '\r') {
	    /* Terminaste the word */
	    char *thisWord;
	    if (IinProgress > inProgress) {
		/* Something is in progress */
		*IinProgress++ = 0;
		thisWord = fastAlloc (IinProgress - inProgress + 2);
		/*if (thisWord == 0) {
		    fprintf (stderr, "Memory exhausted (word)\n");
		    exit(1);
		    }*/
		strcpy (thisWord, inProgress);
		IinProgress = inProgress;
		if (*curWordlist == 0) {
		    curNum = 0;
		    *curWordlist = (wordlist *) malloc (sizeof(wordlist));
		    if (*curWordlist == 0) {
			fprintf (stderr, "Memory exhausted (wordlist)\n");
			exit(1);
		    }
		    (*curWordlist)->cdr = 0;
		}
		(*curWordlist)->car[curNum] = thisWord;
		curNum++; wordNum++;
		if (curNum == wordlistNum) {
		    curNum = 0;
		    curWordlist = &(*curWordlist)->cdr;
		}
	    }
	    if (tmp == EOF) {
		int e = fclose (wf);
		if (e == EOF) {
		    perror ("fclose");
		    exit(1);
		}
		return;
	    }
	} else {
	    *IinProgress++ = recoded;
	}
    }
}

int optionInt P((void))
{
    char *tmp;
    int res = (int)strtol (*argv, &tmp, 0);
    if (tmp == *argv) {
	fprintf (stderr,
		 "Malformed integer: %s\n", *argv);
	usage();
	exit(1);
    }
    return res;
}

#ifndef __SUNPRO_CC
double strtod P((const char *str, char **ptr));

/* the Sun C compiler interprets this file as C++, and the declaration
above causes it to consider strtod as a C++-mangled function; on the
other hand, Sun CC's include files declare strtod. */
#endif

double optionDouble P((void))
{
    char *tmp;
    double res = strtod (*argv, &tmp);
    if (tmp == *argv) {
	fprintf (stderr,
		 "Malformed float: %s\n", *argv);
	usage();
	exit(1);
    }
    return res;
}

double optionDoubleN0 P((void))
{
    double tmp = optionDouble();
    if (tmp == 0) {
	fprintf (stderr, "Illegal zero argument\n");
	exit(1);
    }
    return tmp;
}

char *optionString P((void))
{
    return *argv;
}

static long rndState[512];

#ifndef OLDCC
char *augmentCharset (char *prev, char *extras)
#else
char *augmentCharset (prev, extras)
     char *prev;
     char *extras;
#endif
{
    int prevlen = strlen(prev);
    char *res = (char *)malloc (prevlen + strlen(extras) + 3);
    char *p = res + prevlen;
    int again=0;
    int pending=0;

    if (res == 0) {
	fprintf (stderr, "Memory exhausted (charset)\n");
	exit (1);
    }

    strcpy (res, prev);
    
    /* fprintf (stderr, "Extras len %p\n", strlen(extras)); */
    while (pending || *extras || again) {
	charDesc *cd = charAndProsDecode (*extras, &again, &pending);
	/* fprintf (stderr, "Get Extra %c %d %p %d %d;  ",
		 *extras, *extras, extras, again, pending); */
	if (again==0 && *extras)
	    extras++;
	
	if (cd && cd->thisChar != ' ') {
	    *p++ = cd->thisChar;
	    /*fprintf (stderr, "set %c %d %p\n", *(p-1), *(p-1), p-1); */
	}
    }
    *p=0;
    return res;
}

#ifndef OLDCC
char *setCharset (char *extras)
#else
char *setCharset (extras)
    char *extras;
#endif
{
    return augmentCharset ("", extras);
}

void parseOptions P((void))
{
    while (argc) {
	char option;
	if (**argv == '-')
	    (*argv)++;
	option = **argv;
        if (option != 'h' && option != 'H' && option != 'v' &&  option != 'q'
            && argv[0][1] == 0) {
	    argv++; argc--;
	    if (argc<=0) {
		fprintf (stderr, "Missing argument for option %c\n", option);
		usage();
	    }
	} else
	    (*argv)++;
        
	switch (option) {
        case 'v':
            verbosity++;
            break;
        case 'q':
            verbosity--;
            break;
	case 'd':
            if (**argv == 'w') {
                if (argv[0][1] == 0) {
                    argv++; argc--;
                    if (argc<=0) {
                        fprintf (stderr, "Missing argument for option dw\n");
                        usage();
                    }
                } else
                    (*argv)++;
                dotdurW = optionDoubleN0();
                dotdurWSet=1; dotdurSet=0;
            } else {
                dotdur = optionInt ();
                dotdurSet=1; dotdurWSet=0;
            }
	    break;
	case 'j':
	    jitter = optionDouble();
	    jitterSet=1;
	    break;
	case 'c':
            if (**argv == 'w') {
                if (argv[0][1] == 0) {
                    argv++; argc--;
                    if (argc<=0) {
                        fprintf (stderr, "Missing argument for option cw\n");
                        usage();
                    }
                } else
                    (*argv)++;
                charSpaceW = optionDoubleN0();
                charSpaceWSet=1; charSpaceSet=0;
            } else {
		charSpace = optionInt ();
		charSpaceSet=1;
	    }
	    break;
	case 'p':
	    pitch = optionInt ();
	    pitchSet=1;
	    break;
	case 'P':
            percent = optionInt ();
            percentSet=1;
	    break;
	case 'w':
	    if (**argv == 'w') {
		if (argv[0][1] == 0) {
		    argv++; argc--;
		    if (argc<=0) {
			fprintf (stderr, "Missing argument for option ww\n");
			usage();
		    }
		} else
		    (*argv)++;
		wordSpaceW = optionDoubleN0();
		wordSpaceWSet=1; wordSpaceSet=0;
	    } else {
		wordSpace = optionInt ();
		wordSpaceSet=1;
	    }
	    break;
	case 'g':
	    groupMin = optionInt ();
	    groupMinSet=1;
	    break;
	case 'G':
	    groupMax = optionInt ();
	    groupMaxSet=1;
	    break;
	case 'X':
	    expectedDelay = optionInt ();
	    break;
	case 'n':
	    totalChars = optionInt ();
	    totalCharsSet=1;
	    break;
	case 'F':
	case 'f':
	    filename = optionString ();
	    filenameSet=1;
	    break;
	case 'W':
	    Wfilename = optionString ();
	    WfilenameSet=1;
	    break;
	case 's':
	{
	    char *input = optionString ();
	    switch (input[0]) {
	    case 'f':
		charset = setCharset (charsetFrance);
		break;
	    case 'u':
		charset = setCharset (charsetUSA);
		break;
	    case '+':
		if (charset == 0)
		    charset = setCharset (charsetFrance);
		charset = augmentCharset (charset, input+1);
		break;
	    case '=':
		charset = setCharset (input+1);
		break;
	    default:
		fprintf (stderr,
			 "Wrong option %c%c; %cu %cf %c= %c+ are valid\n",
			 option, *input, option, option, option, option);
		exit(1);
	    }
	} break;
	case 'h':
	case '?':
	    printBlurb(stdout, sharewareText);
	    exit(0);
	case 'H':
	    printBlurb(stdout, usageText);
	    exit(0);
	default:
	    fprintf (stderr, "Unknown option: %c\n", option);
	    usage();
	    exit(1);
	}
	argc--; argv++;
    }
    if (filenameSet && WfilenameSet) {
	fprintf (stderr, "Options W and f are exclusive of each other\n");
	exit(1);
    }
}

#ifndef OLDCC
FILE *myOpen (const char *nm)
#else
FILE *myOpen (nm)
    char *nm;
#endif
{
    if (nm[0] == '-' && nm[1] == 0) {
	return stdin;
    } else {
	FILE *tmp = fopen (nm, "r");
	if (tmp == NULL) {
	    perror ("fopen");
	    exit(1);
	}
	return tmp;
    }
    
}

int charsCounted=0;
double blanksPerChar=0; /* avg # of blanks per char, eg. 0.2 for
			   5-character groups */
int totalTime=0; /* total time of all characters, in dots */

double avgCharTime=0; /* avg time of a char in dots, including a fractrion of
			 blank time */
void computeSpeedInfo P((void))
{
    charsCounted=0;
    totalTime=0;
    if (WfilenameSet) {
	int remains;
	wordlist *curWords = words;
	int i;
	char *s;
	for (remains = wordNum, i=0; remains; remains--, i++) {
	    if (i==wordlistNum) {
		i=0;
		curWords = curWords->cdr;
	    }
	    for (s = curWords->car[i]; *s; s++) {
		charsCounted++;
		totalTime += charTable[*s].time;
	    }
	}
	blanksPerChar = (double)wordNum/charsCounted;
    } else if (filenameSet) {
	charsCounted = 2;
	totalTime = 18;
	blanksPerChar = .2;
    } else {
	char *s;
	for (s=charset; *s; s++) {
	    totalTime += charTable[(unsigned char)*s].time;
	    charsCounted++;
	}
	blanksPerChar = 2.0/(groupMin+groupMax);
    }
    avgCharTime = (double)totalTime/charsCounted + 3.0*blanksPerChar;
    if (verbosity > 0) {
	printf ("Average time per character: %f dots (%d/%d + 3*%f)\n",
		avgCharTime, totalTime, charsCounted, blanksPerChar);
    }
}

#ifndef OLDCC
int main(int __argc, char **__argv)
#else
main (__argc, __argv)
    int __argc; char **__argv;
#endif
{
    int e;
    struct timeval refTime;
    unsigned seed;
    int numChars;
    int firstInLine=0;
    int tmp;
    int again=0;
    int pending=0;

    readChars();

    argc = __argc-1; argv=__argv+1;
    parseOptions();

    if (groupMaxSet == 0)
	groupMax = groupMin;
    if (charset == 0)
	charset = setCharset (charsetFrance);
    numChars = strlen(charset);
    if (numChars == 0) {
	fprintf (stderr, "Error: empty charset\n");
	exit(1);
    }

    if (filenameSet) {
	f = myOpen(filename);
    }
    if (WfilenameSet) {
	buildWordlist(myOpen(Wfilename));
    }

    if (dotdurWSet || charSpaceWSet || wordSpaceWSet)
	computeSpeedInfo ();

    if (dotdurWSet) {
        dotdur = 12000.0/(avgCharTime*dotdurW);
        dotdurSet=1;
    }

    if (charSpaceWSet) {
	charSpace = (12000/charSpaceW
		     - ((double)totalTime/charsCounted-3)*dotdur)
	    / (1+blanksPerChar);
	if (charSpace < 0) {
	    fprintf
		(stderr, 
		 "Error: you requested a negative inter-character space\n");
	    exit(1);
	}
	charSpaceSet=1;
    }
    if (charSpaceSet == 0)
	charSpace = 3*dotdur;

    if (wordSpaceWSet) {
	wordSpace = (12000/wordSpaceW
		     - ((double)totalTime/charsCounted-3)*dotdur + charSpace)
	    / blanksPerChar;
	if (wordSpace < 0) {
	    fprintf
		(stderr, 
		 "Error: you requested a negative inter-word space\n");
	    exit(1);
	}
	wordSpaceSet=1;
    }
    if (wordSpaceSet == 0)
	wordSpace = 2*charSpace;

    if (verbosity > 0) {
	printf ("Duration: dot %d, betw.-char %d, betw.-word %d\n",
		dotdur, charSpace, wordSpace);
    }


    if (jitterSet == 0)
	jitter = 0;
#ifndef NOX
    dispName = getenv("DISPLAY");
#ifndef linux
    if (dispName==0) {
	fprintf (stderr, "DISPLAY undefined\n");
	exit(1);
    }
#endif
    if (dispName) {
      if (pitchSet)
	bellMask |= KBBellPitch;
      if (percentSet)
	bellMask |= KBBellPercent;
      d = XOpenDisplay(dispName);
      if (d == 0) {
	fprintf (stderr, "Cannot open display %s\n", dispName);
	exit(1);
      }
      XGetKeyboardControl(d, &ks);
      prevdur = ks.bell_duration;
      prevpitch = ks.bell_pitch;
      prevpercent = ks.bell_percent;
      kc.bell_pitch = pitch;
      kc.bell_percent = percent;
      XChangeKeyboardControl(d, bellMask, &kc);
    } else {
#endif
#ifdef linux
      console = open("/dev/console", O_WRONLY);
      if (console < 0) {
	perror ("opening console");
	exit(1);
      }
#endif
#ifndef NOX      
    }
#endif
    e = gettimeofday(&startTime, 0);
    if (e == -1)
	perror ("gettimeofday");
    errno=0;
    signal (SIGINT, finishH);
    if (errno)
	perror ("signal");
    errno=0;
    signal (SIGTERM, finishH);
    if (errno)
	perror ("signal");
    errno=0;
    signal (SIGHUP, finishH);
    if (errno)
	perror ("signal");
    errno=0;
    signal (SIGALRM, nothing);
    if (errno)
	perror ("signal (SIGALRM)");
    e = gettimeofday (&refTime, 0);
    if (e == -1)
	perror ("gettimeofday");
    seed = refTime.tv_sec+refTime.tv_usec;
    initstate (seed, (char *)rndState, 512);

    advance (&refTime, dotdur);
    nextGroupEnd = filenameSet ? -1 
	: groupMin + ((unsigned)random()>>3) % (groupMax-groupMin+1);

    tmp = ' ';
    for (;;) {
	static int nlInARow=0;
        static spInARow=0;
	charDesc *toSend;
	char toSendChar;
	if (WfilenameSet) {
	    static char *curChar = 0;
	    if (curChar==0) {
		curChar = wordSelectRandom();
	    }
	    toSend = charTable + (unsigned char) *curChar++;
	    if (toSend == charTable) {
		toSend = charTable + ' ';
		curChar = 0;
	    }
	} else if (filenameSet) {
	    if (again == 0 && tmp != EOF) {
		tmp = getc(f);
		if (verbosity >= 2)
		    printf (" [[%d]] ", tmp);
		if (tmp == 4) {
		    /* In cbreak mode, a ^D appears as \004, not as EOF */
		    tmp = EOF;
		}
		if (tmp == EOF) {
		    if (errno) {
			perror ("getchar");
			finish(0);
		    } else if (pending) {
			tmp = 0;
		    } else {
			finish(0);
		    }
		}
		if (tmp == '\n') {
		    nlInARow++;
		} else {
		    if (tmp != ' ' && tmp != '\t')
			nlInARow=0;
		}
	    }
	    toSend = charAndProsDecode (tmp, &again, &pending);
	    if (toSend == 0)
		continue;
	} else {
	    if (charsDone == nextGroupEnd) {
		nextGroupEnd += groupMin + ((unsigned)random()>>3) % (groupMax-groupMin+1);
		toSend = charTable + ' ';
	    } else {
		unsigned c = (unsigned)random()>>3;
		c %= numChars;
		toSend = charTable + (unsigned char)charset[c];
	    }
	}
        /* Send space for space or unknown char or first newline,
           but never send several spaces in a row
           */
	toSendChar = toSend->thisChar;
	if (toSendChar == ' ' || (toSendChar == '\n' && nlInARow == 1)
            || toSend->name[0] == 0) {
	    if (verbosity >= 0)
		putc (toSendChar, stdout);
	    if (toSendChar == ' ') {
		if (verbosity >= 0) {
		    if (charsDone-firstInLine >= 30 & !filenameSet) {
			firstInLine = charsDone;
			putchar('\n');
		    }
		}
	    }
            if (blankDone == 0) {
                if (verbosity >= 0)
		    fflush (stdout);
                advance (&refTime, wordSpace-charSpace);
            }
            blankDone=1;
	} else {
	    sendChar(&refTime, toSend);
            blankDone=0;
            spInARow=0;
	    if (verbosity >= 0) 
		fputs (toSend->name, stdout);
            if (totalChars >= 0 && charsDone >= totalChars)
                finish(0);
	    advance (&refTime, charSpace);
	}
    }
    finish(0);
    return 0; /* To satisfy ATT compiler */
}

#ifndef OLDCC
void sendChar (struct timeval *t, charDesc *desc)
#else
void sendChar (t, desc)
     struct timeval *t; charDesc *desc;
#endif
{
    unsigned elts = desc->elts;
    int length = desc->length;
    int i;
    for (i=0; i<length; i++) {
	sendBip (t, (elts & (1<<i)) ? 3*dotdur : dotdur, dotdur);
	if (i<length-1)
	    advance (t, dotdur);
    }
    if (length)
        charsDone++;
}

/*
 * This function sends a beep of length bipTime (in milliseconds)
 * at time refTime. It updates refTime so that
 * it corresponds with the end of the beep sent.
 * If the sending is suspected to have been delayed more than toleranceTime
 * (in milliseconds), then return -1. Otherwise, return 0 (THIS FEATURE
 * IS NOT CURRENTLY IMPLEMENTED).
 */

#ifndef OLDCC
int sendBip (struct timeval *refTime, int bipTime, int toleranceTime)
#else
int sendBip (refTime, bipTime, toleranceTime)
     struct timeval *refTime; int bipTime; int toleranceTime;
#endif
{
    struct timeval tvtmp, tvtmp2;
    struct itimerval iti;
    int e;
    int tvdiff;
    iti.it_interval.tv_sec = 0;
    iti.it_interval.tv_usec = 0;
    if (jitter) {
	int jitterHere = jitter*bipTime;
	bipTime += - jitterHere + (((unsigned)random()>>3)%(8*jitterHere+4))/4;
    }
#ifndef NOX
    if (d) {
      kc.bell_duration = bipTime;
      XChangeKeyboardControl(d, KBBellDuration, &kc);
      XFlush(d);
      XBell (d, 0);
    }
#endif
    errno=0;
    signal (SIGALRM, nothing);
    if (errno)
	perror ("signal (SIGALRM)");

    e = gettimeofday(&tvtmp, 0);
    if (e == -1) {
	perror ("gettimeofday");
	finish (1);
    }
    iti.it_value.tv_usec = 1000000 + refTime->tv_usec - tvtmp.tv_usec;
    iti.it_value.tv_sec = refTime->tv_sec - tvtmp.tv_sec
	+ (iti.it_value.tv_usec / 1000000) - 1;
    iti.it_value.tv_usec %= 1000000;
    /* fprintf (stderr, "tvtmp %d, %d; refTime %d, %d; iti %d, %d\n",
       tvtmp.tv_sec, tvtmp.tv_usec, refTime->tv_sec, refTime->tv_usec,
       iti.it_value.tv_sec, iti.it_value.tv_usec); */
      
    if ((double)iti.it_value.tv_sec*1000000 + (double)iti.it_value.tv_usec
	> 0) {
      e = setitimer (ITIMER_REAL, &iti, 0);
      if (e == -1) {
	perror ("setitimer");
	finish(1);
      } else {
	int err;
	static int sigpauseArg = 0;
	/* For correctness, sigpauseArg should be 0 under most
	   systems, and SIGALRM under others; we make the right
	   choice at run time */
      pauseAgain:
	err = sigpause(sigpauseArg);
	if (err == -1 && errno != EINTR) {
	  if (errno == EINVAL && sigpauseArg == 0) {
	     sigpauseArg = SIGALRM;
	     goto pauseAgain;
	  } else {
	    perror ("sigpause");
	    finish(1);
	  }
	}
      }
    } else {
      *refTime = tvtmp;
    }
#ifndef NOX
    if (d) {
      XFlush(d);
    } else {
#endif
#ifdef linux
      e = ioctl(console, KDMKTONE,
		(bipTime << 16) | ((1193180 / pitch) & 0xffff));
      if (e < 0) {
	perror ("ioctl KDMKTONE");
	exit(1);
      }
#endif
#ifndef NOX
    }
#endif
    e = gettimeofday(&tvtmp2, 0);
    if (e == -1) {
	perror ("gettimeofday");
	finish(1);
    }
    tvdiff= 1000000*(tvtmp2.tv_sec-refTime->tv_sec)
	+ tvtmp2.tv_usec - refTime->tv_usec;
    if (tvdiff > expectedDelay) {
	refTime->tv_usec += ((tvdiff-expectedDelay)*3)/4;
    }
    refTime->tv_usec += 1000*bipTime;
    refTime->tv_sec += refTime->tv_usec / 1000000;
    refTime->tv_usec %= 1000000;
    return 0;
}

/*
 * Advance refTime by spaceTime milliseconds
 */

#ifndef OLDCC
void advance (struct timeval *refTime, int spaceTime)
#else
void advance (refTime, spaceTime)
     struct timeval *refTime; int spaceTime;
#endif
{
    if (jitter) {
	int jitterHere = jitter * spaceTime;
	spaceTime += - jitterHere + (((unsigned)random()>>3)%(8*jitterHere+4))/4;
    }
    refTime->tv_usec += spaceTime*1000;
    refTime->tv_sec += refTime->tv_usec / 1000000;
    refTime->tv_usec %= 1000000;
}

#ifndef OLDCC
void finish (int xCode)
#else
void finish (xCode)
     int xCode;
#endif
{
  int e;
#ifndef NOX
  if (d) {
    kc.bell_duration = prevdur;
    kc.bell_pitch = prevpitch;
    kc.bell_percent = prevpercent;
    XChangeKeyboardControl(d, bellMask | KBBellDuration, &kc);
    XFlush(d);
  }
#endif
  if (xCode==0) {
    struct timeval endTime;
    double t;
    e = gettimeofday (&endTime, 0);
    if (e == -1)
      perror ("gettimeofday");
    t = endTime.tv_sec-startTime.tv_sec
      + 1E-6*(endTime.tv_usec-startTime.tv_usec);
    if (verbosity >= 0)
      printf ("\n%d characters in %.2f seconds (%.2f WPM)\n",
	      charsDone, t, (charsDone/t)*12);
  }
  exit(xCode);
}

/*
 * Read the table describing Morse code
 */

void readChars P((void))
{
    char nmBuf[50], didahBuf[50];
    int e, i;
    char **code;
    prosInit (pros);
    if (verbosity >= 3) {
	listPros (pros, 0);
	printf ("==================\n");
    }
    for (i=0; i<256; i++) {
	charTable[i].thisChar = i;
    }
    for (code = morseCode; *code; code++) {
        /* printf ("%s\n", *code); */
	charDesc *ourDesc = charTable + (unsigned char)**code;
	e = sscanf ((*code)+1, " %[^ 	] %[.-]", nmBuf, didahBuf);
	if (e != 2) {
	    perror ("sscanf Morse code table");
	    exit(1);
	}
	strncpy (ourDesc->name, nmBuf, sizeof(ourDesc->name)-1);
	ourDesc->length = strlen(didahBuf);
	ourDesc->time = 2*ourDesc->length+2;
	for (i=0; i<ourDesc->length; i++) {
	    if (didahBuf[i] == '-') {
		ourDesc->elts |= 1<<i;
		ourDesc->time += 2;
	    }
	}
	if (strlen (ourDesc->name) > 1) {
	    registerPros(ourDesc);
	    if (verbosity >= 3) {
		listPros (pros, 0);
		printf ("==================\n");
	    }
	}
    }
    if (errno) {
	perror ("reading Morse.tab");
	exit(1);
    }
}

void usage P((void))
{
    printBlurb(stderr, usageText);
}

#ifndef OLDCC
void printBlurb(FILE *f, char *t[])
#else
void printBlurb(f, t)
     FILE *f; char *t[];
#endif
{
    for(; *t; t++) {
	fputs (*t, f);
	putc ('\n', f);
    }
}

/*
 * Each element in morseCode contains three blank-separated subelements:
 * an ASCII character, the name and the Morse encoding.

 * Whenever you want to input something to bip, in an input file or on
 * command line, you may use either the ASCII char or the name. Names must
 * be capitalized. The name is used whenever bip outputs something,
 * eg. for bip to send digits, ASs and SKs, we recommend
 *            bip -s="0123456789AS SK"
 *
 * morseCode must end with a null pointer (watch for this if you modify it).
 */

char *morseCode[] = {
    "a a .-",
    "b b -...",
    "c c -.-.",
    "d d -..",
    "e e .",
    "f f ..-.",
    "g g --.",
    "h h ....",
    "i i ..",
    "j j .---",
    "k k -.-",
    "l l .-..",
    "m m --",
    "n n -.",
    "o o ---",
    "p p .--.",
    "q q --.-",
    "r r .-.",
    "s s ...",
    "t t -",
    "u u ..-",
    "v v ...-",
    "w w .--",
    "x x -..-",
    "y y -.--",
    "z z --..",
    "1 1 .----",
    "2 2 ..---",
    "3 3 ...--",
    "4 4 ....-",
    "5 5 .....",
    "6 6 -....",
    "7 7 --...",
    "8 8 ---..",
    "9 9 ----.",
    "0 0 -----",
    ". . .-.-.-",
    ", , --..--",
    "? ? ..--..",
    "\030 HH ........",
    "- - -....-",
    "= = -...-",
    ": : ---...",
    "; ; -.-.-.",
    "( ( -.--.",
    ") ) -.--.-",
    "/ / -..-.",
    "\" \" .-..-.",
    "$ $ ...-..-",
    "' ' .----.",
    "\n AL .-.-..",
    " AL .-.-..",
    "_ _ ..--.-",
    "\002 KA -.-.-",
    "\023 AS .-...",
    "+ + .-.-.",
    "! SK ...-.-",
    "\006 SN ...-.",
    "\005 INT ..-.-",
    "\021 HM ....--",
    "\022 IX ..-..-",
    "\001 TTTTT -----",
    "\024 SOS ...---...",
    "\026 DDD -..-..-..",

/* The following part of the encoding is only useful if you want to
   sent French accented characters as their non-accented counterparts.
   It is commented out by default because it is often incorrectly
   transmitted by E-mail (the 8th bit is stripped by many
   systems). Uncomment it if you desire.
*/

/*    "à a .-",
    "â a .-",
    "ç c -.-.",
    "é e .",
    "è e .",
    "ê e .",
    "î i ..",
    "ô o ---",
    "ù u ..-", */
    0
};

/*
 * $Log: bip.c,v $
 * Revision 1.10  1996/03/31  23:25:24  skubi
 * *** empty log message ***
 *
 * Revision 1.9  1996/03/30  17:21:27  skubi
 * *** empty log message ***
 *
 * Revision 1.7  1996/03/10  20:07:09  skubi
 * *** empty log message ***
 *
 * Revision 1.6  1996/03/10  18:37:24  skubi
 * *** empty log message ***
 *
 * Revision 1.5  1996/03/10  16:04:30  skubi
 * *** empty log message ***
 *
 * Revision 1.4  1996/03/03  10:04:13  skubi
 * *** empty log message ***
 *
 */
