home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
World of Ham Radio 1997
/
WOHR97_AmSoft_(1997-02-01).iso
/
cw
/
cw_17
/
prog
/
morse.c
< prev
next >
Wrap
C/C++ Source or Header
|
1997-02-01
|
110KB
|
3,573 lines
/************************************************************************/
/* */
/* File: morse.c */
/* */
/* Morse code keyboard and feedback training program for the IBM */
/* PC and compatible computers. */
/* */
/* This program supports the following modes of operation: */
/* */
/* 1. It will function as a morse code keyboard program using */
/* the computer's internal speaker as an output device. */
/* 2. It will read a specified input file and convert it to */
/* morse code, again using the internal speaker as the */
/* output device. */
/* 3. It will function as a "feedback" trainer for learning */
/* morse code by sending random code groups and making */
/* sure that the student types the corresponding character */
/* on the keyboard in response to the code sent. The */
/* "feedback" is flexible in that it can be made to */
/* automatically increase the code speed as accuracy */
/* improves, as well as concentrate on letters which are */
/* frequently missed. */
/* */
/* This program was written in Microsoft "C" version 3.0 and uses */
/* some functions which may be specific to that compiler, although */
/* equivalents should be easily constructed using other systems. */
/* The functions to watch out for are "intdos" which is used to */
/* make BIOS interrupt calls and the stuff associated with the */
/* "far" function parameter "key_ready" (such as "segread", see */
/* the function header documentation for "key_ready"). To compile */
/* this program using Microsoft "C" V3.0 type: */
/* */
/* cl /Ze morse.c */
/* */
/* Note the /Ze option flag which tells the compiler to enable the */
/* "far" keyword. If you plan on making extensive changes to the */
/* program I suggest that the source code be split up into smaller */
/* modules. I didn't do this to make distribution of the program */
/* easier. */
/* */
/* This program does make extensive use of IBM PC compatible ROM */
/* BIOS services, but should run on everything from a PC-JR */
/* through an AT and most of the compatibles. It also supports */
/* all the normal video board options (CGA,MDA,Hercules,EGA,PGA, */
/* etc.). PC-DOS or MS-DOS version 2.0 or later is assumed. */
/* */
/* Please contact me if you come up with any improvements (or find */
/* any problems), I can be reached at: */
/* */
/* Kirk Bailey, N7CCB */
/* Logical Systems */
/* P.O. Box 1702 */
/* Corvallis, OR 97339 */
/* */
/* Due to limited "HAM" activity time I can't distribute copies */
/* of this program myself. To fill this gap a local company has */
/* agreed to perform this function for a (relatively), nominal */
/* $25.00 fee. I've agreed to keep them updated with the latest */
/* and greatest version (standard IBM-PC 5.25" 360K floppy): */
/* */
/* "IBM-PC Morse Code Program" */
/* COMTEK INFORMATION SYSTEMS, INC. */
/* P.O. Box 3004-246 */
/* Corvallis, OR 97339 */
/* (503) 758-7003 */
/* */
/* Alternately, you can check local BBS/PBBS, friends, etc. */
/* */
/* PLEASE NOTE: THIS PROGRAM IS HEREBY PLACED IN THE PUBLIC */
/* DOMAIN (although I would like it if you kept my name and */
/* address in the code...) */
/* */
/* USAGE NOTES: The program should be self explanatory and */
/* includes a built-in HELP function, fire it up and get busy! */
/* After experimentation with the volume and frequency response of */
/* your specific computer's internal speaker you may wish to */
/* insert a volume control in the speaker line (which is usually */
/* simple since the speaker is connected to the computer via a two */
/* wire cable with a pin plug on the end). Unfortunately there is */
/* no way to control speaker volume from software (not counting */
/* PWM tricks which are EXTREMELY model dependent)! */
/* */
/************************************************************************/
#include <ctype.h> /* Character type include file */
#include <conio.h> /* Console I/O include file */
#include <dos.h> /* System interface include file */
#include <stdio.h> /* Standard include file */
/************************************************************************/
/* */
/* Global Definitions */
/* */
/************************************************************************/
typedef unsigned int BOOL; /* Value of TRUE or FALSE */
#define BS 0x08 /* Backspace character */
#define FALSE 0
#define FOREVER for(;;) /* while(1) equivalent */
#define TRUE 1
typedef unsigned int UINT; /* Value of 0-65535 */
#define VERSION "9/18/86" /* Last update */
/************************************************************************/
/* */
/* Configuration Parameters */
/* */
/************************************************************************/
#define MAX_CG_SIZE 9 /* Max. # chars in code group */
#define MAX_DELAY 99 /* Max. value of inter-char delay */
/*
* Code/speed adaptation configuration parameters.
*/
#define CODE_SAMPLE 50 /* # of groups/code adapt sample */
#define SLOW_DOWN 30 /* Cutoff error % to adapt to slower */
#define SPEED_SAMPLE 5 /* # of groups/speed adapt sample */
#define SPEED_UP 10 /* Cutoff error % to adapt to faster */
/*
* The following are the values returned by "get_key" and "peek_key" for
* the IBM PC function keys.
*/
#define FUNC_UP 0xFF48 /* Up arrow cursor key */
#define FUNC_LEFT 0xFF4B /* Left arrow cursor key */
#define FUNC_RIGHT 0xFF4D /* Right arrow cursor key */
#define FUNC_DOWN 0xFF50 /* Down arrow cursor key */
#define FUNC_F1 0xFF3B /* Function key F1 */
#define FUNC_F2 0xFF3C /* Function key F2 */
#define FUNC_F3 0xFF3D /* Function key F3 */
#define FUNC_F4 0xFF3E /* Function key F4 */
#define FUNC_F5 0xFF3F /* Function key F5 */
#define FUNC_F6 0xFF40 /* Function key F6 */
#define FUNC_F7 0xFF41 /* Function key F7 */
#define FUNC_F8 0xFF42 /* Function key F8 */
#define FUNC_F9 0xFF43 /* Function key F9 */
#define FUNC_F10 0xFF44 /* Function key F10 */
/*
* The following are the possible values for the "field_arg" component
* of the "setup_info" structure when it contains a selector switch.
*/
#define SELECT0 0 /* Pos. 0 of a selector field group */
#define SELECT1 1 /* Pos. 1 of a selector field group */
#define SELECT2 2 /* Pos. 2 of a selector field group */
#define SELECT3 3 /* Pos. 3 of a selector field group */
/*
* The following are the possible values of the "state" variable which
* indicates the current function of "morse" which is being used.
*/
#define MAINMENU 0 /* Messing with main menu screen */
#define KEYBOARD 1 /* Functioning as a morse keyboard */
#define FROMFILE 2 /* Dumping a file in morse */
#define TRAINER 3 /* Interactive training mode */
/************************************************************************/
/* */
/* Global Declarations */
/* */
/************************************************************************/
struct screen /* Holds text to be displayed */
{
UINT row; /* The row to begin the text */
UINT col; /* The column to begin the text */
char *text;
};
struct /* Holds screen info for SETUP frame */
{
BOOL field_used; /* TRUE if this field is used */
void (*field_func)(); /* Function to call for this field */
UINT field_arg; /* Argument for function */
} setup_info[25][4];
BOOL code_adapt = FALSE; /* Should missed codes be common? */
UINT code_group[MAX_CG_SIZE]; /* Hold contents of a code group */
int code_sent; /* In TRAINER mode, last code sent */
BOOL cursor_state; /* TRUE if ON, FALSE if OFF */
UINT delay = 0; /* Extra inter-char/word delay */
UINT enabled_codes; /* # enabled codes in TRAINER mode */
UINT frequency = 1000; /* Default tone oscillator freq. */
UINT group_size = 5; /* # chars in code group (0==random) */
BOOL had_error; /* TRUE if err on last TRAINER char */
FILE *in_fp; /* File pointer for reading files */
UINT max_speed = 1; /* 1 = 22WPM, 2 = 11WPM, .. 4 = 5WPM */
UINT old_cursor_end; /* Cursor size end at program start */
UINT old_cursor_start; /* Cursor size begin at prog. start */
UINT old_vmode; /* Video mode at program start */
union REGS inregs,outregs; /* System call register templates */
UINT sample_attempts; /* # of TRAINER chars sent/adapt */
UINT sample_errors; /* # of TRAINER char missed/adapt */
UINT speed_adapt = 0; /* 0-3 for rate of speed adaptation */
struct SREGS segregs; /* Segment register template */
UINT state; /* "morse" current mode */
BOOL (far *key_ready)(); /* A "far" ptr, see "key_ready" func */
/*
* The following variables are used by the various windowing routines.
*/
BOOL empty_window; /* TRUE if screen window empty */
BOOL full_window; /* TRUE if screen window full */
UINT cursor_row,cursor_col; /* Row/column for current cursor */
UINT cursor_field_col; /* Field column # (0-3) for cursor */
UINT insert_row,insert_col; /* Row/column for insertion point */
BOOL was_empty; /* Same as "empty_window", but for */
/* use internally in "send_window" */
/************************************************************************/
/* */
/* Morse Code Definitions */
/* */
/************************************************************************/
struct code /* Morse code <-> ASCII definitions */
{
BOOL enabled; /* TRUE if enabled for FILE/TRAINING */
BOOL fcc_rqrd; /* TRUE if FCC requires for amateurs */
UINT attempts; /* # of times sent/code adapt sample */
UINT errors; /* Error count/code adapt sample */
char *morse; /* String representation of code */
} codes[] =
{
{ FALSE,FALSE,0,0, NULL }, /* ' ' */
{ FALSE,FALSE,0,0, NULL }, /* '!' */
{ FALSE,FALSE,0,0, ".-..-."}, /* '"' */
{ FALSE,FALSE,0,0, ".-..." }, /* WAIT<->'#' */
{ FALSE,FALSE,0,0, "...-..-"}, /* '$' */
{ FALSE,FALSE,0,0, NULL }, /* '%' */
{ FALSE,FALSE,0,0, NULL }, /* '&' */
{ FALSE,FALSE,0,0, ".----."}, /* ''' */
{ FALSE,FALSE,0,0, "-.--." }, /* '(' */
{ FALSE,FALSE,0,0, "-.--.-"}, /* ')' */
{ FALSE,FALSE,0,0, NULL }, /* '*' */
{ TRUE,TRUE,0,0, ".-.-." }, /* '+' */
{ TRUE,TRUE,0,0, "--..--"}, /* ',' */
{ FALSE,FALSE,0,0, "-....-"}, /* '-' */
{ TRUE,TRUE,0,0, ".-.-.-"}, /* '.' */
{ TRUE,TRUE,0,0, "-..-." }, /* '/' */
{ TRUE,TRUE,0,0, "-----" }, /* '0' */
{ TRUE,TRUE,0,0, ".----" }, /* '1' */
{ TRUE,TRUE,0,0, "..---" }, /* '2' */
{ TRUE,TRUE,0,0, "...--" }, /* '3' */
{ TRUE,TRUE,0,0, "....-" }, /* '4' */
{ TRUE,TRUE,0,0, "....." }, /* '5' */
{ TRUE,TRUE,0,0, "-...." }, /* '6' */
{ TRUE,TRUE,0,0, "--..." }, /* '7' */
{ TRUE,TRUE,0,0, "---.." }, /* '8' */
{ TRUE,TRUE,0,0, "----." }, /* '9' */
{ FALSE,FALSE,0,0, "---..."}, /* ':' */
{ FALSE,FALSE,0,0, "-.-.-."}, /* ';' */
{ FALSE,FALSE,0,0, NULL }, /* '<' */
{ TRUE,TRUE,0,0, "-...-" }, /* '=' */
{ FALSE,FALSE,0,0, NULL }, /* '>' */
{ TRUE,TRUE,0,0, "..--.."}, /* '?' */
{ FALSE,FALSE,0,0, "...-." }, /* UNDERSTOOD<->'@' */
{ TRUE,TRUE,0,0, ".-" }, /* 'A' */
{ TRUE,TRUE,0,0, "-..." }, /* 'B' */
{ TRUE,TRUE,0,0, "-.-." }, /* 'C' */
{ TRUE,TRUE,0,0, "-.." }, /* 'D' */
{ TRUE,TRUE,0,0, "." }, /* 'E' */
{ TRUE,TRUE,0,0, "..-." }, /* 'F' */
{ TRUE,TRUE,0,0, "--." }, /* 'G' */
{ TRUE,TRUE,0,0, "...." }, /* 'H' */
{ TRUE,TRUE,0,0, ".." }, /* 'I' */
{ TRUE,TRUE,0,0, ".---" }, /* 'J' */
{ TRUE,TRUE,0,0, "-.-" }, /* 'K' */
{ TRUE,TRUE,0,0, ".-.." }, /* 'L' */
{ TRUE,TRUE,0,0, "--" }, /* 'M' */
{ TRUE,TRUE,0,0, "-." }, /* 'N' */
{ TRUE,TRUE,0,0, "---" }, /* 'O' */
{ TRUE,TRUE,0,0, ".--." }, /* 'P' */
{ TRUE,TRUE,0,0, "--.-" }, /* 'Q' */
{ TRUE,TRUE,0,0, ".-." }, /* 'R' */
{ TRUE,TRUE,0,0, "..." }, /* 'S' */
{ TRUE,TRUE,0,0, "-" }, /* 'T' */
{ TRUE,TRUE,0,0, "..-" }, /* 'U' */
{ TRUE,TRUE,0,0, "...-" }, /* 'V' */
{ TRUE,TRUE,0,0, ".--" }, /* 'W' */
{ TRUE,TRUE,0,0, "-..-" }, /* 'X' */
{ TRUE,TRUE,0,0, "-.--" }, /* 'Y' */
{ TRUE,TRUE,0,0, "--.." }, /* 'Z' */
{ FALSE,FALSE,0,0, "-.-.-" }, /* START<->'[' */
{ FALSE,FALSE,0,0, ".-.-.."}, /* PARAGRAPH<->'\' */
{ TRUE,TRUE,0,0, "...-.-"}, /* END<->']' */
{ FALSE,FALSE,0,0, "........"}, /* ERROR<->'^' */
{ FALSE,FALSE,0,0, "..--.-"} /* '_' */
};
struct index_codes /* Index structure into "codes" */
{
UINT base; /* The start of the "key" range */
UINT code; /* The ASCII code for the character */
UINT error_rate; /* Holds error rate percentage */
} index[sizeof(codes) / sizeof(struct code)];
/************************************************************************/
/* */
/* Function Declarations */
/* */
/************************************************************************/
void cursor_key();
void cursor_off();
void cursor_on();
void disable_all();
void do_code_adapt();
void do_speed_adapt();
void enable_all();
void enable_fcc();
void erase();
void erase_window();
void fill_setup_info();
void fill_window();
int get_key();
BOOL get_fname();
void help();
void init_keyboard();
void init_setup();
void init_sound();
void init_video();
char key_rdy[]; /* See "key_ready" function doc */
void keyboard();
void main();
void main_menu();
void morse_file();
int peek_key();
void position();
void put_window();
UINT read_char();
void read_position();
BOOL read_uint();
void restore_video();
void scroll_window();
void send();
void send_window();
void set_delay();
void set_freq();
void set_group_size();
void set_max_speed();
void set_speed_adapt();
void set_vmode();
void setup();
void show_attr();
void show_char();
void show_screen();
void show_speed();
void show_string();
void sign_on();
void simple_border();
void sound_off();
void sound_on();
void switch_selector();
void terminate();
void toggle_char();
void toggle_code_adapt();
void training();
void wait_tick();
/************************************************************************/
/* */
/* Routine: main */
/* */
/* Top level control for the program. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
struct screen intro_help[] = /* Introduction help display */
{
{1,31,"MORSE INTRODUCTION" },
{3,2,
"MORSE is a program designed to help you master the International Morse Code."},
{4,2,
"It's designed to run on IBM (and compatible), personal computers ranging"},
{5,2,
"from the PC-JR through the PC-AT. MORSE supports most video interfaces."},
{7,2,
"Mastery of the Morse Code is one of the two main requirements for obtaining"},
{8,2,
"an Amateur Radio license. The best way to get information about Amateur"},
{9,2,
"Radio is to talk to someone who is already licensed (sometimes called a"},
{10,2,
"\"ham\"). Most hams are more than willing to provide assistance. If you are"},
{11,2,
"unable to locate a suitable ham, the ARRL (American Radio Relay League), can"},
{12,2,
"help you contact someone, and also provide you with self-study material:"},
{14,31,"ARRL Headquarters"},
{15,34,"225 Main St."},
{16,30,"Newington, CT 06111"},
{17,33,"(203) 666-1541"},
{19,2,
"In addition to the ARRL material there are several excellent magazines which"},
{20,2,
"are devoted to ham activity. Two good ones are 73 AMATEUR RADIO (good"},
{21,2,
"general coverage), and HAM RADIO (for the technically inclined). These and"},
{22,2,
"others are available at many bookstores and Amateur Radio equipment outlets."},
{0,0,NULL }
};
void main()
{
int c;
init_keyboard(); /* Flush keyboard buffer out */
init_video(); /* Setup appropriate video mode */
init_sound(frequency); /* Setup sound generator */
sign_on(); /* Startup animation/titles */
position(24,12); /* Position to 25 line */
show_string(
"Type F1 for INFORMATION, F2 for MAIN MENU or F10 to QUIT");
FOREVER
{
while(! (c = get_key())) ; /* Wait for something */
switch(c) /* See what we are to do */
{
case FUNC_F1: /* Help screen */
help(intro_help);
case FUNC_F2: /* Main menu */
main_menu();
break;
case FUNC_F10: /* Terminate */
terminate();
default:
break;
}
}
}
/************************************************************************/
/* */
/* Routine: cursor_key */
/* */
/* Used by "setup" to position the cursor in response to cursor */
/* key inputs from the user. This routine uses the "setup_info" */
/* structure to determine which fields are used (and "cursor_key" */
/* assumes the cursor field position on entry is used). */
/* */
/* Parameters: "direction" - The cursor key to process. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void cursor_key(direction)
UINT direction;
{
UINT valid;
/*
* First translate the cursor movement key into the appropriate logical
* operation on the cursor fields used by the "setup" frame.
*/
valid = FALSE;
while(! valid)
{
switch(direction)
{
case FUNC_UP: /* Trying to go UP */
if(cursor_row > 0) cursor_row--;
else direction = FUNC_DOWN;
break;
case FUNC_DOWN: /* Trying to go DOWN */
if(cursor_row < 24) cursor_row++;
else direction = FUNC_UP;
break;
case FUNC_LEFT: /* Trying to go left */
if(cursor_field_col > 0) cursor_field_col--;
else direction = FUNC_RIGHT;
break;
case FUNC_RIGHT: /* Trying to go right */
if(cursor_field_col < 3) cursor_field_col++;
else
{
switch(cursor_row)
{
/* These two special cases are */ case 20:
/* intended to handle the two gaps in */ direction = FUNC_UP;
/* the "setup" display screen on the */ break;
/* right margin. Ideally, this should be */
/* be replaced with a generic */ case 21:
/* "find_closest" function which would */ direction = FUNC_DOWN;
/* operate with any display topology... */ break;
default:
direction = FUNC_LEFT;
break;
}
}
break;
default:
break;
}
if(setup_info[cursor_row][cursor_field_col].field_used)
valid = TRUE;
}
/*
* Now translate the field column # into a cursor column # (cursor
* row # requires no translation since a 1-to-1 mapping exists).
*/
cursor_col = (20 * cursor_field_col) + 3;
position(cursor_row,cursor_col); /* Position actual cursor */
}
/************************************************************************/
/* */
/* Routine: cursor_off */
/* */
/* Disable screen cursor. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void cursor_off()
{
if(old_vmode == 7) /* If MDA/Hercules */
{
inregs.h.ch = 12 | 32; /* Turn cursor OFF */
inregs.h.cl = 13;
}
else /* If CGA or other */
{
inregs.h.ch = 6 | 32; /* Turn cursor OFF */
inregs.h.cl = 7;
}
inregs.h.ah = 0x01; /* 0x01: Set cursor size */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
cursor_state = FALSE; /* Remember that it is off */
}
/************************************************************************/
/* */
/* Routine: cursor_on */
/* */
/* Enable screen cursor (default style). */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void cursor_on()
{
if(old_vmode == 7) /* If MDA/Hercules */
{
inregs.h.ch = 12;
inregs.h.cl = 13;
}
else /* If CGA or other */
{
inregs.h.ch = 6;
inregs.h.cl = 7;
}
inregs.h.ah = 0x01; /* 0x01: Set cursor size */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
cursor_state = TRUE; /* Remember that it is ON */
}
/************************************************************************/
/* */
/* Routine: disable_all */
/* */
/* Disable all morse code characters for use by the MORSE FILE or */
/* MORSE TRAINER modes. This function is usually used prior to re-*/
/* enabling a small set of characters. */
/* */
/* Parameters: "unused"- An unused argument. */
/* "c" - The actual character that was */
/* typed (must be CR to be valid). */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void disable_all(unused,c)
UINT unused,c;
{
UINT i,old_row,old_field_col,old_col;
if(c != '\r') return; /* Must be CR */
/*
* Remember original condition of cursor control variables.
*/
old_row = cursor_row;
old_field_col = cursor_field_col;
old_col = cursor_col;
cursor_off(); /* Turn cursor OFF */
/*
* Disable all morse code characters.
*/
cursor_row = 1; /* Start at row 1 */
cursor_field_col = 0; /* And first column of fields */
for(i = 0; i < (sizeof(codes) / sizeof(struct code)); i++)
{
if(codes[i].morse) /* If a valid morse character */
{
if(codes[i].enabled)
{
cursor_col = (cursor_field_col * 20) + 3;
toggle_char(i,'\r');
}
if(++cursor_field_col > 3)
{
cursor_field_col = 0; /* Wrap around */
cursor_row++;
}
}
}
/*
* Restore original condition of cursor control variables.
*/
cursor_row = old_row;
cursor_field_col = old_field_col;
cursor_col = old_col;
position(cursor_row,cursor_col);
cursor_on(); /* Turn cursor ON */
}
/************************************************************************/
/* */
/* Routine: do_code_adapt */
/* */
/* Used by "trainer" to automatically present the characters which */
/* are missed most, more often. This is done by taking the error */
/* rate percentage over a sample interval and making the chances */
/* of the same character appearing in the next interval be */
/* proportional to the previous error rate percentage. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void do_code_adapt()
{
UINT i,j,k,total_count;
if(! enabled_codes) return; /* No randomness here! */
/*
* First scan all the enabled characters and accumulate the sum of the
* various error rate percentages (no character gets less than 30% even if
* the accuracy has been perfect). If the character wasn't sent in the
* previous sample interval raise its percentage to 50% to encourage the
* random # generator.
*/
for(total_count = i = 0; i < enabled_codes; i++)
{
j = index[i].code - ' ';
if(! codes[j].attempts) k = 50;
else
{
k = (UINT) ((codes[j].errors * 100L) /
codes[j].attempts);
if(k < 30) k = 30;
}
codes[j].errors = codes[j].attempts = 0;
index[i].error_rate = k; /* Remember for next loop */
total_count += k;
}
/*
* Now we go through the "index" data structure and adjust the odds
* based on the percentages we have calculated.
*/
for(j = i = 0; i < enabled_codes; i++)
{
index[i].base = (UINT) ((j * 32767L) / total_count);
j += index[i].error_rate;
}
}
/************************************************************************/
/* */
/* Routine: do_speed_adapt */
/* */
/* Used by "trainer" to automatically adjust the inter-character */
/* and inter-word delay based on the error rate in the last sample */
/* of code groups. The amount the "delay" is adjusted is based */
/* on the "speed_adapt" variable and ranges from 0 to 3 units (55 */
/* milliseconds apiece). */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void do_speed_adapt()
{
UINT error_percentage;
error_percentage = (sample_errors * 100) / sample_attempts;
sample_errors = sample_attempts = 0;
if(error_percentage > SLOW_DOWN)
{
delay += speed_adapt;
if(delay > MAX_DELAY) delay = MAX_DELAY;
}
else if(error_percentage < SPEED_UP)
{
if(delay >= speed_adapt) delay -= speed_adapt;
else delay = 0;
}
show_speed(); /* Update speed/delay display */
}
/************************************************************************/
/* */
/* Routine: enable_all */
/* */
/* Enable all morse code characters for use by the MORSE FILE or */
/* MORSE TRAINER modes. */
/* */
/* Parameters: "unused"- An unused argument. */
/* "c" - The actual character that was */
/* typed (must be CR to be valid). */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void enable_all(unused,c)
UINT unused,c;
{
UINT i,old_row,old_field_col,old_col;
if(c != '\r') return; /* Must be CR */
/*
* Remember original condition of cursor control variables.
*/
old_row = cursor_row;
old_field_col = cursor_field_col;
old_col = cursor_col;
cursor_off(); /* Turn cursor OFF */
/*
* Enable all morse code characters.
*/
cursor_row = 1; /* Start at row 1 */
cursor_field_col = 0; /* And first column of fields */
for(i = 0; i < (sizeof(codes) / sizeof(struct code)); i++)
{
if(codes[i].morse) /* If a valid morse character */
{
if(! codes[i].enabled)
{
cursor_col = (cursor_field_col * 20) + 3;
toggle_char(i,'\r');
}
if(++cursor_field_col > 3)
{
cursor_field_col = 0; /* Wrap around */
cursor_row++;
}
}
}
/*
* Restore original condition of cursor control variables.
*/
cursor_row = old_row;
cursor_field_col = old_field_col;
cursor_col = old_col;
position(cursor_row,cursor_col);
cursor_on(); /* Turn cursor ON */
}
/************************************************************************/
/* */
/* Routine: enable_fcc */
/* */
/* Enable the subset of morse code characters which is required */
/* for the FCC amateur radio operator examinations. */
/* */
/* Parameters: "unused"- An unused argument. */
/* "c" - The actual character that was */
/* typed (must be CR to be valid). */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void enable_fcc(unused,c)
UINT unused,c;
{
UINT i,old_row,old_field_col,old_col;
if(c != '\r') return; /* Must be CR */
/*
* Remember original condition of cursor control variables.
*/
old_row = cursor_row;
old_field_col = cursor_field_col;
old_col = cursor_col;
cursor_off(); /* Turn cursor OFF */
/*
* Enable the morse code subset which the FCC requires for amateur tests.
*/
cursor_row = 1; /* Start at row 1 */
cursor_field_col = 0; /* And first column of fields */
for(i = 0; i < (sizeof(codes) / sizeof(struct code)); i++)
{
if(codes[i].morse) /* If a valid morse character */
{
if(codes[i].fcc_rqrd)
{
if(! codes[i].enabled)
{
cursor_col = (cursor_field_col * 20)+3;
toggle_char(i,'\r');
}
}
else
{
if(codes[i].enabled)
{
cursor_col = (cursor_field_col * 20)+3;
toggle_char(i,'\r');
}
}
if(++cursor_field_col > 3)
{
cursor_field_col = 0; /* Wrap around */
cursor_row++;
}
}
}
/*
* Restore original condition of cursor control variables.
*/
cursor_row = old_row;
cursor_field_col = old_field_col;
cursor_col = old_col;
position(cursor_row,cursor_col);
cursor_on(); /* Turn cursor ON */
}
/************************************************************************/
/* */
/* Routine: erase */
/* */
/* Erase the video screen. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void erase()
{
position(0,0); /* Position cursor at origin */
show_char(' ',(25 * 80)); /* Erase screen */
}
/************************************************************************/
/* */
/* Routine: erase_window */
/* */
/* Erase the top 24 lines of the video screen. Initializes the */
/* global variables used to handle the screen windowing. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void erase_window()
{
position(0,0); /* Position cursor at origin */
cursor_on(); /* And turn it on */
show_attr(' ',(24 * 80),0x07); /* Erase window, set attribute */
empty_window = TRUE; /* Window is completely empty */
was_empty = TRUE; /* Like above ("send_window" only) */
full_window = FALSE; /* Window is not completely full */
insert_row = insert_col = cursor_row = cursor_col = 0;
}
/************************************************************************/
/* */
/* Routine: fill_setup_info */
/* */
/* Fill a field entry in the "setup_info" data structure. */
/* */
/* Parameters: "row" - The entry row #. */
/* "col" - The entry field column #. */
/* "func" - The "field_func" component. */
/* "arg" - The "field_arg" component. */
/* "title" - A title to display. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void fill_setup_info(row,col,func,arg,title)
UINT row,col;
void (*func)();
UINT arg;
char *title;
{
setup_info[row][col].field_used = TRUE;
setup_info[row][col].field_func = func;
setup_info[row][col].field_arg = arg;
position(row,(col * 20) + 6); show_string(title);
}
/************************************************************************/
/* */
/* Routine: fill_window */
/* */
/* Called by various routines to read input characters, eliminate */
/* those which are inappropriate, and call "put_window" to write */
/* them to the screen. If the current "state" is TRAINER, this */
/* function does nothing. This routine is coroutine with routine */
/* "send_window" */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void fill_window()
{
int c;
switch(state) /* Figure out how we are to behave */
{
case TRAINER: /* If in interactive training mode */
if(! (c = peek_key())) return; /* If nought to read */
if(c == FUNC_F9) /* Skip character? */
{
get_key(); /* Remove from buffer */
if(code_sent) /* Awaiting an echo? */
{
had_error = TRUE;
c = code_sent; /* Matches anything */
code_sent = NULL;
break;
}
else return; /* Flush bogus function key */
}
if(c < 0) return; /* We have a function key */
c = toupper(c); /* Force uppercase */
get_key(); /* Remove key from buffer */
if(code_sent) /* If waiting for user echo */
{
if(c == code_sent) code_sent = NULL;
else
{
had_error = TRUE;
c = 0xB1; /* Wrong character */
}
}
else return; /* Flush extra bogus characters */
break;
case KEYBOARD: /* If used as a morse code keyboard */
if(! (c = peek_key())) return; /* If nought to read */
if(c < 0) return; /* If a function key */
get_key(); /* Remove key from buffer */
if(full_window) return; /* If no room */
if(c == BS)
{
put_window(c);
return;
}
if(isspace(c)) /* If whitespace */
{
if(c == '\r') put_window('\n');
else put_window(' ');
return;
}
c = toupper(c); /* Force uppercase */
if((c < ' ') || (c > '_')) return; /* Toss junk */
if(! *codes[c - ' '].morse) return; /* Toss junk */
break;
case FROMFILE: /* If dumping a file in morse code */
if(full_window) return; /* If no room */
if((c = fgetc(in_fp)) == EOF) return; /* If EOF */
if(isspace(c)) /* If whitespace */
{
if(c == '\r') return;
if(c == '\n') put_window('\n');
else put_window(' ');
return;
}
c = toupper(c); /* Force uppercase */
if((c < ' ') || (c > '_')) return; /* Toss junk */
/*
* Note that the following statement only allows characters which are
* explicitly "enabled" to appear on the screen (and hence be sent).
*/
if(! codes[c - ' '].enabled) return;
break;
case MAINMENU: /* Can't be, but maybe in future */
return;
default:
return;
}
put_window(c); /* Put future morse code on screen */
}
/************************************************************************/
/* */
/* Routine: get_key */
/* */
/* Read a character from the keyboard and return the value read. */
/* If no key has been pressed return zero, otherwise return a */
/* positive ASCII value if it is a regular key, or the contents */
/* of the "Auxiliary-Byte" value if it is a "special" key (such as */
/* function and other IBM specific keys). The top eight bits of */
/* return value will be ones if it was a "special" key (ie. the */
/* the return value will be negative). Uses ROM BIOS service 0x16.*/
/* */
/* Parameters: None. */
/* */
/* Returns: An integer holding the key read if non-zero. */
/* */
/************************************************************************/
int get_key()
{
/*
* See if a character is waiting.
*/
if(! (*key_ready)()) return 0; /* If nothing to read */
inregs.h.ah = 0x00; /* 0x00: Read next keyboard char */
int86(0x16,&inregs,&outregs); /* Interrupt 0x16 */
if(outregs.h.al) return (outregs.h.al & 0x00FF);
/*
* We have a "special key", return its value.
*/
return (outregs.h.ah | 0xFF00);
}
/************************************************************************/
/* */
/* Routine: get_fname */
/* */
/* Prompt the user for an input filename to open. This file is */
/* translated by "morse_file" into morse code output. This */
/* function returns TRUE if a file was successfully opened, FALSE */
/* if a function key was pressed. */
/* */
/* Parameters: None. */
/* */
/* Returns: TRUE if a file was opened, FALSE otherwise. */
/* */
/************************************************************************/
BOOL get_fname()
{
BOOL had_error;
char buf[81];
int c;
UINT col;
position(10,0);
show_string(
"Please type the pathname of the file to be converted to Morse Code. The name");
position(11,0);
show_string(
"should be terminated with a carriage return, (the ENTER key on some keyboards):");
had_error = FALSE; /* Assume OK until proven otherwise */
FOREVER
{
if(had_error) /* Leave error up until key hits */
{
while(peek_key() == 0) ;
had_error = FALSE;
}
position(12,0); /* Position to start of prompt line */
show_attr(' ',80,0x70); /* Blank out rev. video prompt line */
position(13,0);
show_char(' ',80); /* Blank out error message */
col = 0;
FOREVER
{
if((c = peek_key()) < 0) return FALSE;
if(c == 0) continue;
get_key(); /* Remove character from buffer */
if(c == '\r')
{
buf[col] = NULL; /* Terminate string */
if((in_fp = fopen(buf,"r")) == NULL)
{
position(13,0);
show_string(
"The above file pathname doesn't exist as it was typed, please try re-typing it!");
had_error = TRUE;
break;
}
else return TRUE;
}
if(c == BS) /* Handle backspace processing */
{
if(col > 0) col--; /* Backup one */
position(12,col);
show_char(' ',1);
continue;
}
if(col > 79)
{
position(13,0);
show_string(
"The file pathname must be 80 or less characters long, please try re-typing it!");
had_error = TRUE;
break;
}
buf[col] = c; /* Put character in buffer */
position(12,col++); /* Position for display */
show_char(c,1); /* Display character on screen */
}
}
}
/************************************************************************/
/* */
/* Routine: help */
/* */
/* Presents a help screen to the user and waits until a key is */
/* pressed before returning. Terminates the program if F10 is */
/* pressed. */
/* */
/* Parameters: "list" - Points to an array of "screen" */
/* structures. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void help(list)
struct screen list[];
{
UINT c;
cursor_off(); /* Won't need a cursor here */
erase(); /* Erase screen */
position(24,19); /* Position to command line */
show_string("Type any key to continue (or F10 to QUIT)");
simple_border(); /* Draw border on screen */
show_screen(list); /* Put text on screen */
while(! (c = get_key())) ; /* Wait for a character to be typed */
if(c == FUNC_F10) terminate(); /* Thats' all folks... */
}
/************************************************************************/
/* */
/* Routine: init_keyboard */
/* */
/* Setup the value of the character-ready "far" function pointer */
/* ("key_ready"), so that it points at the appropriate inline */
/* assembly language routine in array "key_rdy". See the */
/* "key_ready" function section for more info. Also flushes the */
/* keyboard type ahead buffer. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void init_keyboard()
{
segread(&segregs); /* Read values of segment registers */
*((UINT *) &key_ready + 1) = segregs.ds; /* Set segment part of addr */
*((UINT *) &key_ready) = (UINT) key_rdy; /* Set offset part of addr */
while(get_key()) ; /* Suck buffer until nothing left */
}
/************************************************************************/
/* */
/* Routine: init_setup */
/* */
/* Initialize the "setup_info" data structure used by "setup" to */
/* display and parse configuration options. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void init_setup()
{
char buf[81];
UINT i;
/*
* Draw borders on SETUP screen.
*/
position(0,0); show_char(0xC9,1);
position(0,1); show_char(0xCD,78);
position(0,79); show_char(0xBB,1);
for(cursor_row = 1; cursor_row < 16; cursor_row++)
{
position(cursor_row,0); show_char(0xBA,1);
position(cursor_row,79); show_char(0xBA,1);
}
position(16,0); show_char(0xCC,1);
position(16,1); show_char(0xCD,18);
position(16,19); show_char(0xCB,1);
position(16,20); show_char(0xCD,19);
position(16,39); show_char(0xCB,1);
position(16,40); show_char(0xCD,19);
position(16,59); show_char(0xCB,1);
position(16,60); show_char(0xCD,19);
position(16,79); show_char(0xB9,1);
for(cursor_row = 17; cursor_row < 24; cursor_row++)
{
position(cursor_row,0); show_char(0xBA,1);
if(cursor_row != 20)
{
position(cursor_row,19); show_char(0xBA,1);
position(cursor_row,39); show_char(0xBA,1);
position(cursor_row,59); show_char(0xBA,1);
position(cursor_row,79); show_char(0xBA,1);
}
}
position(20,19); show_char(0xCC,1);
position(20,39); show_char(0xB9,1);
position(20,20); show_char(0xCD,19);
position(20,59); show_char(0xCC,1);
position(20,79); show_char(0xB9,1);
position(20,60); show_char(0xCD,19);
/*
* Initialize all the fields to unused and then selectively relax.
*/
for(cursor_row = 0; cursor_row < 25; cursor_row++)
{
for(cursor_field_col = 0; cursor_field_col < 4;
cursor_field_col++)
{
setup_info[cursor_row][cursor_field_col].field_used =
FALSE;
}
}
/*
* First initialize the morse code character portion of the struct/screen.
*/
cursor_row = 1; /* Start at row 1 */
cursor_field_col = 0; /* And first column of fields */
for(i = 0; i < (sizeof(codes) / sizeof(struct code)); i++)
{
if(codes[i].morse) /* If a valid morse character */
{
buf[0] = i + ' '; /* Show ASCII translation */
buf[2] = buf[1] = ' ';
strcpy(&buf[3],codes[i].morse);
fill_setup_info(cursor_row,cursor_field_col,
toggle_char,i,buf);
cursor_col = (20 * cursor_field_col) + 3;
toggle_char(i,'\r'); toggle_char(i,'\r');
if(++cursor_field_col > 3)
{
cursor_field_col = 0; /* Wrap around */
cursor_row++;
}
}
}
/*
* Initialize the enable/disable (all) selectors for the morse code chars.
*/
position(cursor_row,(20 * cursor_field_col) + 3); show_char(0x1A,1);
fill_setup_info(cursor_row,cursor_field_col++,enable_fcc,NULL,
"ENABLE-FCC");
position(cursor_row,(20 * cursor_field_col) + 3); show_char(0x1A,1);
fill_setup_info(cursor_row,cursor_field_col++,enable_all,NULL,
"ENABLE-ALL");
position(cursor_row,(20 * cursor_field_col) + 3); show_char(0x1A,1);
fill_setup_info(cursor_row,cursor_field_col,disable_all,NULL,
"DISABLE-ALL");
/*
* Initialize the speed adaptation selector switch.
*/
position(17,2); show_string("SPEED ADAPTATION");
fill_setup_info(19,0,set_speed_adapt,SELECT0,"NO ADAPTATION");
fill_setup_info(20,0,set_speed_adapt,SELECT1,"ADAPT SLOW");
fill_setup_info(21,0,set_speed_adapt,SELECT2,"ADAPT MEDIUM");
fill_setup_info(22,0,set_speed_adapt,SELECT3,"ADAPT FAST");
cursor_row = 19 + speed_adapt;
position(cursor_row,(20 * 0) + 3); show_char(0x10,1);
/*
* Initialize the code group size selection box.
*/
position(17,22); show_string("CODE GROUP SIZE");
position(18,23); show_string("(0 = RANDOM)");
fill_setup_info(19,1,set_group_size,NULL,"CHARACTERS");
position(cursor_row = 19,cursor_col = (20 * 1) + 3);
read_uint(group_size,&i,1,'\r');
/*
* Initialize the code adaptation toggle switch.
*/
position(21,22); show_string("CODE ADAPTATION");
fill_setup_info(22,1,toggle_code_adapt,NULL,NULL);
position(cursor_row = 22,cursor_col = (20 * 1) + 3);
toggle_code_adapt(NULL,'\r'); toggle_code_adapt(NULL,'\r');
/*
* Initialize the maximum code speed selector switch.
*/
position(17,42); show_string("MAX. CODE SPEED");
fill_setup_info(19,2,set_max_speed,SELECT0,"22 WORDS/MIN");
fill_setup_info(20,2,set_max_speed,SELECT1,"11 WORDS/MIN");
fill_setup_info(21,2,set_max_speed,SELECT2," 7 WORDS/MIN");
fill_setup_info(22,2,set_max_speed,SELECT3," 5 WORDS/MIN");
cursor_row = 19 + max_speed - 1;
position(cursor_row,(20 * 2) + 3); show_char(0x10,1);
/*
* Initialize delay selection box.
*/
position(17,62); show_string("CODE SPEED DELAY");
position(18,63); show_string(".055 SECS/UNIT");
fill_setup_info(19,3,set_delay,NULL," UNITS");
position(cursor_row = 19,cursor_col = (20 * 3) + 3);
read_uint(delay,&i,2,'\r'); /* Set initial value */
/*
* Initialize the oscillator frequency selection box.
*/
position(21,62); show_string("OUTPUT FREQUENCY");
fill_setup_info(22,3,set_freq,NULL," HERTZ");
position(cursor_row = 22,cursor_col = (20 * 3) + 3);
read_uint(frequency,&i,4,'\r'); /* Set initial value */
}
/************************************************************************/
/* */
/* Routine: init_sound */
/* */
/* Program the 8253 sound chip to produce the specified tone. */
/* */
/* Parameters: "freq" - The desired frequency in hertz. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void init_sound(freq)
UINT freq;
{
long count;
if(! freq) freq = 1; /* Avoid divide by zero */
count = 1193180L / freq;
if(count < 1L) count = 1L;
else if(count > 65535L) count = 65535L;
outp(0x43,0xB6); /* Preamble byte to 8253 (port 0x43) */
outp(0x42,count & 0xFFL); /* Low order byte of count */
outp(0x42,(count >> 8) & 0xFFL); /* high order byte of count */
}
/************************************************************************/
/* */
/* Routine: init_video */
/* */
/* Initialize the video display to text-only, 80 * 25 characters. */
/* If the currently active video display is a monochrome adapter */
/* (or Hercules compatible), leave the display mode alone, */
/* otherwise change the video display mode to 2 (for CGA, EGA and */
/* PC-JR compatibility). The old contents of the display mode */
/* are preserved for later restoration. Also, sets the active */
/* video page to be zero and erases it. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void init_video()
{
/*
* Get current video mode.
*/
inregs.h.ah = 0x0F; /* 0x0F: Get current video mode */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
old_vmode = outregs.h.al; /* Old video mode */
/*
* Get current cursor size.
*/
inregs.h.ah = 0x03; /* 0x03: Read cursor position */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
old_cursor_start = outregs.h.ch;/* Starting line of old cursor */
old_cursor_end = outregs.h.cl; /* Ending line of old cursor */
/*
* Set new video mode.
*/
if(old_vmode != 7) /* If not MDA/Hercules */
{
set_vmode(2); /* 80*25 color suppressed */
/*
* Set current page # to be 0.
*/
inregs.h.ah = 0x05; /* 0x05: Set active display page */
inregs.h.al = 0; /* Make it be page zero */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
}
/*
* Time to erase screen and set initial attribute (normal white on black).
*/
position(0,0); /* Position screen to origin */
show_attr(' ',(80 * 25),0x07); /* Erase screen and set attribute */
cursor_off(); /* And turn cursor off */
}
/************************************************************************/
/* */
/* Routine: key_ready */
/* */
/* The character array "key_rdy" is an inline subroutine which is */
/* called by the "far" function pointer "key_ready". This was */
/* done for the following reasons: */
/* */
/* 1. Microsoft "C" provides good runtime BIOS interface */
/* routines which are used extensively in this program */
/* ("intdos"), but these routines provide no way to get at */
/* the zero flag returned by the BIOS service. Normally */
/* this isn't a problem since only one BIOS service */
/* requires this: "Report Whether Character Ready". Of */
/* course, I ended up needing this service! Thus I had to */
/* write an assembly language interface routine. */
/* */
/* 2. Given the above requirements the normal approach is to */
/* write the assembly language routine, assembly it and */
/* link it into the main "C" program (which is what I */
/* initially did). The problem is that this forces anyone */
/* who wishes to change the program to also have a */
/* relatively expensive assembler in addition to the not */
/* inexpensive "C" compiler. My solution was to place the */
/* assembler code INSIDE the "C" program in a character */
/* array, and then point a function pointer at the array. */
/* */
/* 3. The problem with the function pointer is that the 8086 */
/* architecture is hobbled by what are termed "segments". */
/* The result is that code and data in "C" programs */
/* running on these machines end up in different segments */
/* (which makes putting a function (code), in a array */
/* (data) impossible). The work around for this was to */
/* make the function pointer be a "far" pointer which */
/* allows a new code segment to be donned when the */
/* function call happens. The "new" code segment in the */
/* pointer is then initialized to be the "old" data */
/* segment and everything works like magic. The "C" code */
/* which initializes the function pointer is located in */
/* function "init_keyboard". */
/* */
/* 4. A warning... While the above trick does work it is not */
/* in the spirit of "C" to muddy the waters between code */
/* and data distinctions! I justified it this time based */
/* on economic factors (for a presumably "amateur" */
/* audience). This is not the sort of thing that should */
/* be done for "professional" level activities. */
/* */
/* 5. If you are converting this to a "C" compiler which does */
/* not have "far" pointers, but DOES allow the code and */
/* data segments to be made the same (ie. produce ".COM" */
/* output files), you can remove the "far" stuff and just */
/* use a regular function pointer. Another option would */
/* be to use a "kbhit" style function provided by your "C" */
/* compiler to do the same job, but I experienced */
/* interaction between the version I had and some of the */
/* direct BIOS keyboard manipulation that I do. */
/* */
/* Parameters: None. */
/* */
/* Returns: TRUE if a character is available, FALSE */
/* otherwise. */
/* */
/************************************************************************/
char key_rdy[] =
{
/* key_ready proc far */
0x55, /* push bp */
0x8B,0xEC, /* mov bp,sp */
0x57, /* push di */
0x56, /* push si */
0xB4,0x01, /* mov ah,1 */
0xCD,0x16, /* int 016h */
0x74,0x05, /* jz short empty */
0xB8,0x00,0x01, /* mov ax,1 */
0xEB,0x03, /* jmp short end */
/* empty: */
0xB8,0x00,0x00, /* mov ax,0 */
/* end: */
0x5E, /* pop si */
0x5F, /* pop di */
0x8B,0xE5, /* mov sp,bp */
0x5D, /* pop bp */
0xCB /* ret */
/* key_ready endp */
};
/************************************************************************/
/* */
/* Routine: keyboard */
/* */
/* Do the morse code keyboard stuff. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
struct screen keyboard_help[] = /* Morse code keyboard help display */
{
{1,31,"CODE KEYBOARD HELP" },
{3,2,
"This function allows you to enter ASCII characters from the keyboard which"},
{4,2,
"are converted into Morse Code and \"sent\" using the internal speaker."},
{5,2,
"Unlike the CODE FILE and CODE TRAINING functions, any character for which a"},
{6,2,
"a Morse Code equivalent exists may be sent, whether or not it is enabled."},
{8,2,
"Since some Morse Codes have no direct ASCII equivalents, ASCII characters"},
{9,2,
"have been chosen to represent them for the purposes of the MORSE program."},
{10,2,
"See the SETUP function for the specific ASCII to Morse conversions which are"},
{11,2,
"used. The SETUP function may also be used to control the Morse Code speed"},
{12,2,
"and oscillator frequency."},
{14,2,
"You are allowed to type-ahead up to a full screen worth of characters. The"},
{15,2,
"cursor will always highlight the character which is currently being sent."},
{17,2,
"You may use the space bar and carriage return key (called ENTER on some"},
{18,2,
"keyboards), to organize the text you are entering. The backspace key may"},
{19,2,
"also be used to erase previously entered text which is on the current line"},
{20,2,
"and has not yet been sent."},
{0,0,NULL }
};
void keyboard()
{
int c;
state = KEYBOARD; /* Current program activity */
FOREVER
{
erase(); /* Erase screen */
position(24,2); /* Position to 25 line */
show_string(
"[CODE KEYBOARD] F1:HELP F2:MAIN MENU F3:SETUP F10:QUIT");
show_speed(); /* Show current speed/delay */
erase_window(); /* Erase/init scroll window */
FOREVER
{
fill_window(); /* Buffer input if any */
if((c = peek_key()) < 0) /* See if a func key */
{
get_key(); /* If so, flush it out */
switch(c) /* And see what to do */
{
case FUNC_F1: /* Help info */
help(keyboard_help);
break;
case FUNC_F2: /* Main menu */
return;
case FUNC_F3: /* Setup */
setup();
break;
case FUNC_F10: /* Quit */
terminate();
default:
break;
}
break;
}
else send_window(); /* Otherwise, send code! */
}
}
}
/************************************************************************/
/* */
/* Routine: main_menu */
/* */
/* Display the main menu and ask what to do. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
struct screen m_menu[] = /* Menu display (dup command line) */
{
{1,32,"MORSE MAIN MENU" },
{3,15,"Please choose one of the following function keys:"},
{5,2,
"F1 HELP This function gets information about using the MORSE program."},
{6,16,
"The HELP key works anywhere and provides information"},
{7,16,
"appropriate to the function being used." },
{9,2,
"F3 SETUP This function controls parameters such as the allowed Morse"},
{10,16,
"Code characters, the code speed and the oscillator frequency."},
{12,2,
"F4 KEYBOARD This function allows you to enter characters from the keyboard"},
{13,16,
"and have them automatically converted to Morse Code and sent."},
{15,2,
"F5 FILE This function prompts you for the name of a text file which is"},
{16,16,
"then read and converted to Morse Code."},
{18,2,
"F6 TRAINING This function sends random Morse Code groups which are echoed"},
{19,16,
"in turn by typing the ASCII equivalents on the keyboard."},
{21,2,
"F10 QUIT This function key immediately terminates the MORSE program and"},
{22,16,
"returns control to DOS. Like HELP, QUIT works anywhere."},
{0,0,NULL }
};
struct screen m_m_help[] = /* Main menu help display */
{
{1,30,"MORSE MAIN MENU HELP" },
{3,2,
"You select what you want to do by pressing the function key corresponding to"},
{4,2,
"the listed activity. For the MAIN MENU your choices are also listed across"},
{5,2,
"the bottom of the screen in a shorthand format. For the remainder of the"},
{6,2,
"MORSE program your choices are ONLY listed on the bottom of the screen!"},
{8,2,
"Please contact me if you come up with any improvements (or find problems)."},
{9,2,"I can be reached at:"},
{10,31,"KIRK BAILEY, N7CCB"},
{11,33,"P.O. Box 1702"},
{12,30,"Corvallis, OR 97339"},
{14,2,
"Due to limited time I can't distribute copies of MORSE myself. Since it is"},
{15,2,
"PUBLIC DOMAIN you may be able to get the program and source code (Microsoft"},
{16,2,
"\"C\"), from a friend or BBS/PBBS, etc. If all else fails you may obtain the"},
{17,2,
"latest version for $25 (IBM-PC format 5.25\", 360K floppy), from:"},
{19,24,"COMTEK INFORMATION SYSTEMS, INC."},
{20,31,"P.O. Box 3004-246"},
{21,30,"Corvallis, OR 97339"},
{22,33,"(503) 758-7003"},
{0,0,NULL }
};
void main_menu()
{
int c;
FOREVER
{
state = MAINMENU; /* Current program state */
cursor_off(); /* Turn cursor off */
erase(); /* Erase screen */
simple_border(); /* Draw border */
show_screen(m_menu); /* Display menu */
position(24,1); /* Position to 25 line */
show_string(
"F1:HELP F3:SETUP F4:CODE KEYBOARD F5:CODE FILE F6:CODE TRAINING F10:QUIT");
FOREVER
{
while(! (c = get_key())) ; /* Wait for something */
switch(c) /* See what we are to do */
{
case FUNC_F1: /* Help screen */
help(m_m_help);
break;
case FUNC_F3: /* Configure program */
setup();
break;
case FUNC_F4: /* Morse keyboard */
keyboard();
break;
case FUNC_F5: /* Morse file */
morse_file();
break;
case FUNC_F6: /* Morse training */
training();
break;
case FUNC_F10: /* Terminate */
terminate();
default:
continue;
}
break;
}
}
}
/************************************************************************/
/* */
/* Routine: morse_file */
/* */
/* Translate an input file into morse code. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
struct screen file_help[] = /* Morse code file help display */
{
{1,33,"CODE FILE HELP" },
{3,2,
"This function asks you for a pathname for an ASCII file to be read,"},
{4,2,
"converted into Morse Code, and \"sent\" using the internal speaker. The"},
{5,2,
"characters which are converted are limited to those which have Morse Code"},
{6,2,
"equivalents AND which are \"enabled\". The SETUP (F3), function may be used"},
{7,2,
"to modify which characters are enabled. Characters which cannot be"},
{8,2,
"converted are ignored in the ASCII file."},
{10,2,
"Since some Morse Codes have no direct ASCII equivalents, ASCII characters"},
{11,2,
"have been chosen to represent them for the purposes of the MORSE program."},
{12,2,
"See the SETUP function for the specific ASCII to Morse conversions which are"},
{13,2,
"used. The SETUP function may also be used to control the Morse Code speed"},
{14,2,
"and oscillator frequency."},
{16,2,
"When the ASCII file has been completely converted and sent you are prompted"},
{17,2,
"for another ASCII file pathname to be converted and sent."},
{0,0,NULL }
};
void morse_file()
{
BOOL file_open;
int c;
state = FROMFILE; /* Currently sending a file as morse */
FOREVER
{
cursor_off(); /* Turn cursor off */
erase(); /* Erase screen */
position(24,2); /* Position to 25 line */
show_string(
"[CODE FILE] F1:HELP F2:MAIN MENU F3:SETUP F10:QUIT");
show_speed(); /* Show current speed/delay */
file_open = get_fname(); /* Read desired filename */
erase_window(); /* Erase/init scroll window */
FOREVER
{
if((c = get_key()) < 0) /* See if a func key */
{
switch(c) /* And see what to do */
{
case FUNC_F1: /* Help info */
help(file_help);
break;
case FUNC_F2: /* Main menu */
if(file_open) fclose(in_fp);
return;
case FUNC_F3: /* Setup */
setup();
break;
case FUNC_F10: /* Quit */
terminate();
default:
break;
}
break;
}
fill_window(); /* Buffer input if any */
send_window(); /* And send code! */
if(empty_window) /* See if we are done */
{
if((c = fgetc(in_fp)) == EOF) break;
else ungetc(c,in_fp); /* Put char back */
}
}
if(file_open) fclose(in_fp); /* Close input file */
}
}
/************************************************************************/
/* */
/* Routine: peek_key */
/* */
/* Like "get_key", except the character is just looked at, not */
/* actually removed from the ROM BIOS character buffer. */
/* */
/* Parameters: None. */
/* */
/* Returns: An integer holding the next key to be read if */
/* one exists, otherwise zero (note special keys */
/* are reported as negative #'s as in "get_key"). */
/* */
/************************************************************************/
int peek_key()
{
/*
* See if a character is waiting.
*/
if(! (*key_ready)()) return 0; /* If nothing to read */
inregs.h.ah = 0x01; /* 0x01: Report whether char ready */
int86(0x16,&inregs,&outregs); /* Interrupt 0x16 */
/*
* Note that the above ROM BIOS service is also used by "key_ready", but
* in this case we are using it to peek at the next character to be read.
*/
if(outregs.h.al) return (outregs.h.al & 0x00FF);
/*
* We have a "special key", return its value.
*/
return (outregs.h.ah | 0xFF00);
}
/************************************************************************/
/* */
/* Routine: position */
/* */
/* Position the cursor to a specified row and column on the video */
/* screen. */
/* */
/* Parameters: "row" - The starting row #. */
/* "col" - The starting column #. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void position(row,col)
UINT row,col;
{
inregs.h.ah = 0x02; /* 0x02: Set cursor position */
inregs.h.dh = row; /* Set row # */
inregs.h.dl = col; /* Set column # */
inregs.h.bh = 0; /* Screen page is always zero */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
}
/************************************************************************/
/* */
/* Routine: put_window */
/* */
/* Write a character to the screen window. This (and its caller, */
/* "fill_window"), are coroutine with "send_window" which takes */
/* characters from the screen window and sends them as morse code. */
/* */
/* Parameters: "c" - The character to write. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void put_window(c)
UINT c;
{
UINT row,col;
/*
* The following code handles the MORSE-KEYBOARD and MORSE-FILE modes.
*/
if(state != TRAINER)
{
/*
* The following statement reads the actual cursor position since
* "send_window" may have updated the values of "cursor_row" and
* "cursor_col" without actually updating the cursor position when
* "put_window" executes.
*/
read_position(&row,&col); /* Read current cursor pos. */
cursor_off(); /* Turn off cursor for a bit */
/*
* The following statement checks to see if a scroll operation was
* prevented (by the window being full), the last time we were here and
* does one if so.
*/
if(insert_row > 23)
{
scroll_window(); /* Scroll the window */
row--; /* Update after scroll */
cursor_row--; /* Update after scroll */
insert_row--;
}
if(c == '\n') /* Is it a carriage return? */
{
empty_window = FALSE; /* Enable output coroutine */
insert_row++; /* If so update insertion point */
insert_col = 0;
}
else if(c == BS) /* Is it backspace? */
{
if(insert_row > cursor_row)
{
if(insert_col > 0)
{
insert_col--;
position(insert_row,insert_col);
show_char(' ',1);
}
}
else
{
if(insert_col > (cursor_col + 1))
{
insert_col--;
position(insert_row,insert_col);
show_char(' ',1);
}
}
}
else
{
empty_window = FALSE; /* Enable output coroutine */
position(insert_row,insert_col++);
show_char(c,1); /* Out we go */
}
if(insert_col > 79) /* Need to fake a CR */
{
insert_row++;
insert_col = 0;
}
if(insert_row > 23) /* Need to try scrolling the window */
{
if(cursor_row == 0) /* Assuming we can... */
{
full_window = TRUE; /* Nope, full up */
}
else
{
scroll_window();/* Scroll the window */
row--; /* Update after scroll */
cursor_row--; /* Update after scroll */
insert_row--;
}
}
position(row,col); /* Put cursor back correctly */
cursor_on(); /* Turn cursor back on */
}
/*
* The following code handle the MORSE-TRAINING mode.
*/
else
{
position(cursor_row,cursor_col++);
show_char(c,1); /* Write TRAINER character out */
if(cursor_col > 79) /* Need to fake a CR */
{
cursor_row++;
cursor_col = 0;
}
if(cursor_row > 23) /* Need to scroll the window */
{
scroll_window();/* Scroll the window */
cursor_row--; /* Update after scroll */
}
position(cursor_row,cursor_col); /* Update cursor */
}
}
/************************************************************************/
/* */
/* Routine: read_char */
/* */
/* Read the character on the screen at the current cursor */
/* position. */
/* */
/* Parameters: None. */
/* */
/* Returns: The character read. */
/* */
/************************************************************************/
UINT read_char()
{
inregs.h.ah = 0x08; /* 0x08: Read char and attribute */
inregs.h.bh = 0; /* Always use page zero */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
return (outregs.h.al);
}
/************************************************************************/
/* */
/* Routine: read_position */
/* */
/* Reads the current cursor position. */
/* */
/* Parameters: "row" - Points where to return row #. */
/* "col" - Points where to return col #. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void read_position(row,col)
UINT *row,*col;
{
inregs.h.ah = 0x03; /* 0x03: Read cursor position */
inregs.h.bh = 0; /* Always use page zero */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
*row = outregs.h.dh; /* Current row # */
*col = outregs.h.dl; /* Current column # */
}
/************************************************************************/
/* */
/* Routine: read_uint */
/* */
/* Read an unsigned integer from the user. */
/* */
/* Parameters: "existing"- The value to use if the user */
/* types a function key prior to */
/* terminating the number (CR). */
/* "result"- A pointer to the UINT to store */
/* the result in. */
/* "size" - The numeric field size. */
/* "c" - The actual character that was */
/* typed. */
/* */
/* Returns: TRUE if a non-digit (non-CR), was pressed. The */
/* "existing" value will be displayed. FALSE if a */
/* new value is read (i.e. 0 or more digits */
/* followed by a CR). */
/* */
/************************************************************************/
BOOL read_uint(existing,result,size,c)
UINT existing,*result,size,c;
{
BOOL r_value;
char buf[81];
UINT count;
position(cursor_row,cursor_col);
show_attr(' ',size,0x70); /* Reverse video data entry field */
count = 0; /* No digits as yet */
FOREVER
{
if(c == '\r') /* Was it a carriage return? */
{
if(count) /* Have we had any digits yet? */
{
buf[count] = NULL; /* Yes, terminate */
*result = atoi(buf); /* Convert value */
r_value = FALSE; /* Signal success */
break;
}
else /* Nothing yet, return "existing" */
{
*result = existing;
r_value = FALSE; /* Signal success */
break;
}
}
else if(c == BS) /* Was it a backspace? */
{
if(count) /* Anything to remove? */
{
count--; /* Backup */
position(cursor_row,cursor_col + count);
show_char(' ',1);
}
}
else if((c >= '0') && (c <= '9')) /* Was it a digit? */
{
buf[count] = c; /* Remember digit for later */
show_char(c,1); /* Echo character */
if(++count != size) position(cursor_row,
cursor_col + count);
else
{
buf[count] = NULL;
*result = atoi(buf); /* Full field, quit */
r_value = FALSE; /* Signal success */
break;
}
}
else /* Must be an error/function key */
{
*result = existing;
r_value = TRUE;
break;
}
/*
* Get the next character.
*/
while(! (c = peek_key())) ; /* Wait for something */
if((c == '\r') || (c == BS) || isdigit(c)) get_key();
}
/*
* Time to return, restore field to normal and display whatever result we
* have decided on.
*/
position(cursor_row,cursor_col);
show_attr(' ',size,0x07); /* re-reverse-video field */
itoa(*result,buf,10); /* Convert result to ASCII */
buf[size] = NULL; /* Terminate at field width */
show_string(buf); /* Display result */
position(cursor_row,cursor_col);
return r_value;
}
/************************************************************************/
/* */
/* Routine: restore_video */
/* */
/* Restore original video mode and default cursor size. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void restore_video()
{
position(0,0); /* Position cursor at origin */
show_attr(' ',(25 * 80),0x07); /* Erase screen, nuke reverse video */
if(old_vmode != 7) set_vmode(old_vmode); /* If ! MDA/Hercules */
/*
* Restore cursor size to what it was.
*/
inregs.h.ch = old_cursor_start; /* Original cursor start */
inregs.h.cl = old_cursor_end; /* Original cursor ending */
inregs.h.ah = 0x01; /* 0x01: Set cursor size */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
}
/************************************************************************/
/* */
/* Routine: scroll_window */
/* */
/* Scroll screen window (top 24 lines), up one line. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void scroll_window()
{
inregs.h.ah = 0x06; /* 0x06: Scroll window up */
inregs.h.al = 1; /* # of lines to scroll */
inregs.h.ch = 0; /* Upper row # */
inregs.h.cl = 0; /* Left column # */
inregs.h.dh = 23; /* Lower row # */
inregs.h.dl = 79; /* Right column # */
inregs.h.bh = 0x07; /* Attribute: normal white on black */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
}
/************************************************************************/
/* */
/* Routine: send */
/* */
/* Send a morse character out the speaker. */
/* */
/* Parameters: "str" - A pointer to a string which */
/* consists of '.' for morse dots, */
/* and '-'s for morse dashes. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void send(str)
char *str;
{
UINT dash;
if(! *str) return; /* Zero length string */
dash = 3 * max_speed; /* Dash is three times length of dot */
FOREVER
{
sound_on(); /* Begin dot/dash */
if(*str++ == '.') wait_tick(max_speed); /* Dot time */
else wait_tick(dash);
sound_off(); /* Finish dot/dash */
if(! *str) return; /* If all done */
wait_tick(max_speed); /* Otherwise inter-dot/dash gap */
}
}
/************************************************************************/
/* */
/* Routine: send_window */
/* */
/* Send the next legal character from the window out as a morse */
/* code character. This routine is coroutine with "fill_window" */
/* (and the routine it calls, "put_window"). */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void send_window()
{
BOOL was_space;
char *s;
UINT c;
if(empty_window) return; /* If nothing to send */
was_space = FALSE; /* Set to TRUE if a space found */
c = read_char(); /* Read next char off window */
FOREVER
{
if(cursor_row == insert_row)
{
cursor_col++; /* Update cursor column # */
if(cursor_col == insert_col) /* Window now empty */
{
empty_window = TRUE;
break;
}
}
else
{
if(cursor_col < 79) cursor_col++;
else
{
full_window = FALSE; /* Enable coroutine */
cursor_col = 0;
cursor_row++;
if((cursor_row == insert_row) &&
(cursor_col == insert_col))
{
empty_window = TRUE;
break;
}
}
}
if(c == ' ') /* If character was a space */
{
was_space = TRUE;
position(cursor_row,cursor_col);
c = read_char(); /* Read next char off window */
continue; /* Until find a non space */
}
else break; /* Otherwise, we can send this one */
}
if(c == ' ') /* If all we got was spaces */
{
position(cursor_row,cursor_col); /* Update cursor */
return;
}
s = codes[c - ' '].morse; /* Get morse coding to send */
if(was_space || was_empty) /* Do we need a inter-word gap? */
{
if(empty_window) was_empty = TRUE; /* Remember */
else was_empty = FALSE;
wait_tick((7 * max_speed) + (2 * delay));
}
else /* Otherwise, put a inter-char gap */
{
if(empty_window) was_empty = TRUE; /* Remember */
else was_empty = FALSE;
wait_tick((3 * max_speed) + delay);
}
send(s); /* Finally, send the character */
position(cursor_row,cursor_col); /* Update cursor position */
}
/************************************************************************/
/* */
/* Routine: set_delay */
/* */
/* Used by the "setup" function to select the code speed delay */
/* value. This allows an additional inter-character delay to be */
/* inserted whose length is equal to the delay value times 55 */
/* milliseconds. An additional inter-word delay is also inserted */
/* which is twice as long as the inter-character delay. */
/* */
/* Parameters: "unused"- An unused parameter. */
/* "c" - The actual character that was */
/* typed. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void set_delay(unused,c)
UINT unused,c;
{
UINT i;
if(read_uint(delay,&i,2,c)) return; /* 2 digit field width */
delay = i; /* Update delay count */
show_speed(); /* Update command line display */
}
/************************************************************************/
/* */
/* Routine: set_freq */
/* */
/* Used by the "setup" function to set the sidetone oscillator */
/* frequency. */
/* */
/* Parameters: "unused"- An unused parameter. */
/* "c" - The actual character that was */
/* typed. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void set_freq(unused,c)
UINT unused,c;
{
UINT i;
if(read_uint(frequency,&i,4,c)) return; /* 4 digit field width */
init_sound(frequency = i); /* Update frequency value */
}
/************************************************************************/
/* */
/* Routine: set_group_size */
/* */
/* Used by the "setup" function to set the code group size for the */
/* TRAINER function. If a group size of zero is entered the code */
/* group size is random up to the maximum (MAX_CG_SIZE). */
/* */
/* Parameters: "unused"- An unused parameter. */
/* "c" - The actual character that was */
/* typed. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void set_group_size(unused,c)
UINT unused,c;
{
UINT i;
if(read_uint(group_size,&i,1,c)) return;
group_size = i; /* Update code group size */
}
/************************************************************************/
/* */
/* Routine: set_max_speed */
/* */
/* Used by the "setup" function to select the maximum rate at */
/* which code will be sent. Code may be sent at slower than this */
/* rate by having a non-zero "delay" value. */
/* */
/* Parameters: "rate" - The desired maximum rate. 0 */
/* indicates a speed of 22WPM is */
/* desired (equal to a "max_speed" */
/* value of 1). 1 indicates 11WPM */
/* or a "max_speed" value of 2, */
/* and so on up to a value of 3 */
/* which means a speed of 5WPM and */
/* value for "max_speed" of 4. */
/* "c" - The actual character that was */
/* typed (must be CR to be valid). */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void set_max_speed(rate,c)
UINT rate,c;
{
if(c != '\r') return; /* Must be CR */
switch_selector(rate); /* Update "setup" screen display */
max_speed = rate + 1; /* Update maximum speed */
show_speed(); /* Update speed on command line */
}
/************************************************************************/
/* */
/* Routine: set_speed_adapt */
/* */
/* Used by the "setup" function to select the code speed */
/* adaptation rate. The basic idea is that more misses while in */
/* TRAINER mode means that the code rate should slow down and vice */
/* versa. */
/* */
/* Parameters: "rate" - The desired adaptation rate. 0 */
/* indicates no adaptation is */
/* desired, 1-3 indicate that */
/* slow to fast adaptation is */
/* desired respectively. */
/* "c" - The actual character that was */
/* typed (must be CR to be valid). */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void set_speed_adapt(rate,c)
UINT rate,c;
{
if(c != '\r') return; /* Must be CR */
switch_selector(rate); /* Update "setup" screen display */
speed_adapt = rate; /* Update speed adaptation rate */
}
/************************************************************************/
/* */
/* Routine: set_vmode */
/* */
/* Set the video display mode. Uses the ROM BIOS service 0x10. */
/* */
/* Parameters: "mode" - The video display mode to set. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void set_vmode(mode)
UINT mode;
{
inregs.h.ah = 0x00; /* 0x00: Set video mode */
inregs.h.al = mode; /* The mode to set */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
}
/************************************************************************/
/* */
/* Routine: setup */
/* */
/* Configure program parameters. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
struct screen setup_help1[] = /* Morse setup help display #1 */
{
{1,30,"SETUP HELP (PAGE 1)" },
{3,2,
"This function allows you to control the various MORSE program options."},
{5,2,
"To change an option you use the cursor arrow keys to position the cursor"},
{6,2,
"just to the left of the desired option. If the option is either ON/OFF (or"},
{7,2,
"ENABLED/DISABLED), you use the carriage return key (ENTER on some"},
{8,2,
"keyboards), to toggle the option back and forth. If the option requires a"},
{9,2,
"numeric input just enter the digits followed by a carriage return (if the"},
{10,2,
"field is not completely full)."},
{12,2,
"The top window on the screen controls the \"enabled\" status of the specific"},
{13,2,
"Morse Codes. If a triangle is present to the left of the ASCII equivalent"},
{14,2,
"the character is enabled. You may change the enabled status either by"},
{15,2,
"individually toggling characters, or by using one of the three enabling"},
{16,2,
"functions. These are located in the lower right portion of the window and"},
{17,2,
"allow you to enable all the characters, enable only those required by the"},
{18,2,
"FCC amateur radio examinations, or disable them all. You use the enabling"},
{19,2,
"functions by positioning the cursor next to the desired function and typing"},
{20,2,
"carriage return."},
{22,28,"(CONTINUED ON NEXT PAGE)"},
{0,0,NULL }
};
struct screen setup_help2[] = /* Morse setup help display #2 */
{
{1,30,"SETUP HELP (PAGE 2)" },
{3,2,
"The three windows located in the lower right corner of the screen are used"},
{4,2,
"to adjust the Morse Code speed and oscillator frequency."},
{6,2,
"The speed adjustment consists of two components: A basic character speed"},
{7,2,
"(5,7,11 or 22 WPM), and an adjustable \"delay\" which is added to the inter-"},
{8,2,
"character time. The same delay is also added to the inter-word time, but is"},
{9,2,
"doubled in effect. The idea behind this is to always have the characters"},
{10,2,
"sent at the same rate (and thus sound the same), but to vary the spacing"},
{11,2,
"between characters and words. It is suggested that you set the character"},
{12,2,
"speed to the fastest rate you ever anticipate using and adjust the delay"},
{13,2,
"from a large value down as you improve. The speed adaptation feature"},
{14,2,
"of the CODE TRAINING function can be used to do this for you automatically."},
{16,2,
"The three windows located in the lower left corner of the screen are used"},
{17,2,
"to control options which are particular to the CODE TRAINING function."},
{18,2,
"These include the code group size, how fast to adapt the code speed \"delay\""},
{19,2,
"in response to changes in your accuracy, and whether to adapt the choices of"},
{20,2,
"which characters are in the code groups to those you miss more often."},
{0,0,NULL }
};
void setup()
{
int c;
FOREVER
{
cursor_off(); /* Turn cursor off */
erase(); /* Erase screen */
/*
* Display command line.
*/
position(24,2); /* Position to 25 line */
show_string("[SETUP] F1:HELP");
position(24,33);
switch(state)
{
case MAINMENU: /* Came from MAIN MENU */
show_string("F2:MAIN MENU");
break;
case KEYBOARD: /* Came from CODE KEYBOARD */
show_string("F2:CODE KEYBOARD");
break;
case FROMFILE: /* Came from CODE FILE */
show_string("F2:CODE FILE");
break;
case TRAINER: /* Came from CODE TRAINING */
show_string("F2:CODE TRAINER");
break;
default:
break;
}
position(24,53);
show_string("F10:QUIT");
show_speed(); /* Show current code speed/delay */
init_setup(); /* Init. "setup_info" data/screen */
/*
* Position the cursor to the origin cell.
*/
cursor_row = 19;
cursor_field_col = 3;
cursor_key(FUNC_RIGHT); /* Force actual cursor position calc */
cursor_on(); /* We can turn cursor ON now */
FOREVER
{
while(! (c = get_key())) ; /* Wait for user */
switch(c) /* See what we are to do */
{
case FUNC_UP:
case FUNC_DOWN:
case FUNC_LEFT:
case FUNC_RIGHT:
cursor_key(c); /* Move cursor */
break;
case FUNC_F1: /* Help screens */
help(setup_help1);
help(setup_help2);
break;
case FUNC_F2: /* Return to whoever */
return;
case FUNC_F10: /* Terminate */
terminate();
default: /* Do something */
(*setup_info[cursor_row][cursor_field_col].
field_func)(setup_info[cursor_row]
[cursor_field_col].field_arg,
c);
break;
}
if(c == FUNC_F1) break;
}
}
}
/************************************************************************/
/* */
/* Routine: show_attr */
/* */
/* Display a specified # of copies of a single character starting */
/* at the current cursor position, using the specified display */
/* attribute. */
/* */
/* Parameters: "c" - The character to display. */
/* "number" - The desired # of copies. */
/* "attr" - Text mode display attribute. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void show_attr(c,number,attr)
UINT c,number,attr;
{
inregs.h.ah = 0x09; /* 0x09: Write character/attribute */
inregs.h.al = c; /* The character to write */
inregs.h.bh = 0; /* Always use page zero */
inregs.h.bl = attr; /* Text mode display attribute */
inregs.x.cx = number; /* # of times to write character */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
}
/************************************************************************/
/* */
/* Routine: show_char */
/* */
/* Display a specified # of copies of a single character starting */
/* at the current cursor position. */
/* */
/* Parameters: "c" - The character to display. */
/* "number" - The desired # of copies. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void show_char(c,number)
UINT c,number;
{
inregs.h.ah = 0x0A; /* 0x0A: Write character */
inregs.h.al = c; /* The character to write */
inregs.h.bh = 0; /* Always use page zero */
inregs.x.cx = number; /* # of times to write character */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
}
/************************************************************************/
/* */
/* Routine: show_screen */
/* */
/* Display a series of strings at specified positions on the */
/* screen. */
/* */
/* Parameters: "list" - A pointer to an array of */
/* "screen" structures. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void show_screen(list)
struct screen list[];
{
struct screen *sptr;
/*
* Loop through the display list until a NULL display string is found.
*/
sptr = list;
while(sptr->text)
{
position(sptr->row,sptr->col); /* Start location for text */
show_string(sptr->text); /* Show text */
sptr++;
}
}
/************************************************************************/
/* */
/* Routine: show_speed */
/* */
/* Display the current code speed and delay factor on the command */
/* line in the following format: */
/* */
/* "SPEED:XX(YY)WPM" */
/* */
/* Where XX is the base rate and YY represents the number of 55 ms */
/* delays to toss in between characters (also the # of 110 ms */
/* delays between words...) */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void show_speed()
{
BOOL was_cursor;
char buf[81];
UINT row,col;
read_position(&row,&col); /* Read current cursor position */
was_cursor = cursor_state; /* Remember whether ON or OFF */
cursor_off(); /* Turn cursor off */
position(24,64); /* Place to write speed */
show_char(' ',16); /* Blank out beforehand */
show_string("SPEED:");
switch(max_speed)
{
case 1: /* Base rate is 22 WPM */
show_string("22(");
break;
case 2: /* Base rate is 11 WPM */
show_string("11(");
break;
case 3: /* Base rate is 7 WPM */
show_string("7(");
break;
case 4: /* Base rate is 5 WPM */
show_string("5(");
break;
default:
break;
}
show_string(itoa(delay,buf,10)); /* Current value of delay */
show_string(")WPM");
position(row,col); /* Restore cursor position */
if(was_cursor) cursor_on(); /* And turn cursor back on if needed */
}
/************************************************************************/
/* */
/* Routine: show_string */
/* */
/* Display the specified string at the current cursor position and */
/* advance the cursor. */
/* */
/* Parameters: "string" - A pointer to the string to */
/* display. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void show_string(string)
char *string;
{
inregs.h.ah = 0x0E; /* 0x0E: Write character as TTY */
inregs.h.bh = 0; /* Always use page zero */
while(*string)
{
inregs.h.al = *string++; /* Character to write */
int86(0x10,&inregs,&outregs); /* Interrupt 0x10 */
}
}
/************************************************************************/
/* */
/* Routine: sign_on */
/* */
/* Clear screen and show startup animation and titles. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
char image[25][80]; /* Screen image for dissolves */
struct screen startup[] =
{
{ 2,18, "-.-. --.- -.. . -. --... -.-. -.-. -..." },
{ 4,23, "$ $ $$$ $$$$ $$$ $$$$$" },
{ 5,23, "$$ $$ $ $ $ $ $ $ $" },
{ 6,23, "$$$$$ $ $ $ $ $ $" },
{ 7,23, "$ $ $ $ $ $$$$ $$$ $$$$$" },
{ 8,23, "$ $ $ $ $ $ $ $" },
{ 9,23, "$ $ $ $ $ $ $ $ $" },
{ 10,23,"$ $ $$$ $ $ $$$ $$$$$" },
{ 12,19,"A MORSE CODE KEYBOARD AND TRAINING PROGRAM" },
{ 13,25,"WRITTEN BY KIRK BAILEY, N7CCB" },
{ 17,20,"THIS PROGRAM IS COMPLETELY PUBLIC DOMAIN" }
};
void sign_on()
{
char *cptr;
UINT left,right,top,bottom,i,j,k;
/*
* Fill "image" video overlay with desired startup screen.
*/
for(cptr = &image[0][0], i = 0; i < (80 * 25); i++)
{
*cptr++ = ' '; /* Erase "image" overlay */
}
/*
* Put title border in "image" video overlay.
*/
top = 1; bottom = 18;
left = 14; right = 65;
image[top][left] = 0xC9;
image[top][right] = 0xBB;
image[bottom][left] = 0xC8;
image[bottom][right] = 0xBC;
for(i = left + 1; i < right; i++)
{
image[top][i] = 0xCD;
image[bottom][i] = 0xCD;
}
for(i = top + 1; i < bottom; i++)
{
image[i][left] = 0xBA;
image[i][right] = 0xBA;
}
/*
* Put titles in "image" video overlay.
*/
for(i = 0; i < (sizeof(startup) / sizeof(struct screen)); i++)
{
cptr = startup[i].text;
for(j = startup[i].col; *cptr; j++)
{
if(*cptr == '$') *cptr = 0xB2; /* Use block instead */
image[startup[i].row][j] = *cptr++;
}
}
/*
* Display version date.
*/
cptr = VERSION;
for(i = 0; *cptr; i++) image[15][36 + i] = *cptr++;
/*
* Completely fill screen from middle out.
*/
top = 11;
bottom = 12;
left = 33; right = 46;
for(i = 0; i <= 11; i++, top--, bottom++, left -= 3, right += 3)
{
position(top,left);
show_char(0xDB,right - left + 1);
position(bottom,left);
show_char(0xDB,right - left + 1);
for(j = top + 1; j < bottom; j++)
{
position(j,left);
show_char(0xDB,3);
position(j,right - 2);
show_char(0xDB,3);
}
}
/*
* Do a dissolve from full screen to desired startup page "image".
*/
top = 11;
bottom = 12;
left = 33; right = 46;
for(i = 0; i <= 11; i++, top--, bottom++, left -= 3, right += 3)
{
for(j = left; j <= right; j++)
{
position(top,j);
show_char(image[top][j],1);
}
for(j = left; j <= right; j++)
{
position(bottom,j);
show_char(image[bottom][j],1);
}
for(j = top + 1; j < bottom; j++)
{
for(k = 0; k < 3; k++)
{
position(j,left + k);
show_char(image[j][left + k],1);
}
for(k = 0; k < 3; k++)
{
position(j,right - k);
show_char(image[j][right - k],1);
}
}
}
/*
* Kick in the reverse video 25 line.
*/
position(24,0); /* Position to start of 25 line */
show_attr(' ',80,0x70); /* Reverse video the line */
}
/************************************************************************/
/* */
/* Routine: simple_border */
/* */
/* Draw a double line border across the top and down both sides to */
/* connect with the command line across the bottom. Also add a */
/* a double line across the screen two lines from the top to allow */
/* a title to be placed on the second line. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void simple_border()
{
position(0,0); show_char(0xC9,1);
position(0,1); show_char(0xCD,78);
position(0,79); show_char(0xBB,1);
for(cursor_row = 1; cursor_row < 24; cursor_row++)
{
position(cursor_row,0);
if(cursor_row == 2) show_char(0xCC,1);
else show_char(0xBA,1);
position(cursor_row,79);
if(cursor_row == 2) show_char(0xB9,1);
else show_char(0xBA,1);
}
position(2,1); show_char(0xCD,78);
}
/************************************************************************/
/* */
/* Routine: sound_off */
/* */
/* Disconnect the 8253 timer from the internal speaker (ie. turn */
/* speaker off). */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void sound_off()
{
outp(0x61,inp(0x61) & 0xFC); /* Clear bottom two bits in PPI */
}
/************************************************************************/
/* */
/* Routine: sound_on */
/* */
/* Connect the 8253 timer to the internal speaker and produce a */
/* tone whose frequency was set by the most recent "init_sound" */
/* call. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void sound_on()
{
outp(0x61,inp(0x61) | 0x03); /* Set bottom two bits in PPI */
}
/************************************************************************/
/* */
/* Routine: switch_selector */
/* */
/* Do screen updates for "setup" display of a selector switch. */
/* This is a collection of 4 fields, only one of which may be */
/* selected at any given time. */
/* */
/* Parameters: "setting"- The switch position being */
/* selected. See the GLOBAL */
/* DEFINITION section for the */
/* macro definitions of the legal */
/* positions (the "field_arg" */
/* component of the "setup_info" */
/* data structure). */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void switch_selector(setting)
UINT setting;
{
UINT off_row1,off_row2,off_row3;
/*
* Figure out which switch positions need to be turned off.
*/
switch(setting)
{
case SELECT0: /* Top switch position */
off_row1 = cursor_row + 1;
off_row2 = cursor_row + 2;
off_row3 = cursor_row + 3;
break;
case SELECT1: /* Second position from the top */
off_row1 = cursor_row - 1;
off_row2 = cursor_row + 1;
off_row3 = cursor_row + 2;
break;
case SELECT2: /* Second position from the bottom */
off_row1 = cursor_row - 2;
off_row2 = cursor_row - 1;
off_row3 = cursor_row + 1;
break;
case SELECT3: /* Bottom switch position */
off_row1 = cursor_row - 3;
off_row2 = cursor_row - 2;
off_row3 = cursor_row - 1;
break;
default:
break;
}
/*
* Turn ON/OFF the appropriate switch positions.
*/
cursor_off(); /* Turn OFF the cursor during update */
position(off_row1,cursor_col); show_char(' ',1);
position(off_row2,cursor_col); show_char(' ',1);
position(off_row3,cursor_col); show_char(' ',1);
position(cursor_row,cursor_col); show_char(0x10,1);
cursor_on(); /* Turn cursor back ON */
}
/************************************************************************/
/* */
/* Routine: terminate */
/* */
/* Restore computer to original status and exit. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void terminate()
{
sound_off(); /* Turn off noise */
restore_video(); /* Put screen back together */
exit(FALSE); /* And exit without prejudice */
}
/************************************************************************/
/* */
/* Routine: toggle_char */
/* */
/* Toggle the enabled status of morse code characters (the MORSE */
/* FILE and MORSE TRAINER sections use this status to see what */
/* can be translated into morse code). */
/* */
/* Parameters: "i" - An index into the "codes" data */
/* structure. */
/* "c" - The actual character that was */
/* typed (must be CR to be valid). */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void toggle_char(i,c)
UINT i,c;
{
if(c != '\r') return;
position(cursor_row,cursor_col);
if(codes[i].enabled = !codes[i].enabled) show_char(0x10,1);
else show_char(' ',1);
}
/************************************************************************/
/* */
/* Routine: toggle_code_adapt */
/* */
/* Used by the "setup" function to enable and disable code */
/* adaptation. The basic idea is that characters which are missed */
/* often should be presented more often (in TRAINER mode), to */
/* enhance learning them. */
/* */
/* Parameters: "unused"- An unused parameter. */
/* "c" - The actual character that was */
/* typed (must be CR to be valid). */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void toggle_code_adapt(unused,c)
UINT unused,c;
{
if(c != '\r') return; /* Must be CR */
position(cursor_row,cursor_col);
if(code_adapt = !code_adapt) show_string("\x10 ENABLED ");
else show_string(" DISABLED");
position(cursor_row,cursor_col);
}
/************************************************************************/
/* */
/* Routine: training */
/* */
/* Perform the interactive morse code training stuff. Note that */
/* two fields in the "codes" data structure are used to accumulate */
/* the total #'s of each character sent and the total # of errors */
/* of each character. This data is used by the "do_code_adapt" */
/* function to adjust the character choices made for code groups. */
/* In a similar fashion, the "total_attempts" amd "total_errors" */
/* variables are used to gather information about the overall */
/* progress of the user for the "do_speed_adapt" function. */
/* */
/* Parameters: None. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
struct screen trainer_help[] = /* Morse code trainer help display */
{
{1,31,"CODE TRAINING HELP" },
{3,2,
"This function \"sends\" random Morse Codes which you then echo with the"},
{4,2,
"ASCII equivalent on the keyboard. The characters from which the random"},
{5,2,
"code groups are constructed are restricted to those which are \"enabled\"."},
{6,2,
"The SETUP (F3), function may be used to modify which characters are enabled."},
{7,2,
"If you type the wrong ASCII code (or type nothing prior to the next code"},
{8,2,
"being completely sent), the corresponding Morse Code will be repeated until"},
{9,2,
"you do correctly echo it. You may skip echoing a particular code with the"},
{10,2,
"SKIP function key (F9). If you wish to take a break use the HELP function"},
{11,2,
"(F1), which preserves information about your progress."},
{13,2,
"Since some Morse Codes have no direct ASCII equivalents, ASCII characters"},
{14,2,
"have been chosen to represent them for the purposes of the MORSE program."},
{15,2,
"See the SETUP function for the specific ASCII to Morse conversions which are"},
{16,2,
"used. The SETUP function may also be used to control the code group size"},
{17,2,
"(or it may be set to random), the code speed, and the oscillator frequency."},
{19,2,
"The CODE TRAINING function may also be set to \"adapt\" to your progress."},
{20,2,
"Using options controlled by the SETUP function you may have the code speed"},
{21,2,
"be automatically adjusted based on your accuracy and have codes which you"},
{22,2,
"miss often appear more frequently."},
{0,0,NULL }
};
void training()
{
int c;
UINT i,j,code_sample_count,speed_sample_count,t_state;
UINT key,low,mid,high;
state = TRAINER; /* Currently feedback training */
/*
* The following code implements a 8 state, state machine. It handles the
* 1 character "fall-behind" for the user echoing the sent code and
* "correctly" recovers when an error is detected.
*/
t_state = 0; /* Initialization state */
FOREVER
{
switch(t_state)
{
case 0: /* Initialization state */
/* Erase screen */ erase();
/* Position to 25 line */ position(24,2);
show_string(
"[CODE TRAINING] F1:HELP F2:MENU F3:SETUP F9:SKIP F10:QUIT");
/* Show speed/delay */ show_speed();
/* Erase/init window */ erase_window();
/*
* Initialize the index table into the "codes" structure that is used to
* generate random (and other) code groups.
*/
for(i = enabled_codes = 0; i < (sizeof(codes) /
sizeof(struct code)); i++)
{
/* If code is to be used */ if(codes[i].enabled)
{
/* No errors to start with */ codes[i].errors = 0;
/* No characters sent to start with */ codes[i].attempts = 0;
/* The ASCII equivalent */ index[enabled_codes].code =
i +' ';
enabled_codes++;
}
}
/*
* Setup initial probabilities so that all codes are equally likely.
*/
for(i = 0; i < enabled_codes; i++)
{
index[i].base = (UINT) ((i * 32767L) /
enabled_codes);
}
/*
* The following three statement initialize the information needed for the
* code/speed adaptation stuff.
*/
sample_attempts = 0;
sample_errors = 0;
code_sample_count = speed_sample_count = 0;
/* Fall into state 1 */ t_state = 1;
case 1:
/* Put inter-word space */ put_window(' ');
/*
* Figure out if it is time to do the code/speed adaptation stuff and
* perform if needed.
*/
if(code_adapt)
{
if(++code_sample_count > CODE_SAMPLE)
{
code_sample_count = 0;
do_code_adapt();
}
}
if(++speed_sample_count > SPEED_SAMPLE)
{
speed_sample_count = 0;
do_speed_adapt();
}
/*
* Figure out how long the next code group should be and find out its
* contents.
*/
/* If non-random */ if(group_size) i = group_size;
/* If random */ else i = (rand() % MAX_CG_SIZE) + 1;
/*
* Figure out the contents of the code group using binary search on the
* "index" structure to adjust the probabilities appropriately.
*/
for(j = 0; j < i; j++)
{
if(! enabled_codes) code_group[j]= '^';
else
{
key = rand();
low = 0;
high = enabled_codes;
while((high - low) > 1)
{
mid = (high + low) / 2;
if(key <= index[mid].
base)
high = mid;
else low = mid;
}
code_group[j] = index[low].
code;
}
}
/* Reset error status */ had_error = FALSE;
/* Character counter */ j = 0;
/* Fall into state 2 */ t_state = 2;
case 2:
/* Anything left? */ if(j >= i)
{
/* No, wait rest of inter-word */ wait_tick((4 * max_speed) + delay);
t_state = 1;
break;
}
/*
* Time to begin sending out the code group.
*/
code_sent = code_group[j];
send(codes[code_sent - ' '].morse);
/* Fall into state 3 */ t_state = 3;
case 3:
/* Inter-char interval */ wait_tick((3 * max_speed) + delay);
/* Function key? */ if((c = peek_key()) < 0)
{
t_state = 6;
break;
}
/* Code echoed ok? */ if(! code_sent)
{
/* Yes, advance to next */ t_state = 7;
break;
}
/* Code echoed wrong */ else if(had_error)
{
/* Yes, resend it */ t_state = 2;
break;
}
/*
* The above two cases handled a correct echo (or skip), and an incorrect
* echo. Now we handle the case when nothing was echoed by sending the
* next character in the group (if there is one), and still waiting for
* a correct echo of the first character.
*/
/* Any more in group? */ if(j >= (i - 1))
{
/* Nope, that was it */ t_state = 5;
break;
}
/* Fall into state 4 */ t_state = 4;
case 4:
/*
* The previous character has not yet been echoed, but go ahead
* and send the next one anyway. The user has until the next one is
* finished being sent to correctly echo the previous one.
*/
send(codes[code_group[j + 1] - ' '].morse);
/* Function key? */ if((c = peek_key()) < 0)
{
t_state = 6;
break;
}
/* Code echoed ok? */ if(code_sent)
{
/* No, tell user wrong */ put_window(0xB1);
/* And flag error */ had_error = TRUE;
/* Wait inter-word time */ wait_tick((7 * max_speed) +
(2 * delay));
/* If user finally got it */ if(! code_sent)
{
/* Advance to next char */ t_state = 7;
break;
}
/* Otherwise, resend it */ t_state = 2;
break;
}
/*
* The previous character WAS echoed OK, update everything so we are
* waiting for the echo of the lookahead character.
*/
/* Record error if any */ if(had_error)
{
had_error = FALSE;
codes[code_group[j] - ' '].errors++;
sample_errors++;
}
/* Record char sent */ codes[code_group[j] - ' '].attempts++;
sample_attempts++;
/* Update char counter */ j++;
/* Update code to echo */ code_sent = code_group[j];
t_state = 3;
break;
case 5:
/*
* Wait remainder of inter-word delay time.
*/
wait_tick((4 * max_speed) + delay);
/* Function key? */ if((c = peek_key()) < 0)
{
t_state = 6;
break;
}
/* Code echoed ok? */ if(code_sent)
{
/* No, tell user wrong */ put_window(0xB1);
/* And flag error */ had_error = TRUE;
t_state = 2;
break;
}
/* Was OK, start group */ t_state = 1;
break;
case 6:
/*
* This state handles function key inputs.
*/
/* Remove from buffer */ get_key();
/* And see what to do */ switch(c)
{
/* Help info */ case FUNC_F1:
help(trainer_help);
/* Erase screen */ erase();
/* Position to 25 line */ position(24,2);
show_string(
"[CODE TRAINING] F1:HELP F2:MENU F3:SETUP F9:SKIP F10:QUIT");
/* Show speed/delay */ show_speed();
/* Erase/init window */ erase_window();
t_state = 2;
break;
/* Main menu */ case FUNC_F2:
return;
/* Setup */ case FUNC_F3:
setup();
t_state = 0;
break;
/* Quit MORSE */ case FUNC_F10:
terminate();
default:
/* Unknown function key */ t_state = 2;
break;
}
break;
case 7:
/*
* This state updates the character pointer within a code group and
* records any errors detected.
*/
if(had_error)
{
had_error = FALSE;
codes[code_group[j] - ' '].
errors++;
sample_errors++;
}
/* Record char sent */ codes[code_group[j] - ' '].attempts++;
sample_attempts++;
/* Advance char counter */ j++;
t_state = 2;
break;
default:
/* Should never happen */ t_state = 0;
break;
}
}
}
/************************************************************************/
/* */
/* Routine: wait_tick */
/* */
/* Waits the specified # of 18.2 Hz (55 millisecond), system clock */
/* tick intervals before returning. Note that the actual time */
/* waited is influenced by how "close" the current clock tick is */
/* to expiring and the interval waited may thus be almost one tick */
/* shorter than that desired. This can be compensated for by only */
/* calling "wait_tick" soon after a tick has expired (by issuing a */
/* preliminary synchronization "wait_tick" call prior to the calls */
/* where relative timing is important). */
/* */
/* Parameters: "count" - # of intervals to wait. */
/* */
/* Returns: None. */
/* */
/************************************************************************/
void wait_tick(count)
UINT count;
{
UINT old_tick;
if(! count) return;
inregs.h.ah = 0x00; /* 0x00: Read current clock count */
int86(0x1A,&inregs,&outregs); /* Interrupt 0x1A */
old_tick = outregs.x.dx; /* Low order word of clock tick */
while(count--)
{
FOREVER
{
inregs.h.ah = 0x00; /* 0x00: Read clock count */
int86(0x1A,&inregs,&outregs); /* Interrupt 0x1A */
if(old_tick != outregs.x.dx) break; /* If done */
fill_window(); /* Buffer input chars while waiting */
}
old_tick = outregs.x.dx; /* Remember new "old" count */
}
}