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 >
C/C++ Source or Header  |  1997-02-01  |  110KB  |  3,573 lines

  1. /************************************************************************/
  2. /*                                    */
  3. /*    File:    morse.c                            */
  4. /*                                    */
  5. /*    Morse code keyboard and feedback training program for the IBM    */
  6. /*    PC and compatible computers.                    */
  7. /*                                    */
  8. /*    This program supports the following modes of operation:        */
  9. /*                                    */
  10. /*    1.    It will function as a morse code keyboard program using    */
  11. /*        the computer's internal speaker as an output device.    */
  12. /*    2.    It will read a specified input file and convert it to    */
  13. /*        morse code, again using the internal speaker as the    */
  14. /*        output device.                        */
  15. /*    3.    It will function as a "feedback" trainer for learning    */
  16. /*        morse code by sending random code groups and making    */
  17. /*        sure that the student types the corresponding character    */
  18. /*        on the keyboard in response to the code sent.  The    */
  19. /*        "feedback" is flexible in that it can be made to    */
  20. /*        automatically increase the code speed as accuracy    */
  21. /*        improves, as well as concentrate on letters which are    */
  22. /*        frequently missed.                    */
  23. /*                                    */
  24. /*    This program was written in Microsoft "C" version 3.0 and uses    */
  25. /*    some functions which may be specific to that compiler, although    */
  26. /*    equivalents should be easily constructed using other systems.    */
  27. /*    The functions to watch out for are "intdos" which is used to    */
  28. /*    make BIOS interrupt calls and the stuff associated with the    */
  29. /*    "far" function parameter "key_ready" (such as "segread", see    */
  30. /*    the function header documentation for "key_ready").  To compile    */
  31. /*    this program using Microsoft "C" V3.0 type:            */
  32. /*                                    */
  33. /*            cl /Ze morse.c                    */
  34. /*                                    */
  35. /*    Note the /Ze option flag which tells the compiler to enable the    */
  36. /*    "far" keyword.  If you plan on making extensive changes to the    */
  37. /*    program I suggest that the source code be split up into smaller    */
  38. /*    modules.  I didn't do this to make distribution of the program    */
  39. /*    easier.                                */
  40. /*                                    */
  41. /*    This program does make extensive use of IBM PC compatible ROM    */
  42. /*    BIOS services, but should run on everything from a PC-JR    */
  43. /*    through an AT and most of the compatibles.  It also supports    */
  44. /*    all the normal video board options (CGA,MDA,Hercules,EGA,PGA,    */
  45. /*    etc.).  PC-DOS or MS-DOS version 2.0 or later is assumed.    */
  46. /*                                    */
  47. /*    Please contact me if you come up with any improvements (or find    */
  48. /*    any problems), I can be reached at:                */
  49. /*                                    */
  50. /*            Kirk Bailey, N7CCB                */
  51. /*            Logical Systems                    */
  52. /*            P.O. Box 1702                    */
  53. /*            Corvallis, OR 97339                */
  54. /*                                    */
  55. /*    Due to limited "HAM" activity time I can't distribute copies    */
  56. /*    of this program myself.  To fill this gap a local company has    */
  57. /*    agreed to perform this function for a (relatively), nominal    */
  58. /*    $25.00 fee.  I've agreed to keep them updated with the latest    */
  59. /*    and greatest version (standard IBM-PC 5.25" 360K floppy):    */
  60. /*                                    */
  61. /*            "IBM-PC Morse Code Program"            */
  62. /*            COMTEK INFORMATION SYSTEMS, INC.        */
  63. /*            P.O. Box 3004-246                */
  64. /*            Corvallis, OR 97339                */
  65. /*            (503) 758-7003                    */
  66. /*                                    */
  67. /*    Alternately, you can check local BBS/PBBS, friends, etc.    */
  68. /*                                    */
  69. /*    PLEASE NOTE: THIS PROGRAM IS HEREBY PLACED IN THE PUBLIC    */
  70. /*    DOMAIN (although I would like it if you kept my name and    */
  71. /*    address in the code...)                        */
  72. /*                                    */
  73. /*    USAGE NOTES:    The program should be self explanatory and    */
  74. /*    includes a built-in HELP function, fire it up and get busy!    */
  75. /*    After experimentation with the volume and frequency response of    */
  76. /*    your specific computer's internal speaker you may wish to    */
  77. /*    insert a volume control in the speaker line (which is usually    */
  78. /*    simple since the speaker is connected to the computer via a two    */
  79. /*    wire cable with a pin plug on the end).  Unfortunately there is    */
  80. /*    no way to control speaker volume from software (not counting    */
  81. /*    PWM tricks which are EXTREMELY model dependent)!        */
  82. /*                                    */
  83. /************************************************************************/
  84.  
  85. #include    <ctype.h>        /* Character type include file */
  86. #include    <conio.h>        /* Console I/O include file */
  87. #include    <dos.h>            /* System interface include file */
  88. #include    <stdio.h>        /* Standard include file */
  89.  
  90. /************************************************************************/
  91. /*                                    */
  92. /*            Global Definitions                */
  93. /*                                    */
  94. /************************************************************************/
  95.  
  96. typedef    unsigned int    BOOL;        /* Value of TRUE or FALSE */
  97. #define    BS        0x08        /* Backspace character */
  98. #define    FALSE        0
  99. #define    FOREVER        for(;;)        /* while(1) equivalent */
  100. #define    TRUE        1
  101. typedef    unsigned int    UINT;        /* Value of 0-65535 */
  102. #define    VERSION        "9/18/86"    /* Last update */
  103.  
  104. /************************************************************************/
  105. /*                                    */
  106. /*            Configuration Parameters            */
  107. /*                                    */
  108. /************************************************************************/
  109.  
  110. #define    MAX_CG_SIZE    9        /* Max. # chars in code group */
  111. #define    MAX_DELAY    99        /* Max. value of inter-char delay */
  112. /*
  113.  *    Code/speed adaptation configuration parameters.
  114.  */
  115.  
  116. #define    CODE_SAMPLE    50        /* # of groups/code adapt sample */
  117. #define    SLOW_DOWN    30        /* Cutoff error % to adapt to slower */
  118. #define    SPEED_SAMPLE    5        /* # of groups/speed adapt sample */
  119. #define    SPEED_UP    10        /* Cutoff error % to adapt to faster */
  120.  
  121. /*
  122.  *    The following are the values returned by "get_key" and "peek_key" for
  123.  *    the IBM PC function keys.
  124.  */
  125.  
  126. #define    FUNC_UP        0xFF48        /* Up arrow cursor key */
  127. #define    FUNC_LEFT    0xFF4B        /* Left arrow cursor key */
  128. #define    FUNC_RIGHT    0xFF4D        /* Right arrow cursor key */
  129. #define    FUNC_DOWN    0xFF50        /* Down arrow cursor key */
  130. #define    FUNC_F1        0xFF3B        /* Function key F1 */
  131. #define    FUNC_F2        0xFF3C        /* Function key F2 */
  132. #define    FUNC_F3        0xFF3D        /* Function key F3 */
  133. #define    FUNC_F4        0xFF3E        /* Function key F4 */
  134. #define    FUNC_F5        0xFF3F        /* Function key F5 */
  135. #define    FUNC_F6        0xFF40        /* Function key F6 */
  136. #define    FUNC_F7        0xFF41        /* Function key F7 */
  137. #define    FUNC_F8        0xFF42        /* Function key F8 */
  138. #define    FUNC_F9        0xFF43        /* Function key F9 */
  139. #define    FUNC_F10    0xFF44        /* Function key F10 */
  140.  
  141. /*
  142.  *    The following are the possible values for the "field_arg" component
  143.  *    of the "setup_info" structure when it contains a selector switch.
  144.  */
  145.  
  146. #define    SELECT0        0        /* Pos. 0 of a selector field group */
  147. #define    SELECT1        1        /* Pos. 1 of a selector field group */
  148. #define    SELECT2        2        /* Pos. 2 of a selector field group */
  149. #define    SELECT3        3        /* Pos. 3 of a selector field group */
  150.  
  151. /*
  152.  *    The following are the possible values of the "state" variable which
  153.  *    indicates the current function of "morse" which is being used.
  154.  */
  155.  
  156. #define    MAINMENU    0        /* Messing with main menu screen */
  157. #define    KEYBOARD    1        /* Functioning as a morse keyboard */
  158. #define    FROMFILE    2        /* Dumping a file in morse */
  159. #define    TRAINER        3        /* Interactive training mode */
  160.  
  161. /************************************************************************/
  162. /*                                    */
  163. /*            Global Declarations                */
  164. /*                                    */
  165. /************************************************************************/
  166.  
  167. struct    screen                /* Holds text to be displayed */
  168.     {
  169.     UINT    row;            /* The row to begin the text */
  170.     UINT    col;            /* The column to begin the text */
  171.     char    *text;
  172.     };
  173.  
  174. struct                    /* Holds screen info for SETUP frame */
  175.     {
  176.     BOOL    field_used;        /* TRUE if this field is used */
  177.     void    (*field_func)();    /* Function to call for this field */
  178.     UINT    field_arg;        /* Argument for function */
  179.     } setup_info[25][4];
  180.  
  181. BOOL    code_adapt = FALSE;        /* Should missed codes be common? */
  182. UINT    code_group[MAX_CG_SIZE];    /* Hold contents of a code group */
  183. int    code_sent;            /* In TRAINER mode, last code sent */
  184. BOOL    cursor_state;            /* TRUE if ON, FALSE if OFF */
  185. UINT    delay = 0;            /* Extra inter-char/word delay */
  186. UINT    enabled_codes;            /* # enabled codes in TRAINER mode */
  187. UINT    frequency = 1000;        /* Default tone oscillator freq. */
  188. UINT    group_size = 5;            /* # chars in code group (0==random) */
  189. BOOL    had_error;            /* TRUE if err on last TRAINER char */
  190. FILE    *in_fp;                /* File pointer for reading files */
  191. UINT    max_speed = 1;            /* 1 = 22WPM, 2 = 11WPM, .. 4 = 5WPM */
  192. UINT    old_cursor_end;            /* Cursor size end at program start */
  193. UINT    old_cursor_start;        /* Cursor size begin at prog. start */
  194. UINT    old_vmode;            /* Video mode at program start */
  195. union    REGS    inregs,outregs;        /* System call register templates */
  196. UINT    sample_attempts;        /* # of TRAINER chars sent/adapt */
  197. UINT    sample_errors;            /* # of TRAINER char missed/adapt */
  198. UINT    speed_adapt = 0;        /* 0-3 for rate of speed adaptation */
  199. struct    SREGS    segregs;        /* Segment register template */
  200. UINT    state;                /* "morse" current mode */
  201.  
  202. BOOL    (far *key_ready)();        /* A "far" ptr, see "key_ready" func */
  203.  
  204. /*
  205.  *    The following variables are used by the various windowing routines.
  206.  */
  207. BOOL    empty_window;            /* TRUE if screen window empty */
  208. BOOL    full_window;            /* TRUE if screen window full */
  209. UINT    cursor_row,cursor_col;        /* Row/column for current cursor */
  210. UINT    cursor_field_col;        /* Field column # (0-3) for cursor */
  211. UINT    insert_row,insert_col;        /* Row/column for insertion point */
  212. BOOL    was_empty;            /* Same as "empty_window", but for */
  213.                     /* use internally in "send_window" */
  214.  
  215. /************************************************************************/
  216. /*                                    */
  217. /*            Morse Code Definitions                */
  218. /*                                    */
  219. /************************************************************************/
  220.  
  221. struct    code                /* Morse code <-> ASCII definitions */
  222.     {
  223.     BOOL    enabled;        /* TRUE if enabled for FILE/TRAINING */
  224.     BOOL    fcc_rqrd;        /* TRUE if FCC requires for amateurs */
  225.     UINT    attempts;        /* # of times sent/code adapt sample */
  226.     UINT    errors;            /* Error count/code adapt sample */
  227.     char    *morse;            /* String representation of code */
  228.     } codes[] =
  229.     {
  230.         { FALSE,FALSE,0,0,    NULL    },    /* ' ' */
  231.         { FALSE,FALSE,0,0,    NULL    },    /* '!' */
  232.         { FALSE,FALSE,0,0,    ".-..-."},    /* '"' */
  233.         { FALSE,FALSE,0,0,    ".-..."    },    /* WAIT<->'#' */
  234.         { FALSE,FALSE,0,0,    "...-..-"},    /* '$' */
  235.         { FALSE,FALSE,0,0,    NULL    },    /* '%' */
  236.         { FALSE,FALSE,0,0,    NULL    },    /* '&' */
  237.         { FALSE,FALSE,0,0,    ".----."},    /* ''' */
  238.         { FALSE,FALSE,0,0,    "-.--."    },    /* '(' */
  239.         { FALSE,FALSE,0,0,    "-.--.-"},    /* ')' */
  240.         { FALSE,FALSE,0,0,    NULL    },    /* '*' */
  241.         { TRUE,TRUE,0,0,    ".-.-."    },    /* '+' */
  242.         { TRUE,TRUE,0,0,    "--..--"},    /* ',' */
  243.         { FALSE,FALSE,0,0,    "-....-"},    /* '-' */
  244.         { TRUE,TRUE,0,0,    ".-.-.-"},    /* '.' */
  245.         { TRUE,TRUE,0,0,    "-..-."    },    /* '/' */
  246.         { TRUE,TRUE,0,0,    "-----"    },    /* '0' */
  247.         { TRUE,TRUE,0,0,    ".----"    },    /* '1' */
  248.         { TRUE,TRUE,0,0,    "..---"    },    /* '2' */
  249.         { TRUE,TRUE,0,0,    "...--"    },    /* '3' */
  250.         { TRUE,TRUE,0,0,    "....-"    },    /* '4' */
  251.         { TRUE,TRUE,0,0,    "....."    },    /* '5' */
  252.         { TRUE,TRUE,0,0,    "-...."    },    /* '6' */
  253.         { TRUE,TRUE,0,0,    "--..."    },    /* '7' */
  254.         { TRUE,TRUE,0,0,    "---.."    },    /* '8' */
  255.         { TRUE,TRUE,0,0,    "----."    },    /* '9' */
  256.         { FALSE,FALSE,0,0,    "---..."},    /* ':' */
  257.         { FALSE,FALSE,0,0,    "-.-.-."},    /* ';' */
  258.         { FALSE,FALSE,0,0,    NULL    },    /* '<' */
  259.         { TRUE,TRUE,0,0,    "-...-"    },    /* '=' */
  260.         { FALSE,FALSE,0,0,    NULL    },    /* '>' */
  261.         { TRUE,TRUE,0,0,    "..--.."},    /* '?' */
  262.         { FALSE,FALSE,0,0,    "...-."    },    /* UNDERSTOOD<->'@' */
  263.         { TRUE,TRUE,0,0,    ".-"    },    /* 'A' */
  264.         { TRUE,TRUE,0,0,    "-..."    },    /* 'B' */
  265.         { TRUE,TRUE,0,0,    "-.-."    },    /* 'C' */
  266.         { TRUE,TRUE,0,0,    "-.."    },    /* 'D' */
  267.         { TRUE,TRUE,0,0,    "."    },    /* 'E' */
  268.         { TRUE,TRUE,0,0,    "..-."    },    /* 'F' */
  269.         { TRUE,TRUE,0,0,    "--."    },    /* 'G' */
  270.         { TRUE,TRUE,0,0,    "...."    },    /* 'H' */
  271.         { TRUE,TRUE,0,0,    ".."    },    /* 'I' */
  272.         { TRUE,TRUE,0,0,    ".---"    },    /* 'J' */
  273.         { TRUE,TRUE,0,0,    "-.-"    },    /* 'K' */
  274.         { TRUE,TRUE,0,0,    ".-.."    },    /* 'L' */
  275.         { TRUE,TRUE,0,0,    "--"    },    /* 'M' */
  276.         { TRUE,TRUE,0,0,    "-."    },    /* 'N' */
  277.         { TRUE,TRUE,0,0,    "---"    },    /* 'O' */
  278.         { TRUE,TRUE,0,0,    ".--."    },    /* 'P' */
  279.         { TRUE,TRUE,0,0,    "--.-"    },    /* 'Q' */
  280.         { TRUE,TRUE,0,0,    ".-."    },    /* 'R' */
  281.         { TRUE,TRUE,0,0,    "..."    },    /* 'S' */
  282.         { TRUE,TRUE,0,0,    "-"    },    /* 'T' */
  283.         { TRUE,TRUE,0,0,    "..-"    },    /* 'U' */
  284.         { TRUE,TRUE,0,0,    "...-"    },    /* 'V' */
  285.         { TRUE,TRUE,0,0,    ".--"    },    /* 'W' */
  286.         { TRUE,TRUE,0,0,    "-..-"    },    /* 'X' */
  287.         { TRUE,TRUE,0,0,    "-.--"    },    /* 'Y' */
  288.         { TRUE,TRUE,0,0,    "--.."    },    /* 'Z' */
  289.         { FALSE,FALSE,0,0,    "-.-.-"    },    /* START<->'[' */
  290.         { FALSE,FALSE,0,0,    ".-.-.."},    /* PARAGRAPH<->'\' */
  291.         { TRUE,TRUE,0,0,    "...-.-"},    /* END<->']' */
  292.         { FALSE,FALSE,0,0,    "........"},    /* ERROR<->'^' */
  293.         { FALSE,FALSE,0,0,    "..--.-"}    /* '_' */
  294.     };
  295.  
  296. struct    index_codes            /* Index structure into "codes" */
  297.     {
  298.     UINT    base;            /* The start of the "key" range */
  299.     UINT    code;            /* The ASCII code for the character */
  300.     UINT    error_rate;        /* Holds error rate percentage */
  301.     } index[sizeof(codes) / sizeof(struct code)];
  302.  
  303. /************************************************************************/
  304. /*                                    */
  305. /*            Function Declarations                */
  306. /*                                    */
  307. /************************************************************************/
  308.  
  309. void    cursor_key();
  310. void    cursor_off();
  311. void    cursor_on();
  312. void    disable_all();
  313. void    do_code_adapt();
  314. void    do_speed_adapt();
  315. void    enable_all();
  316. void    enable_fcc();
  317. void    erase();
  318. void    erase_window();
  319. void    fill_setup_info();
  320. void    fill_window();
  321. int    get_key();
  322. BOOL    get_fname();
  323. void    help();
  324. void    init_keyboard();
  325. void    init_setup();
  326. void    init_sound();
  327. void    init_video();
  328. char    key_rdy[];            /* See "key_ready" function doc */
  329. void    keyboard();
  330. void    main();
  331. void    main_menu();
  332. void    morse_file();
  333. int    peek_key();
  334. void    position();
  335. void    put_window();
  336. UINT    read_char();
  337. void    read_position();
  338. BOOL    read_uint();
  339. void    restore_video();
  340. void    scroll_window();
  341. void    send();
  342. void    send_window();
  343. void    set_delay();
  344. void    set_freq();
  345. void    set_group_size();
  346. void    set_max_speed();
  347. void    set_speed_adapt();
  348. void    set_vmode();
  349. void    setup();
  350. void    show_attr();
  351. void    show_char();
  352. void    show_screen();
  353. void    show_speed();
  354. void    show_string();
  355. void    sign_on();
  356. void    simple_border();
  357. void    sound_off();
  358. void    sound_on();
  359. void    switch_selector();
  360. void    terminate();
  361. void    toggle_char();
  362. void    toggle_code_adapt();
  363. void    training();
  364. void    wait_tick();
  365.  
  366. /************************************************************************/
  367. /*                                    */
  368. /*    Routine:    main                        */
  369. /*                                    */
  370. /*    Top level control for the program.                */
  371. /*                                    */
  372. /*    Parameters:    None.                        */
  373. /*                                    */
  374. /*    Returns:    None.                        */
  375. /*                                    */
  376. /************************************************************************/
  377.  
  378. struct    screen    intro_help[] =        /* Introduction help display */
  379.     {
  380.         {1,31,"MORSE INTRODUCTION" },
  381.         {3,2,
  382. "MORSE is a program designed to help you master the International Morse Code."},
  383.         {4,2,
  384. "It's designed to run on IBM (and compatible), personal computers ranging"},
  385.         {5,2,
  386. "from the PC-JR through the PC-AT.  MORSE supports most video interfaces."},
  387.         {7,2,
  388. "Mastery of the Morse Code is one of the two main requirements for obtaining"},
  389.         {8,2,
  390. "an Amateur Radio license.  The best way to get information about Amateur"},
  391.         {9,2,
  392. "Radio is to talk to someone who is already licensed (sometimes called a"},
  393.         {10,2,
  394. "\"ham\").  Most hams are more than willing to provide assistance.  If you are"},
  395.         {11,2,
  396. "unable to locate a suitable ham, the ARRL (American Radio Relay League), can"},
  397.         {12,2,
  398. "help you contact someone, and also provide you with self-study material:"},
  399.         {14,31,"ARRL Headquarters"},
  400.         {15,34,"225 Main St."},
  401.         {16,30,"Newington, CT 06111"},
  402.         {17,33,"(203) 666-1541"},
  403.         {19,2,
  404. "In addition to the ARRL material there are several excellent magazines which"},
  405.         {20,2,
  406. "are devoted to ham activity.  Two good ones are 73 AMATEUR RADIO (good"},
  407.         {21,2,
  408. "general coverage), and HAM RADIO (for the technically inclined).  These and"},
  409.         {22,2,
  410. "others are available at many bookstores and Amateur Radio equipment outlets."},
  411.         {0,0,NULL }
  412.     };
  413.  
  414. void    main()
  415.     {
  416.     int    c;
  417.  
  418.     init_keyboard();        /* Flush keyboard buffer out */
  419.     init_video();            /* Setup appropriate video mode */
  420.     init_sound(frequency);        /* Setup sound generator */
  421.  
  422.     sign_on();            /* Startup animation/titles */
  423.  
  424.     position(24,12);        /* Position to 25 line */
  425.     show_string(
  426.         "Type F1 for INFORMATION, F2 for MAIN MENU or F10 to QUIT");
  427.  
  428.     FOREVER
  429.         {
  430.         while(! (c = get_key())) ;    /* Wait for something */
  431.         switch(c)        /* See what we are to do */
  432.             {
  433.             case    FUNC_F1:    /* Help screen */
  434.                 help(intro_help);
  435.  
  436.             case    FUNC_F2:    /* Main menu */
  437.                 main_menu();
  438.                 break;
  439.  
  440.             case    FUNC_F10:    /* Terminate */
  441.                 terminate();
  442.             default:
  443.                 break;
  444.             }
  445.         }
  446.     }
  447.  
  448. /************************************************************************/
  449. /*                                    */
  450. /*    Routine:    cursor_key                    */
  451. /*                                    */
  452. /*    Used by "setup" to position the cursor in response to cursor    */
  453. /*    key inputs from the user.  This routine uses the "setup_info"    */
  454. /*    structure to determine which fields are used (and "cursor_key"    */
  455. /*    assumes the cursor field position on entry is used).        */
  456. /*                                    */
  457. /*    Parameters:    "direction" -    The cursor key to process.    */
  458. /*                                    */
  459. /*    Returns:    None.                        */
  460. /*                                    */
  461. /************************************************************************/
  462.  
  463. void    cursor_key(direction)
  464.     UINT    direction;
  465.     {
  466.     UINT    valid;
  467. /*
  468.  *    First translate the cursor movement key into the appropriate logical
  469.  *    operation on the cursor fields used by the "setup" frame.
  470.  */
  471.     valid = FALSE;
  472.  
  473.     while(! valid)
  474.         {
  475.         switch(direction)
  476.             {
  477.             case    FUNC_UP:    /* Trying to go UP */
  478.                 if(cursor_row > 0) cursor_row--;
  479.                 else direction = FUNC_DOWN;
  480.                 break;
  481.  
  482.             case    FUNC_DOWN:    /* Trying to go DOWN */
  483.                 if(cursor_row < 24) cursor_row++;
  484.                 else direction = FUNC_UP;
  485.                 break;
  486.  
  487.             case    FUNC_LEFT:    /* Trying to go left */
  488.                 if(cursor_field_col > 0) cursor_field_col--;
  489.                 else direction = FUNC_RIGHT;
  490.                 break;
  491.  
  492.             case    FUNC_RIGHT:    /* Trying to go right */
  493.                 if(cursor_field_col < 3) cursor_field_col++;
  494.                 else
  495.                     {
  496.                     switch(cursor_row)
  497.                         {
  498. /*    These two special cases are */        case    20:
  499. /*    intended to handle the two gaps in */        direction = FUNC_UP;
  500. /*    the "setup" display screen on the */        break;
  501. /*    right margin.  Ideally, this should be */
  502. /*    be replaced with a generic */        case    21:
  503. /*    "find_closest" function which would */        direction = FUNC_DOWN;
  504. /*    operate with any display topology... */        break;
  505.  
  506.                         default:
  507.                             direction = FUNC_LEFT;
  508.                             break;
  509.                         }
  510.                     }
  511.                 break;
  512.  
  513.             default:
  514.                 break;
  515.             }
  516.         if(setup_info[cursor_row][cursor_field_col].field_used)
  517.             valid = TRUE;
  518.         }
  519. /*
  520.  *    Now translate the field column # into a cursor column # (cursor
  521.  *    row # requires no translation since a 1-to-1 mapping exists).
  522.  */
  523.     cursor_col = (20 * cursor_field_col) + 3;
  524.     position(cursor_row,cursor_col);    /* Position actual cursor */
  525.     }
  526.  
  527. /************************************************************************/
  528. /*                                    */
  529. /*    Routine:    cursor_off                    */
  530. /*                                    */
  531. /*    Disable screen cursor.                        */
  532. /*                                    */
  533. /*    Parameters:    None.                        */
  534. /*                                    */
  535. /*    Returns:    None.                        */
  536. /*                                    */
  537. /************************************************************************/
  538.  
  539. void    cursor_off()
  540.     {
  541.     if(old_vmode == 7)        /* If MDA/Hercules */
  542.         {
  543.         inregs.h.ch = 12 | 32;    /* Turn cursor OFF */
  544.         inregs.h.cl = 13;
  545.         }
  546.     else                /* If CGA or other */
  547.         {
  548.         inregs.h.ch = 6 | 32;    /* Turn cursor OFF */
  549.         inregs.h.cl = 7;
  550.         }
  551.     inregs.h.ah = 0x01;        /* 0x01: Set cursor size */
  552.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  553.     cursor_state = FALSE;        /* Remember that it is off */
  554.     }
  555.  
  556. /************************************************************************/
  557. /*                                    */
  558. /*    Routine:    cursor_on                    */
  559. /*                                    */
  560. /*    Enable screen cursor (default style).                */
  561. /*                                    */
  562. /*    Parameters:    None.                        */
  563. /*                                    */
  564. /*    Returns:    None.                        */
  565. /*                                    */
  566. /************************************************************************/
  567.  
  568. void    cursor_on()
  569.     {
  570.     if(old_vmode == 7)        /* If MDA/Hercules */
  571.         {
  572.         inregs.h.ch = 12;
  573.         inregs.h.cl = 13;
  574.         }
  575.     else                /* If CGA or other */
  576.         {
  577.         inregs.h.ch = 6;
  578.         inregs.h.cl = 7;
  579.         }
  580.     inregs.h.ah = 0x01;        /* 0x01: Set cursor size */
  581.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  582.     cursor_state = TRUE;        /* Remember that it is ON */
  583.     }    
  584.  
  585. /************************************************************************/
  586. /*                                    */
  587. /*    Routine:    disable_all                    */
  588. /*                                    */
  589. /*    Disable all morse code characters for use by the MORSE FILE or    */
  590. /*    MORSE TRAINER modes.  This function is usually used prior to re-*/
  591. /*    enabling a small set of characters.                */
  592. /*                                    */
  593. /*    Parameters:    "unused"-    An unused argument.        */
  594. /*            "c"    -    The actual character that was    */
  595. /*                    typed (must be CR to be valid).    */
  596. /*                                    */
  597. /*    Returns:    None.                        */
  598. /*                                    */
  599. /************************************************************************/
  600.  
  601. void    disable_all(unused,c)
  602.     UINT    unused,c;
  603.     {
  604.     UINT    i,old_row,old_field_col,old_col;
  605.  
  606.     if(c != '\r') return;        /* Must be CR */
  607. /*
  608.  *    Remember original condition of cursor control variables.
  609.  */
  610.     old_row = cursor_row;
  611.     old_field_col = cursor_field_col;
  612.     old_col = cursor_col;
  613.     cursor_off();            /* Turn cursor OFF */
  614. /*
  615.  *    Disable all morse code characters.
  616.  */
  617.     cursor_row = 1;            /* Start at row 1 */
  618.     cursor_field_col = 0;        /* And first column of fields */
  619.     for(i = 0; i < (sizeof(codes) / sizeof(struct code)); i++)
  620.         {
  621.         if(codes[i].morse)    /* If a valid morse character */
  622.             {
  623.             if(codes[i].enabled)
  624.                 {
  625.                 cursor_col = (cursor_field_col * 20) + 3;
  626.                 toggle_char(i,'\r');
  627.                 }
  628.             if(++cursor_field_col > 3)
  629.                 {
  630.                 cursor_field_col = 0;    /* Wrap around */
  631.                 cursor_row++;
  632.                 }
  633.             }
  634.         }
  635. /*
  636.  *    Restore original condition of cursor control variables.
  637.  */
  638.     cursor_row = old_row;
  639.     cursor_field_col = old_field_col;
  640.     cursor_col = old_col;
  641.     position(cursor_row,cursor_col);
  642.     cursor_on();            /* Turn cursor ON */
  643.     }
  644.  
  645. /************************************************************************/
  646. /*                                    */
  647. /*    Routine:    do_code_adapt                    */
  648. /*                                    */
  649. /*    Used by "trainer" to automatically present the characters which    */
  650. /*    are missed most, more often.  This is done by taking the error    */
  651. /*    rate percentage over a sample interval and making the chances    */
  652. /*    of the same character appearing in the next interval be        */
  653. /*    proportional to the previous error rate percentage.        */
  654. /*                                    */
  655. /*    Parameters:    None.                        */
  656. /*                                    */
  657. /*    Returns:    None.                        */
  658. /*                                    */
  659. /************************************************************************/
  660.  
  661. void    do_code_adapt()
  662.     {
  663.     UINT    i,j,k,total_count;
  664.  
  665.     if(! enabled_codes) return;    /* No randomness here! */
  666. /*
  667.  *    First scan all the enabled characters and accumulate the sum of the
  668.  *    various error rate percentages (no character gets less than 30% even if
  669.  *    the accuracy has been perfect).  If the character wasn't sent in the
  670.  *    previous sample interval raise its percentage to 50% to encourage the
  671.  *    random # generator.
  672.  */
  673.     for(total_count = i = 0; i < enabled_codes; i++)
  674.         {
  675.         j = index[i].code - ' ';
  676.         if(! codes[j].attempts) k = 50;
  677.         else
  678.             {
  679.             k = (UINT) ((codes[j].errors * 100L) /
  680.                     codes[j].attempts);
  681.             if(k < 30) k = 30;
  682.             }
  683.         codes[j].errors = codes[j].attempts = 0;
  684.         index[i].error_rate = k;    /* Remember for next loop */
  685.         total_count += k;
  686.         }
  687. /*
  688.  *    Now we go through the "index" data structure and adjust the odds
  689.  *    based on the percentages we have calculated.
  690.  */
  691.     for(j = i = 0; i < enabled_codes; i++)
  692.         {
  693.         index[i].base = (UINT) ((j * 32767L) / total_count);
  694.         j += index[i].error_rate;
  695.         }
  696.     }
  697.  
  698. /************************************************************************/
  699. /*                                    */
  700. /*    Routine:    do_speed_adapt                    */
  701. /*                                    */
  702. /*    Used by "trainer" to automatically adjust the inter-character    */
  703. /*    and inter-word delay based on the error rate in the last sample    */
  704. /*    of code groups.  The amount the "delay" is adjusted is based    */
  705. /*    on the "speed_adapt" variable and ranges from 0 to 3 units (55    */
  706. /*    milliseconds apiece).                        */
  707. /*                                    */
  708. /*    Parameters:    None.                        */
  709. /*                                    */
  710. /*    Returns:    None.                        */
  711. /*                                    */
  712. /************************************************************************/
  713.  
  714. void    do_speed_adapt()
  715.     {
  716.     UINT    error_percentage;
  717.  
  718.     error_percentage = (sample_errors * 100) / sample_attempts;
  719.     sample_errors = sample_attempts = 0;
  720.  
  721.     if(error_percentage > SLOW_DOWN)
  722.         {
  723.         delay += speed_adapt;
  724.         if(delay > MAX_DELAY) delay = MAX_DELAY;
  725.         }
  726.     else if(error_percentage < SPEED_UP)
  727.         {
  728.         if(delay >= speed_adapt) delay -= speed_adapt;
  729.         else delay = 0;
  730.         }
  731.     show_speed();            /* Update speed/delay display */
  732.     }
  733.  
  734. /************************************************************************/
  735. /*                                    */
  736. /*    Routine:    enable_all                    */
  737. /*                                    */
  738. /*    Enable all morse code characters for use by the MORSE FILE or    */
  739. /*    MORSE TRAINER modes.                        */
  740. /*                                    */
  741. /*    Parameters:    "unused"-    An unused argument.        */
  742. /*            "c"    -    The actual character that was    */
  743. /*                    typed (must be CR to be valid).    */
  744. /*                                    */
  745. /*    Returns:    None.                        */
  746. /*                                    */
  747. /************************************************************************/
  748.  
  749. void    enable_all(unused,c)
  750.     UINT    unused,c;
  751.     {
  752.     UINT    i,old_row,old_field_col,old_col;
  753.  
  754.     if(c != '\r') return;        /* Must be CR */
  755. /*
  756.  *    Remember original condition of cursor control variables.
  757.  */
  758.     old_row = cursor_row;
  759.     old_field_col = cursor_field_col;
  760.     old_col = cursor_col;
  761.     cursor_off();            /* Turn cursor OFF */
  762. /*
  763.  *    Enable all morse code characters.
  764.  */
  765.     cursor_row = 1;            /* Start at row 1 */
  766.     cursor_field_col = 0;        /* And first column of fields */
  767.     for(i = 0; i < (sizeof(codes) / sizeof(struct code)); i++)
  768.         {
  769.         if(codes[i].morse)    /* If a valid morse character */
  770.             {
  771.             if(! codes[i].enabled)
  772.                 {
  773.                 cursor_col = (cursor_field_col * 20) + 3;
  774.                 toggle_char(i,'\r');
  775.                 }
  776.             if(++cursor_field_col > 3)
  777.                 {
  778.                 cursor_field_col = 0;    /* Wrap around */
  779.                 cursor_row++;
  780.                 }
  781.             }
  782.         }
  783. /*
  784.  *    Restore original condition of cursor control variables.
  785.  */
  786.     cursor_row = old_row;
  787.     cursor_field_col = old_field_col;
  788.     cursor_col = old_col;
  789.     position(cursor_row,cursor_col);
  790.     cursor_on();            /* Turn cursor ON */
  791.     }
  792.  
  793. /************************************************************************/
  794. /*                                    */
  795. /*    Routine:    enable_fcc                    */
  796. /*                                    */
  797. /*    Enable the subset of morse code characters which is required    */
  798. /*    for the FCC amateur radio operator examinations.        */
  799. /*                                    */
  800. /*    Parameters:    "unused"-    An unused argument.        */
  801. /*            "c"    -    The actual character that was    */
  802. /*                    typed (must be CR to be valid).    */
  803. /*                                    */
  804. /*    Returns:    None.                        */
  805. /*                                    */
  806. /************************************************************************/
  807.  
  808. void    enable_fcc(unused,c)
  809.     UINT    unused,c;
  810.     {
  811.     UINT    i,old_row,old_field_col,old_col;
  812.  
  813.     if(c != '\r') return;        /* Must be CR */
  814. /*
  815.  *    Remember original condition of cursor control variables.
  816.  */
  817.     old_row = cursor_row;
  818.     old_field_col = cursor_field_col;
  819.     old_col = cursor_col;
  820.     cursor_off();            /* Turn cursor OFF */
  821. /*
  822.  *    Enable the morse code subset which the FCC requires for amateur tests.
  823.  */
  824.     cursor_row = 1;            /* Start at row 1 */
  825.     cursor_field_col = 0;        /* And first column of fields */
  826.     for(i = 0; i < (sizeof(codes) / sizeof(struct code)); i++)
  827.         {
  828.         if(codes[i].morse)    /* If a valid morse character */
  829.             {
  830.             if(codes[i].fcc_rqrd)
  831.                 {
  832.                 if(! codes[i].enabled)
  833.                     {
  834.                     cursor_col = (cursor_field_col * 20)+3;
  835.                     toggle_char(i,'\r');
  836.                     }
  837.                 }
  838.             else
  839.                 {
  840.                 if(codes[i].enabled)
  841.                     {
  842.                     cursor_col = (cursor_field_col * 20)+3;
  843.                     toggle_char(i,'\r');
  844.                     }
  845.                 }
  846.             if(++cursor_field_col > 3)
  847.                 {
  848.                 cursor_field_col = 0;    /* Wrap around */
  849.                 cursor_row++;
  850.                 }
  851.             }
  852.         }
  853. /*
  854.  *    Restore original condition of cursor control variables.
  855.  */
  856.     cursor_row = old_row;
  857.     cursor_field_col = old_field_col;
  858.     cursor_col = old_col;
  859.     position(cursor_row,cursor_col);
  860.     cursor_on();            /* Turn cursor ON */
  861.     }
  862.  
  863. /************************************************************************/
  864. /*                                    */
  865. /*    Routine:    erase                        */
  866. /*                                    */
  867. /*    Erase the video screen.                        */
  868. /*                                    */
  869. /*    Parameters:    None.                        */
  870. /*                                    */
  871. /*    Returns:    None.                        */
  872. /*                                    */
  873. /************************************************************************/
  874.  
  875. void    erase()
  876.     {
  877.     position(0,0);            /* Position cursor at origin */
  878.     show_char(' ',(25 * 80));    /* Erase screen */
  879.     }
  880.  
  881. /************************************************************************/
  882. /*                                    */
  883. /*    Routine:    erase_window                    */
  884. /*                                    */
  885. /*    Erase the top 24 lines of the video screen.  Initializes the    */
  886. /*    global variables used to handle the screen windowing.        */
  887. /*                                    */
  888. /*    Parameters:    None.                        */
  889. /*                                    */
  890. /*    Returns:    None.                        */
  891. /*                                    */
  892. /************************************************************************/
  893.  
  894. void    erase_window()
  895.     {
  896.     position(0,0);            /* Position cursor at origin */
  897.     cursor_on();            /* And turn it on */
  898.     show_attr(' ',(24 * 80),0x07);    /* Erase window, set attribute */
  899.     empty_window = TRUE;        /* Window is completely empty */
  900.     was_empty = TRUE;        /* Like above ("send_window" only) */
  901.     full_window = FALSE;        /* Window is not completely full */
  902.     insert_row = insert_col = cursor_row = cursor_col = 0;
  903.     }
  904.  
  905. /************************************************************************/
  906. /*                                    */
  907. /*    Routine:    fill_setup_info                    */
  908. /*                                    */
  909. /*    Fill a field entry in the "setup_info" data structure.        */
  910. /*                                    */
  911. /*    Parameters:    "row"    -    The entry row #.        */
  912. /*            "col"    -    The entry field column #.    */
  913. /*            "func"    -     The "field_func" component.    */
  914. /*            "arg"    -    The "field_arg" component.    */
  915. /*            "title"    -    A title to display.        */
  916. /*                                    */
  917. /*    Returns:    None.                        */
  918. /*                                    */
  919. /************************************************************************/
  920.  
  921. void    fill_setup_info(row,col,func,arg,title)
  922.     UINT    row,col;
  923.     void    (*func)();
  924.     UINT    arg;
  925.     char    *title;
  926.     {
  927.     setup_info[row][col].field_used = TRUE;
  928.     setup_info[row][col].field_func = func;
  929.     setup_info[row][col].field_arg = arg;
  930.     position(row,(col * 20) + 6); show_string(title);
  931.     }
  932.  
  933. /************************************************************************/
  934. /*                                    */
  935. /*    Routine:    fill_window                    */
  936. /*                                    */
  937. /*    Called by various routines to read input characters, eliminate    */
  938. /*    those which are inappropriate, and call "put_window" to write    */
  939. /*    them to the screen.  If the current "state" is TRAINER, this    */
  940. /*    function does nothing.  This routine is coroutine with routine    */
  941. /*    "send_window"                            */
  942. /*                                    */
  943. /*    Parameters:    None.                        */
  944. /*                                    */
  945. /*    Returns:    None.                        */
  946. /*                                    */
  947. /************************************************************************/
  948.  
  949. void    fill_window()
  950.     {
  951.     int    c;
  952.  
  953.     switch(state)            /* Figure out how we are to behave */
  954.         {
  955.         case    TRAINER:    /* If in interactive training mode */
  956.             if(! (c = peek_key())) return;    /* If nought to read */
  957.             if(c == FUNC_F9)    /* Skip character? */
  958.                 {
  959.                 get_key();    /* Remove from buffer */
  960.                 if(code_sent)    /* Awaiting an echo? */
  961.                     {
  962.                     had_error = TRUE;
  963.                     c = code_sent;    /* Matches anything */
  964.                     code_sent = NULL;
  965.                     break;
  966.                     }
  967.                 else    return;    /* Flush bogus function key */
  968.                 }
  969.             if(c < 0) return;    /* We have a function key */
  970.             c = toupper(c);    /* Force uppercase */
  971.             get_key();    /* Remove key from buffer */
  972.             if(code_sent)    /* If waiting for user echo */
  973.                 {
  974.                 if(c == code_sent) code_sent = NULL;
  975.                 else
  976.                     {
  977.                     had_error = TRUE;
  978.                     c = 0xB1;    /* Wrong character */
  979.                     }
  980.                 }
  981.             else    return;    /* Flush extra bogus characters */
  982.             break;
  983.  
  984.         case    KEYBOARD:    /* If used as a morse code keyboard */
  985.             if(! (c = peek_key())) return;    /* If nought to read */
  986.             if(c < 0) return;    /* If a function key */
  987.             get_key();    /* Remove key from buffer */
  988.             if(full_window) return;    /* If no room */
  989.             if(c == BS)
  990.                 {
  991.                 put_window(c);
  992.                 return;
  993.                 }
  994.             if(isspace(c))    /* If whitespace */
  995.                 {
  996.                 if(c == '\r') put_window('\n');
  997.                 else put_window(' ');
  998.                 return;
  999.                 }
  1000.             c = toupper(c);    /* Force uppercase */
  1001.             if((c < ' ') || (c > '_')) return;    /* Toss junk */
  1002.             if(! *codes[c - ' '].morse) return;    /* Toss junk */
  1003.             break;
  1004.  
  1005.         case    FROMFILE:    /* If dumping a file in morse code */
  1006.             if(full_window) return;        /* If no room */
  1007.             if((c = fgetc(in_fp)) == EOF) return;    /* If EOF */
  1008.             if(isspace(c))    /* If whitespace */
  1009.                 {
  1010.                 if(c == '\r') return;
  1011.                 if(c == '\n') put_window('\n');
  1012.                 else put_window(' ');
  1013.                 return;
  1014.                 }
  1015.             c = toupper(c);    /* Force uppercase */
  1016.             if((c < ' ') || (c > '_')) return;    /* Toss junk */
  1017. /*
  1018.  *    Note that the following statement only allows characters which are
  1019.  *    explicitly "enabled" to appear on the screen (and hence be sent).
  1020.  */
  1021.             if(! codes[c - ' '].enabled) return;
  1022.             break;
  1023.  
  1024.         case    MAINMENU:    /* Can't be, but maybe in future */
  1025.             return;
  1026.  
  1027.         default:
  1028.             return;
  1029.         }
  1030.     put_window(c);            /* Put future morse code on screen */
  1031.     }
  1032.  
  1033. /************************************************************************/
  1034. /*                                    */
  1035. /*    Routine:    get_key                        */
  1036. /*                                    */
  1037. /*    Read a character from the keyboard and return the value read.    */
  1038. /*    If no key has been pressed return zero, otherwise return a    */
  1039. /*    positive ASCII value if it is a regular key, or the contents    */
  1040. /*    of the "Auxiliary-Byte" value if it is a "special" key (such as    */
  1041. /*    function and other IBM specific keys).  The top eight bits of    */
  1042. /*    return value will be ones if it was a "special" key (ie. the    */
  1043. /*    the return value will be negative).  Uses ROM BIOS service 0x16.*/
  1044. /*                                    */
  1045. /*    Parameters:    None.                        */
  1046. /*                                    */
  1047. /*    Returns:    An integer holding the key read if non-zero.    */
  1048. /*                                    */
  1049. /************************************************************************/
  1050.  
  1051. int    get_key()
  1052.     {
  1053. /*
  1054.  *    See if a character is waiting.
  1055.  */
  1056.     if(! (*key_ready)()) return 0;    /* If nothing to read */
  1057.  
  1058.     inregs.h.ah = 0x00;        /* 0x00: Read next keyboard char */
  1059.     int86(0x16,&inregs,&outregs);    /* Interrupt 0x16 */
  1060.  
  1061.     if(outregs.h.al) return (outregs.h.al & 0x00FF);
  1062. /*
  1063.  *    We have a "special key", return its value.
  1064.  */
  1065.     return (outregs.h.ah | 0xFF00);
  1066.     }
  1067.  
  1068. /************************************************************************/
  1069. /*                                    */
  1070. /*    Routine:    get_fname                    */
  1071. /*                                    */
  1072. /*    Prompt the user for an input filename to open.  This file is    */
  1073. /*    translated by "morse_file" into morse code output.  This    */
  1074. /*    function returns TRUE if a file was successfully opened, FALSE    */
  1075. /*    if a function key was pressed.                    */
  1076. /*                                    */
  1077. /*    Parameters:    None.                        */
  1078. /*                                    */
  1079. /*    Returns:    TRUE if a file was opened, FALSE otherwise.    */
  1080. /*                                    */
  1081. /************************************************************************/
  1082.  
  1083. BOOL    get_fname()
  1084.     {
  1085.     BOOL    had_error;
  1086.     char    buf[81];
  1087.     int    c;
  1088.     UINT    col;
  1089.  
  1090.     position(10,0);
  1091.     show_string(
  1092. "Please type the pathname of the file to be converted to Morse Code.  The name");
  1093.     position(11,0);
  1094.     show_string(
  1095. "should be terminated with a carriage return, (the ENTER key on some keyboards):");
  1096.  
  1097.     had_error = FALSE;        /* Assume OK until proven otherwise */
  1098.     FOREVER
  1099.         {
  1100.         if(had_error)        /* Leave error up until key hits */
  1101.             {
  1102.             while(peek_key() == 0) ;
  1103.             had_error = FALSE;
  1104.             }
  1105.         position(12,0);        /* Position to start of prompt line */
  1106.         show_attr(' ',80,0x70);    /* Blank out rev. video prompt line */
  1107.         position(13,0);
  1108.         show_char(' ',80);    /* Blank out error message */
  1109.         col = 0;
  1110.         FOREVER
  1111.             {
  1112.             if((c = peek_key()) < 0) return FALSE;
  1113.             if(c == 0) continue;
  1114.             get_key();    /* Remove character from buffer */
  1115.             if(c == '\r')
  1116.                 {
  1117.                 buf[col] = NULL;    /* Terminate string */
  1118.                 if((in_fp = fopen(buf,"r")) == NULL)
  1119.                     {
  1120.                     position(13,0);
  1121.                     show_string(
  1122. "The above file pathname doesn't exist as it was typed, please try re-typing it!");
  1123.                     had_error = TRUE;
  1124.                     break;
  1125.                     }
  1126.                 else    return TRUE;
  1127.                 }
  1128.             if(c == BS)    /* Handle backspace processing */
  1129.                 {
  1130.                 if(col > 0) col--;    /* Backup one */
  1131.                 position(12,col);
  1132.                 show_char(' ',1);
  1133.                 continue;
  1134.                 }
  1135.             if(col > 79)
  1136.                 {
  1137.                 position(13,0);
  1138.                 show_string(
  1139. "The file pathname must be 80 or less characters long, please try re-typing it!");
  1140.                 had_error = TRUE;
  1141.                 break;
  1142.                 }
  1143.             buf[col] = c;    /* Put character in buffer */
  1144.             position(12,col++);    /* Position for display */
  1145.             show_char(c,1);    /* Display character on screen */
  1146.             }
  1147.         }
  1148.     }
  1149.  
  1150. /************************************************************************/
  1151. /*                                    */
  1152. /*    Routine:    help                        */
  1153. /*                                    */
  1154. /*    Presents a help screen to the user and waits until a key is    */
  1155. /*    pressed before returning.  Terminates the program if F10 is    */
  1156. /*    pressed.                            */
  1157. /*                                    */
  1158. /*    Parameters:    "list"    -    Points to an array of "screen"    */
  1159. /*                    structures.            */
  1160. /*                                    */
  1161. /*    Returns:    None.                        */
  1162. /*                                    */
  1163. /************************************************************************/
  1164.  
  1165. void    help(list)
  1166.     struct    screen    list[];
  1167.     {
  1168.     UINT    c;
  1169.  
  1170.     cursor_off();            /* Won't need a cursor here */
  1171.     erase();            /* Erase screen */
  1172.     position(24,19);        /* Position to command line */
  1173.     show_string("Type any key to continue (or F10 to QUIT)");
  1174.     simple_border();        /* Draw border on screen */
  1175.     show_screen(list);        /* Put text on screen */
  1176.  
  1177.     while(! (c = get_key())) ;    /* Wait for a character to be typed */
  1178.     if(c == FUNC_F10) terminate();    /* Thats' all folks... */
  1179.     }
  1180.  
  1181. /************************************************************************/
  1182. /*                                    */
  1183. /*    Routine:    init_keyboard                    */
  1184. /*                                    */
  1185. /*    Setup the value of the character-ready "far" function pointer    */
  1186. /*    ("key_ready"), so that it points at the appropriate inline    */
  1187. /*    assembly language routine in array "key_rdy".  See the        */
  1188. /*    "key_ready" function section for more info.  Also flushes the    */
  1189. /*    keyboard type ahead buffer.                    */
  1190. /*                                    */
  1191. /*    Parameters:    None.                        */
  1192. /*                                    */
  1193. /*    Returns:    None.                        */
  1194. /*                                    */
  1195. /************************************************************************/
  1196.  
  1197. void    init_keyboard()
  1198.     {
  1199.     segread(&segregs);        /* Read values of segment registers */
  1200.     *((UINT *) &key_ready + 1) = segregs.ds; /* Set segment part of addr */
  1201.     *((UINT *) &key_ready) = (UINT) key_rdy; /* Set offset part of addr */
  1202.  
  1203.     while(get_key()) ;        /* Suck buffer until nothing left */
  1204.     }
  1205.  
  1206. /************************************************************************/
  1207. /*                                    */
  1208. /*    Routine:    init_setup                    */
  1209. /*                                    */
  1210. /*    Initialize the "setup_info" data structure used by "setup" to    */
  1211. /*    display and parse configuration options.            */
  1212. /*                                    */
  1213. /*    Parameters:    None.                        */
  1214. /*                                    */
  1215. /*    Returns:    None.                        */
  1216. /*                                    */
  1217. /************************************************************************/
  1218.  
  1219. void    init_setup()
  1220.     {
  1221.     char    buf[81];
  1222.     UINT    i;
  1223. /*
  1224.  *    Draw borders on SETUP screen.
  1225.  */
  1226.     position(0,0); show_char(0xC9,1);
  1227.     position(0,1); show_char(0xCD,78);
  1228.     position(0,79); show_char(0xBB,1);
  1229.  
  1230.     for(cursor_row = 1; cursor_row < 16; cursor_row++)
  1231.         {
  1232.         position(cursor_row,0); show_char(0xBA,1);
  1233.         position(cursor_row,79); show_char(0xBA,1);
  1234.         }
  1235.  
  1236.     position(16,0); show_char(0xCC,1);
  1237.     position(16,1); show_char(0xCD,18);
  1238.     position(16,19); show_char(0xCB,1);
  1239.     position(16,20); show_char(0xCD,19);
  1240.     position(16,39); show_char(0xCB,1);
  1241.     position(16,40); show_char(0xCD,19);
  1242.     position(16,59); show_char(0xCB,1);
  1243.     position(16,60); show_char(0xCD,19);
  1244.     position(16,79); show_char(0xB9,1);
  1245.  
  1246.     for(cursor_row = 17; cursor_row < 24; cursor_row++)
  1247.         {
  1248.         position(cursor_row,0); show_char(0xBA,1);
  1249.         if(cursor_row != 20)
  1250.             {
  1251.             position(cursor_row,19); show_char(0xBA,1);
  1252.             position(cursor_row,39); show_char(0xBA,1);
  1253.             position(cursor_row,59); show_char(0xBA,1);
  1254.             position(cursor_row,79); show_char(0xBA,1);
  1255.             }
  1256.         }
  1257.  
  1258.     position(20,19); show_char(0xCC,1);
  1259.     position(20,39); show_char(0xB9,1);
  1260.     position(20,20); show_char(0xCD,19);
  1261.     position(20,59); show_char(0xCC,1);
  1262.     position(20,79); show_char(0xB9,1);
  1263.     position(20,60); show_char(0xCD,19);
  1264. /*
  1265.  *    Initialize all the fields to unused and then selectively relax.
  1266.  */
  1267.     for(cursor_row = 0; cursor_row < 25; cursor_row++)
  1268.         {
  1269.         for(cursor_field_col = 0; cursor_field_col < 4;
  1270.             cursor_field_col++)
  1271.             {
  1272.             setup_info[cursor_row][cursor_field_col].field_used =
  1273.                 FALSE;
  1274.             }
  1275.         }
  1276. /*
  1277.  *    First initialize the morse code character portion of the struct/screen.
  1278.  */
  1279.     cursor_row = 1;            /* Start at row 1 */
  1280.     cursor_field_col = 0;        /* And first column of fields */
  1281.     for(i = 0; i < (sizeof(codes) / sizeof(struct code)); i++)
  1282.         {
  1283.         if(codes[i].morse)    /* If a valid morse character */
  1284.             {
  1285.             buf[0] = i + ' ';    /* Show ASCII translation */
  1286.             buf[2] = buf[1] = ' ';
  1287.             strcpy(&buf[3],codes[i].morse);
  1288.             fill_setup_info(cursor_row,cursor_field_col,
  1289.                 toggle_char,i,buf);
  1290.             cursor_col = (20 * cursor_field_col) + 3;
  1291.             toggle_char(i,'\r'); toggle_char(i,'\r');
  1292.             if(++cursor_field_col > 3)
  1293.                 {
  1294.                 cursor_field_col = 0;    /* Wrap around */
  1295.                 cursor_row++;
  1296.                 }
  1297.             }
  1298.         }
  1299. /*
  1300.  *    Initialize the enable/disable (all) selectors for the morse code chars.
  1301.  */
  1302.     position(cursor_row,(20 * cursor_field_col) + 3); show_char(0x1A,1);
  1303.     fill_setup_info(cursor_row,cursor_field_col++,enable_fcc,NULL,
  1304.             "ENABLE-FCC");
  1305.     position(cursor_row,(20 * cursor_field_col) + 3); show_char(0x1A,1);
  1306.     fill_setup_info(cursor_row,cursor_field_col++,enable_all,NULL,
  1307.             "ENABLE-ALL");
  1308.     position(cursor_row,(20 * cursor_field_col) + 3); show_char(0x1A,1);
  1309.     fill_setup_info(cursor_row,cursor_field_col,disable_all,NULL,
  1310.             "DISABLE-ALL");
  1311. /*
  1312.  *    Initialize the speed adaptation selector switch.
  1313.  */
  1314.     position(17,2); show_string("SPEED ADAPTATION");
  1315.     fill_setup_info(19,0,set_speed_adapt,SELECT0,"NO ADAPTATION");
  1316.     fill_setup_info(20,0,set_speed_adapt,SELECT1,"ADAPT SLOW");
  1317.     fill_setup_info(21,0,set_speed_adapt,SELECT2,"ADAPT MEDIUM");
  1318.     fill_setup_info(22,0,set_speed_adapt,SELECT3,"ADAPT FAST");
  1319.     cursor_row = 19 + speed_adapt;
  1320.     position(cursor_row,(20 * 0) + 3); show_char(0x10,1);
  1321. /*
  1322.  *    Initialize the code group size selection box.
  1323.  */
  1324.     position(17,22); show_string("CODE GROUP SIZE");
  1325.     position(18,23); show_string("(0 = RANDOM)");
  1326.     fill_setup_info(19,1,set_group_size,NULL,"CHARACTERS");
  1327.     position(cursor_row = 19,cursor_col = (20 * 1) + 3);
  1328.     read_uint(group_size,&i,1,'\r');
  1329. /*
  1330.  *    Initialize the code adaptation toggle switch.
  1331.  */
  1332.     position(21,22); show_string("CODE ADAPTATION");
  1333.     fill_setup_info(22,1,toggle_code_adapt,NULL,NULL);
  1334.     position(cursor_row = 22,cursor_col = (20 * 1) + 3);
  1335.     toggle_code_adapt(NULL,'\r'); toggle_code_adapt(NULL,'\r');
  1336. /*
  1337.  *    Initialize the maximum code speed selector switch.
  1338.  */
  1339.     position(17,42); show_string("MAX. CODE SPEED");
  1340.     fill_setup_info(19,2,set_max_speed,SELECT0,"22 WORDS/MIN");
  1341.     fill_setup_info(20,2,set_max_speed,SELECT1,"11 WORDS/MIN");
  1342.     fill_setup_info(21,2,set_max_speed,SELECT2," 7 WORDS/MIN");
  1343.     fill_setup_info(22,2,set_max_speed,SELECT3," 5 WORDS/MIN");
  1344.     cursor_row = 19 + max_speed - 1;
  1345.     position(cursor_row,(20 * 2) + 3); show_char(0x10,1);
  1346. /*
  1347.  *    Initialize delay selection box.
  1348.  */
  1349.     position(17,62); show_string("CODE SPEED DELAY");
  1350.     position(18,63); show_string(".055 SECS/UNIT");
  1351.     fill_setup_info(19,3,set_delay,NULL,"   UNITS");
  1352.     position(cursor_row = 19,cursor_col = (20 * 3) + 3);
  1353.     read_uint(delay,&i,2,'\r');    /* Set initial value */
  1354. /*
  1355.  *    Initialize the oscillator frequency selection box.
  1356.  */
  1357.     position(21,62); show_string("OUTPUT FREQUENCY");
  1358.     fill_setup_info(22,3,set_freq,NULL,"   HERTZ");
  1359.     position(cursor_row = 22,cursor_col = (20 * 3) + 3);
  1360.     read_uint(frequency,&i,4,'\r');    /* Set initial value */
  1361.     }
  1362.  
  1363. /************************************************************************/
  1364. /*                                    */
  1365. /*    Routine:    init_sound                    */
  1366. /*                                    */
  1367. /*    Program the 8253 sound chip to produce the specified tone.    */
  1368. /*                                    */
  1369. /*    Parameters:    "freq" -     The desired frequency in hertz.    */
  1370. /*                                    */
  1371. /*    Returns:    None.                        */
  1372. /*                                    */
  1373. /************************************************************************/
  1374.  
  1375. void    init_sound(freq)
  1376.     UINT    freq;
  1377.     {
  1378.     long    count;
  1379.  
  1380.     if(! freq) freq = 1;        /* Avoid divide by zero */
  1381.  
  1382.     count = 1193180L / freq;
  1383.  
  1384.     if(count < 1L) count = 1L;
  1385.     else if(count > 65535L) count = 65535L;
  1386.  
  1387.     outp(0x43,0xB6);        /* Preamble byte to 8253 (port 0x43) */
  1388.     outp(0x42,count & 0xFFL);    /* Low order byte of count */
  1389.     outp(0x42,(count >> 8) & 0xFFL);    /* high order byte of count */
  1390.     }
  1391.  
  1392. /************************************************************************/
  1393. /*                                    */
  1394. /*    Routine:    init_video                    */
  1395. /*                                    */
  1396. /*    Initialize the video display to text-only, 80 * 25 characters.    */
  1397. /*    If the currently active video display is a monochrome adapter    */
  1398. /*    (or Hercules compatible), leave the display mode alone,        */
  1399. /*    otherwise change the video display mode to 2 (for CGA, EGA and    */
  1400. /*    PC-JR compatibility).  The old contents of the display mode    */
  1401. /*    are preserved for later restoration.  Also, sets the active    */
  1402. /*    video page to be zero and erases it.                */
  1403. /*                                    */
  1404. /*    Parameters:    None.                        */
  1405. /*                                    */
  1406. /*    Returns:    None.                        */
  1407. /*                                    */
  1408. /************************************************************************/
  1409.  
  1410. void    init_video()
  1411.     {
  1412. /*
  1413.  *    Get current video mode.
  1414.  */
  1415.     inregs.h.ah = 0x0F;        /* 0x0F: Get current video mode */
  1416.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  1417.     old_vmode = outregs.h.al;    /* Old video mode */
  1418. /*
  1419.  *    Get current cursor size.
  1420.  */
  1421.     inregs.h.ah = 0x03;        /* 0x03: Read cursor position */
  1422.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  1423.     old_cursor_start = outregs.h.ch;/* Starting line of old cursor */
  1424.     old_cursor_end = outregs.h.cl;    /* Ending line of old cursor */
  1425. /*
  1426.  *    Set new video mode.
  1427.  */
  1428.     if(old_vmode != 7)        /* If not MDA/Hercules */
  1429.         {
  1430.         set_vmode(2);        /* 80*25 color suppressed */
  1431. /*
  1432.  *    Set current page # to be 0.
  1433.  */
  1434.         inregs.h.ah = 0x05;    /* 0x05: Set active display page */
  1435.         inregs.h.al = 0;    /* Make it be page zero */
  1436.         int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  1437.         }
  1438. /*
  1439.  *    Time to erase screen and set initial attribute (normal white on black).
  1440.  */
  1441.     position(0,0);            /* Position screen to origin */
  1442.     show_attr(' ',(80 * 25),0x07);    /* Erase screen and set attribute */
  1443.     cursor_off();            /* And turn cursor off */
  1444.     }
  1445.  
  1446. /************************************************************************/
  1447. /*                                    */
  1448. /*    Routine:    key_ready                    */
  1449. /*                                    */
  1450. /*    The character array "key_rdy" is an inline subroutine which is    */
  1451. /*    called by the "far" function pointer "key_ready".  This was    */
  1452. /*    done for the following reasons:                    */
  1453. /*                                    */
  1454. /*    1.    Microsoft "C" provides good runtime BIOS interface    */
  1455. /*        routines which are used extensively in this program    */
  1456. /*        ("intdos"), but these routines provide no way to get at    */
  1457. /*        the zero flag returned by the BIOS service.  Normally    */
  1458. /*        this isn't a problem since only one BIOS service    */
  1459. /*        requires this: "Report Whether Character Ready".  Of    */
  1460. /*        course, I ended up needing this service!  Thus I had to    */
  1461. /*        write an assembly language interface routine.        */
  1462. /*                                    */
  1463. /*    2.    Given the above requirements the normal approach is to    */
  1464. /*        write the assembly language routine, assembly it and    */
  1465. /*        link it into the main "C" program (which is what I    */
  1466. /*        initially did).  The problem is that this forces anyone    */
  1467. /*        who wishes to change the program to also have a        */
  1468. /*        relatively expensive assembler in addition to the not    */
  1469. /*        inexpensive "C" compiler.  My solution was to place the    */
  1470. /*        assembler code INSIDE the "C" program in a character    */
  1471. /*        array, and then point a function pointer at the array.    */
  1472. /*                                    */
  1473. /*    3.    The problem with the function pointer is that the 8086    */
  1474. /*        architecture is hobbled by what are termed "segments".    */
  1475. /*        The result is that code and data in "C" programs    */
  1476. /*        running on these machines end up in different segments    */
  1477. /*        (which makes putting a function (code), in a array    */
  1478. /*        (data) impossible).  The work around for this was to    */
  1479. /*        make the function pointer be a "far" pointer which    */
  1480. /*        allows a new code segment to be donned when the        */
  1481. /*        function call happens.  The "new" code segment in the    */
  1482. /*        pointer is then initialized to be the "old" data    */
  1483. /*        segment and everything works like magic.  The "C" code    */
  1484. /*        which initializes the function pointer is located in    */
  1485. /*        function "init_keyboard".                */
  1486. /*                                    */
  1487. /*    4.    A warning... While the above trick does work it is not    */
  1488. /*        in the spirit of "C" to muddy the waters between code    */
  1489. /*        and data distinctions!  I justified it this time based    */
  1490. /*        on economic factors (for a presumably "amateur"        */
  1491. /*        audience).  This is not the sort of thing that should    */
  1492. /*        be done for "professional" level activities.        */
  1493. /*                                    */
  1494. /*    5.    If you are converting this to a "C" compiler which does    */
  1495. /*        not have "far" pointers, but DOES allow the code and    */
  1496. /*        data segments to be made the same (ie. produce ".COM"    */
  1497. /*        output files), you can remove the "far" stuff and just    */
  1498. /*        use a regular function pointer.  Another option would    */
  1499. /*        be to use a "kbhit" style function provided by your "C"    */
  1500. /*        compiler to do the same job, but I experienced        */
  1501. /*        interaction between the version I had and some of the    */
  1502. /*        direct BIOS keyboard manipulation that I do.        */
  1503. /*                                    */
  1504. /*    Parameters:    None.                        */
  1505. /*                                    */
  1506. /*    Returns:    TRUE if a character is available, FALSE        */
  1507. /*            otherwise.                    */
  1508. /*                                    */
  1509. /************************************************************************/
  1510.  
  1511. char    key_rdy[] =
  1512.     {
  1513.                 /* key_ready    proc    far    */
  1514.     0x55,            /*    push    bp        */
  1515.     0x8B,0xEC,        /*    mov    bp,sp        */
  1516.     0x57,            /*    push    di        */
  1517.     0x56,            /*    push    si        */
  1518.     0xB4,0x01,        /*    mov    ah,1        */
  1519.     0xCD,0x16,        /*    int    016h        */
  1520.     0x74,0x05,        /*    jz    short empty    */
  1521.     0xB8,0x00,0x01,        /*    mov    ax,1        */
  1522.     0xEB,0x03,        /*    jmp    short end    */
  1523.                 /* empty:            */ 
  1524.     0xB8,0x00,0x00,        /*    mov    ax,0        */
  1525.                 /* end:                */
  1526.     0x5E,            /*    pop    si        */
  1527.     0x5F,            /*    pop    di        */
  1528.     0x8B,0xE5,        /*    mov    sp,bp        */
  1529.     0x5D,            /*    pop    bp        */
  1530.     0xCB            /*    ret            */
  1531.                 /* key_ready    endp        */
  1532.     };
  1533.  
  1534. /************************************************************************/
  1535. /*                                    */
  1536. /*    Routine:    keyboard                    */
  1537. /*                                    */
  1538. /*    Do the morse code keyboard stuff.                */
  1539. /*                                    */
  1540. /*    Parameters:    None.                        */
  1541. /*                                    */
  1542. /*    Returns:    None.                        */
  1543. /*                                    */
  1544. /************************************************************************/
  1545.  
  1546. struct    screen    keyboard_help[] =    /* Morse code keyboard help display */
  1547.     {
  1548.         {1,31,"CODE KEYBOARD HELP" },
  1549.         {3,2,
  1550. "This function allows you to enter ASCII characters from the keyboard which"},
  1551.         {4,2,
  1552. "are converted into Morse Code and \"sent\" using the internal speaker."},
  1553.         {5,2,
  1554. "Unlike the CODE FILE and CODE TRAINING functions, any character for which a"},
  1555.         {6,2,
  1556. "a Morse Code equivalent exists may be sent, whether or not it is enabled."},
  1557.         {8,2,
  1558. "Since some Morse Codes have no direct ASCII equivalents, ASCII characters"},
  1559.         {9,2,
  1560. "have been chosen to represent them for the purposes of the MORSE program."},
  1561.         {10,2,
  1562. "See the SETUP function for the specific ASCII to Morse conversions which are"},
  1563.         {11,2,
  1564. "used.  The SETUP function may also be used to control the Morse Code speed"},
  1565.         {12,2,
  1566. "and oscillator frequency."},
  1567.         {14,2,
  1568. "You are allowed to type-ahead up to a full screen worth of characters.  The"},
  1569.         {15,2,
  1570. "cursor will always highlight the character which is currently being sent."},
  1571.         {17,2,
  1572. "You may use the space bar and carriage return key (called ENTER on some"},
  1573.         {18,2,
  1574. "keyboards), to organize the text you are entering.  The backspace key may"},
  1575.         {19,2,
  1576. "also be used to erase previously entered text which is on the current line"},
  1577.         {20,2,
  1578. "and has not yet been sent."},
  1579.         {0,0,NULL }
  1580.     };
  1581.  
  1582. void    keyboard()
  1583.     {
  1584.     int    c;
  1585.  
  1586.     state = KEYBOARD;        /* Current program activity */
  1587.  
  1588.     FOREVER
  1589.         {
  1590.         erase();        /* Erase screen */
  1591.         position(24,2);        /* Position to 25 line */
  1592.         show_string(
  1593. "[CODE KEYBOARD]   F1:HELP  F2:MAIN MENU  F3:SETUP  F10:QUIT");
  1594.         show_speed();        /* Show current speed/delay */
  1595.         erase_window();        /* Erase/init scroll window */
  1596.         FOREVER
  1597.             {
  1598.             fill_window();        /* Buffer input if any */
  1599.             if((c = peek_key()) < 0)    /* See if a func key */
  1600.                 {
  1601.                 get_key();    /* If so, flush it out */
  1602.                 switch(c)    /* And see what to do */
  1603.                     {
  1604.                     case    FUNC_F1:    /* Help info */
  1605.                         help(keyboard_help);
  1606.                         break;
  1607.  
  1608.                     case    FUNC_F2:    /* Main menu */
  1609.                         return;
  1610.  
  1611.                     case    FUNC_F3:    /* Setup */
  1612.                         setup();
  1613.                         break;
  1614.  
  1615.                     case    FUNC_F10:    /* Quit */
  1616.                         terminate();
  1617.  
  1618.                     default:
  1619.                         break;
  1620.                     }
  1621.                 break;
  1622.                 }
  1623.             else    send_window();    /* Otherwise, send code! */
  1624.             }
  1625.         }
  1626.     }
  1627.  
  1628. /************************************************************************/
  1629. /*                                    */
  1630. /*    Routine:    main_menu                    */
  1631. /*                                    */
  1632. /*    Display the main menu and ask what to do.            */
  1633. /*                                    */
  1634. /*    Parameters:    None.                        */
  1635. /*                                    */
  1636. /*    Returns:    None.                        */
  1637. /*                                    */
  1638. /************************************************************************/
  1639.  
  1640. struct    screen    m_menu[] =        /* Menu display (dup command line) */
  1641.     {
  1642.         {1,32,"MORSE MAIN MENU" },
  1643.         {3,15,"Please choose one of the following function keys:"},
  1644.         {5,2,
  1645. "F1    HELP    This function gets information about using the MORSE program."},
  1646.         {6,16,
  1647.         "The HELP key works anywhere and provides information"},
  1648.         {7,16,
  1649.         "appropriate to the function being used." },
  1650.         {9,2,
  1651. "F3   SETUP    This function controls parameters such as the allowed Morse"},
  1652.         {10,16,
  1653.         "Code characters, the code speed and the oscillator frequency."},
  1654.         {12,2,
  1655. "F4  KEYBOARD  This function allows you to enter characters from the keyboard"},
  1656.         {13,16,
  1657.         "and have them automatically converted to Morse Code and sent."},
  1658.         {15,2,
  1659. "F5    FILE    This function prompts you for the name of a text file which is"},
  1660.         {16,16,
  1661.         "then read and converted to Morse Code."},
  1662.         {18,2,
  1663. "F6  TRAINING  This function sends random Morse Code groups which are echoed"},
  1664.         {19,16,
  1665.         "in turn by typing the ASCII equivalents on the keyboard."},
  1666.         {21,2,
  1667. "F10   QUIT    This function key immediately terminates the MORSE program and"},
  1668.         {22,16,
  1669.         "returns control to DOS.  Like HELP, QUIT works anywhere."},
  1670.         {0,0,NULL }
  1671.     };
  1672. struct    screen    m_m_help[] =        /* Main menu help display */
  1673.     {
  1674.         {1,30,"MORSE MAIN MENU HELP" },
  1675.         {3,2,
  1676. "You select what you want to do by pressing the function key corresponding to"},
  1677.         {4,2,
  1678. "the listed activity.  For the MAIN MENU your choices are also listed across"},
  1679.         {5,2,
  1680. "the bottom of the screen in a shorthand format.  For the remainder of the"},
  1681.         {6,2,
  1682. "MORSE program your choices are ONLY listed on the bottom of the screen!"},
  1683.         {8,2,
  1684. "Please contact me if you come up with any improvements (or find problems)."},
  1685.         {9,2,"I can be reached at:"},
  1686.         {10,31,"KIRK BAILEY, N7CCB"},
  1687.         {11,33,"P.O. Box 1702"},
  1688.         {12,30,"Corvallis, OR 97339"},
  1689.         {14,2,
  1690. "Due to limited time I can't distribute copies of MORSE myself.  Since it is"},
  1691.         {15,2,
  1692. "PUBLIC DOMAIN you may be able to get the program and source code (Microsoft"},
  1693.         {16,2,
  1694. "\"C\"), from a friend or BBS/PBBS, etc.  If all else fails you may obtain the"},
  1695.         {17,2,
  1696. "latest version for $25 (IBM-PC format 5.25\", 360K floppy), from:"},
  1697.         {19,24,"COMTEK INFORMATION SYSTEMS, INC."},
  1698.         {20,31,"P.O. Box 3004-246"},
  1699.         {21,30,"Corvallis, OR 97339"},
  1700.         {22,33,"(503) 758-7003"},
  1701.         {0,0,NULL }
  1702.     };
  1703.  
  1704. void    main_menu()
  1705.     {
  1706.     int    c;
  1707.  
  1708.     FOREVER
  1709.         {
  1710.         state = MAINMENU;    /* Current program state */
  1711.         cursor_off();        /* Turn cursor off */
  1712.         erase();        /* Erase screen */
  1713.         simple_border();    /* Draw border */
  1714.         show_screen(m_menu);    /* Display menu */
  1715.         position(24,1);        /* Position to 25 line */
  1716.         show_string(
  1717. "F1:HELP  F3:SETUP  F4:CODE KEYBOARD  F5:CODE FILE  F6:CODE TRAINING  F10:QUIT");
  1718.  
  1719.         FOREVER
  1720.             {
  1721.             while(! (c = get_key())) ;    /* Wait for something */
  1722.             switch(c)        /* See what we are to do */
  1723.                 {
  1724.                 case    FUNC_F1:    /* Help screen */
  1725.                     help(m_m_help);
  1726.                     break;
  1727.  
  1728.                 case    FUNC_F3:    /* Configure program */
  1729.                     setup();
  1730.                     break;
  1731.  
  1732.                 case    FUNC_F4:    /* Morse keyboard */
  1733.                     keyboard();
  1734.                     break;
  1735.  
  1736.                 case    FUNC_F5:    /* Morse file */
  1737.                     morse_file();
  1738.                     break;
  1739.  
  1740.                 case    FUNC_F6:    /* Morse training */
  1741.                     training();
  1742.                     break;
  1743.  
  1744.                 case    FUNC_F10:    /* Terminate */
  1745.                     terminate();
  1746.  
  1747.                 default:
  1748.                     continue;
  1749.                 }
  1750.             break;
  1751.             }
  1752.         }
  1753.     }
  1754.  
  1755. /************************************************************************/
  1756. /*                                    */
  1757. /*    Routine:    morse_file                    */
  1758. /*                                    */
  1759. /*    Translate an input file into morse code.            */
  1760. /*                                    */
  1761. /*    Parameters:    None.                        */
  1762. /*                                    */
  1763. /*    Returns:    None.                        */
  1764. /*                                    */
  1765. /************************************************************************/
  1766.  
  1767. struct    screen    file_help[] =        /* Morse code file help display */
  1768.     {
  1769.         {1,33,"CODE FILE HELP" },
  1770.         {3,2,
  1771. "This function asks you for a pathname for an ASCII file to be read,"},
  1772.         {4,2,
  1773. "converted into Morse Code, and \"sent\" using the internal speaker.  The"},
  1774.         {5,2,
  1775. "characters which are converted are limited to those which have Morse Code"},
  1776.         {6,2,
  1777. "equivalents AND which are \"enabled\".  The SETUP (F3), function may be used"},
  1778.         {7,2,
  1779. "to modify which characters are enabled.  Characters which cannot be"},
  1780.         {8,2,
  1781. "converted are ignored in the ASCII file."},
  1782.         {10,2,
  1783. "Since some Morse Codes have no direct ASCII equivalents, ASCII characters"},
  1784.         {11,2,
  1785. "have been chosen to represent them for the purposes of the MORSE program."},
  1786.         {12,2,
  1787. "See the SETUP function for the specific ASCII to Morse conversions which are"},
  1788.         {13,2,
  1789. "used.  The SETUP function may also be used to control the Morse Code speed"},
  1790.         {14,2,
  1791. "and oscillator frequency."},
  1792.         {16,2,
  1793. "When the ASCII file has been completely converted and sent you are prompted"},
  1794.         {17,2,
  1795. "for another ASCII file pathname to be converted and sent."},
  1796.         {0,0,NULL }
  1797.     };
  1798.  
  1799. void    morse_file()
  1800.     {
  1801.     BOOL    file_open;
  1802.     int    c;
  1803.  
  1804.     state = FROMFILE;        /* Currently sending a file as morse */
  1805.  
  1806.     FOREVER
  1807.         {
  1808.         cursor_off();        /* Turn cursor off */
  1809.         erase();        /* Erase screen */
  1810.         position(24,2);        /* Position to 25 line */
  1811.         show_string(
  1812. "[CODE FILE]       F1:HELP  F2:MAIN MENU  F3:SETUP  F10:QUIT");
  1813.         show_speed();        /* Show current speed/delay */
  1814.         file_open = get_fname();    /* Read desired filename */
  1815.         erase_window();        /* Erase/init scroll window */
  1816.         FOREVER
  1817.             {
  1818.             if((c = get_key()) < 0)    /* See if a func key */
  1819.                 {
  1820.                 switch(c)    /* And see what to do */
  1821.                     {
  1822.                     case    FUNC_F1:    /* Help info */
  1823.                         help(file_help);
  1824.                         break;
  1825.  
  1826.                     case    FUNC_F2:    /* Main menu */
  1827.                         if(file_open) fclose(in_fp);
  1828.                         return;
  1829.  
  1830.                     case    FUNC_F3:    /* Setup */
  1831.                         setup();
  1832.                         break;
  1833.  
  1834.                     case    FUNC_F10:    /* Quit */
  1835.                         terminate();
  1836.  
  1837.                     default:
  1838.                         break;
  1839.                     }
  1840.                 break;
  1841.                 }
  1842.             fill_window();    /* Buffer input if any */
  1843.             send_window();    /* And send code! */
  1844.             if(empty_window)    /* See if we are done */
  1845.                 {
  1846.                 if((c = fgetc(in_fp)) == EOF) break;
  1847.                 else ungetc(c,in_fp);    /* Put char back */
  1848.                 }
  1849.             }
  1850.         if(file_open) fclose(in_fp);    /* Close input file */
  1851.         }
  1852.     }
  1853.  
  1854. /************************************************************************/
  1855. /*                                    */
  1856. /*    Routine:    peek_key                    */
  1857. /*                                    */
  1858. /*    Like "get_key", except the character is just looked at, not    */
  1859. /*    actually removed from the ROM BIOS character buffer.        */
  1860. /*                                    */
  1861. /*    Parameters:    None.                        */
  1862. /*                                    */
  1863. /*    Returns:    An integer holding the next key to be read if    */
  1864. /*            one exists, otherwise zero (note special keys    */
  1865. /*            are reported as negative #'s as in "get_key").    */
  1866. /*                                    */
  1867. /************************************************************************/
  1868.  
  1869. int    peek_key()
  1870.     {
  1871. /*
  1872.  *    See if a character is waiting.
  1873.  */
  1874.     if(! (*key_ready)()) return 0;    /* If nothing to read */
  1875.  
  1876.     inregs.h.ah = 0x01;        /* 0x01: Report whether char ready */
  1877.     int86(0x16,&inregs,&outregs);    /* Interrupt 0x16 */
  1878. /*
  1879.  *    Note that the above ROM BIOS service is also used by "key_ready", but
  1880.  *    in this case we are using it to peek at the next character to be read.
  1881.  */
  1882.     if(outregs.h.al) return (outregs.h.al & 0x00FF);
  1883. /*
  1884.  *    We have a "special key", return its value.
  1885.  */
  1886.     return (outregs.h.ah | 0xFF00);
  1887.     }
  1888.  
  1889. /************************************************************************/
  1890. /*                                    */
  1891. /*    Routine:    position                    */
  1892. /*                                    */
  1893. /*    Position the cursor to a specified row and column on the video    */
  1894. /*    screen.                                */
  1895. /*                                    */
  1896. /*    Parameters:    "row"    -    The starting row #.        */
  1897. /*            "col"    -    The starting column #.        */
  1898. /*                                    */
  1899. /*    Returns:    None.                        */
  1900. /*                                    */
  1901. /************************************************************************/
  1902.  
  1903. void    position(row,col)
  1904.     UINT    row,col;
  1905.     {
  1906.     inregs.h.ah = 0x02;        /* 0x02: Set cursor position */
  1907.     inregs.h.dh = row;        /* Set row # */
  1908.     inregs.h.dl = col;        /* Set column # */
  1909.     inregs.h.bh = 0;        /* Screen page is always zero */
  1910.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  1911.     }
  1912.  
  1913. /************************************************************************/
  1914. /*                                    */
  1915. /*    Routine:    put_window                    */
  1916. /*                                    */
  1917. /*    Write a character to the screen window.  This (and its caller,    */
  1918. /*    "fill_window"), are coroutine with "send_window" which takes    */
  1919. /*    characters from the screen window and sends them as morse code.    */
  1920. /*                                    */
  1921. /*    Parameters:    "c"    -    The character to write.        */
  1922. /*                                    */
  1923. /*    Returns:    None.                        */
  1924. /*                                    */
  1925. /************************************************************************/
  1926.  
  1927. void    put_window(c)
  1928.     UINT    c;
  1929.     {
  1930.     UINT    row,col;
  1931. /*
  1932.  *    The following code handles the MORSE-KEYBOARD and MORSE-FILE modes.
  1933.  */
  1934.     if(state != TRAINER)
  1935.         {
  1936. /*
  1937.  *    The following statement reads the actual cursor position since
  1938.  *    "send_window" may have updated the values of "cursor_row" and
  1939.  *    "cursor_col" without actually updating the cursor position when
  1940.  *    "put_window" executes.
  1941.  */
  1942.         read_position(&row,&col);    /* Read current cursor pos. */
  1943.  
  1944.         cursor_off();        /* Turn off cursor for a bit */
  1945. /*
  1946.  *    The following statement checks to see if a scroll operation was
  1947.  *    prevented (by the window being full), the last time we were here and
  1948.  *    does one if so.
  1949.  */
  1950.         if(insert_row > 23)
  1951.             {
  1952.             scroll_window();    /* Scroll the window */
  1953.             row--;        /* Update after scroll */
  1954.             cursor_row--;    /* Update after scroll */
  1955.             insert_row--;
  1956.             }
  1957.  
  1958.         if(c == '\n')        /* Is it a carriage return? */
  1959.             {
  1960.             empty_window = FALSE;    /* Enable output coroutine */
  1961.             insert_row++;    /* If so update insertion point */
  1962.             insert_col = 0;
  1963.             }
  1964.         else if(c == BS)    /* Is it backspace? */
  1965.             {
  1966.             if(insert_row > cursor_row)
  1967.                 {
  1968.                 if(insert_col > 0)
  1969.                     {
  1970.                     insert_col--;
  1971.                     position(insert_row,insert_col);
  1972.                     show_char(' ',1);
  1973.                     }
  1974.                 }
  1975.             else
  1976.                 {
  1977.                 if(insert_col > (cursor_col + 1))
  1978.                     {
  1979.                     insert_col--;
  1980.                     position(insert_row,insert_col);
  1981.                     show_char(' ',1);
  1982.                     }
  1983.                 }
  1984.             }
  1985.         else
  1986.             {
  1987.             empty_window = FALSE;    /* Enable output coroutine */
  1988.             position(insert_row,insert_col++);
  1989.             show_char(c,1);    /* Out we go */
  1990.             }
  1991.         if(insert_col > 79)    /* Need to fake a CR */
  1992.             {
  1993.             insert_row++;
  1994.             insert_col = 0;
  1995.             }
  1996.         if(insert_row > 23)    /* Need to try scrolling the window */
  1997.             {
  1998.             if(cursor_row == 0)    /* Assuming we can... */
  1999.                 {
  2000.                 full_window = TRUE;    /* Nope, full up */
  2001.                 }
  2002.             else
  2003.                 {
  2004.                 scroll_window();/* Scroll the window */
  2005.                 row--;        /* Update after scroll */
  2006.                 cursor_row--;    /* Update after scroll */
  2007.                 insert_row--;
  2008.                 }
  2009.             }
  2010.         position(row,col);    /* Put cursor back correctly */
  2011.         cursor_on();        /* Turn cursor back on */
  2012.         }
  2013. /*
  2014.  *    The following code handle the MORSE-TRAINING mode.
  2015.  */
  2016.     else
  2017.         {
  2018.         position(cursor_row,cursor_col++);
  2019.         show_char(c,1);        /* Write TRAINER character out */
  2020.         if(cursor_col > 79)    /* Need to fake a CR */
  2021.             {
  2022.             cursor_row++;
  2023.             cursor_col = 0;
  2024.             }
  2025.         if(cursor_row > 23)    /* Need to scroll the window */
  2026.             {
  2027.             scroll_window();/* Scroll the window */
  2028.             cursor_row--;    /* Update after scroll */
  2029.             }
  2030.         position(cursor_row,cursor_col);    /* Update cursor */
  2031.         }
  2032.     }
  2033.  
  2034. /************************************************************************/
  2035. /*                                    */
  2036. /*    Routine:    read_char                    */
  2037. /*                                    */
  2038. /*    Read the character on the screen at the current cursor        */
  2039. /*    position.                            */
  2040. /*                                    */
  2041. /*    Parameters:    None.                        */
  2042. /*                                    */
  2043. /*    Returns:    The character read.                */
  2044. /*                                    */
  2045. /************************************************************************/
  2046.  
  2047. UINT    read_char()
  2048.     {
  2049.     inregs.h.ah = 0x08;        /* 0x08: Read char and attribute */
  2050.     inregs.h.bh = 0;        /* Always use page zero */
  2051.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  2052.     return    (outregs.h.al);
  2053.     }
  2054.  
  2055. /************************************************************************/
  2056. /*                                    */
  2057. /*    Routine:    read_position                    */
  2058. /*                                    */
  2059. /*    Reads the current cursor position.                */
  2060. /*                                    */
  2061. /*    Parameters:    "row"    -    Points where to return row #.    */
  2062. /*            "col"    -    Points where to return col #.    */
  2063. /*                                    */
  2064. /*    Returns:    None.                        */
  2065. /*                                    */
  2066. /************************************************************************/
  2067.  
  2068. void    read_position(row,col)
  2069.     UINT    *row,*col;
  2070.     {
  2071.     inregs.h.ah = 0x03;        /* 0x03: Read cursor position */
  2072.     inregs.h.bh = 0;        /* Always use page zero */
  2073.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  2074.     *row = outregs.h.dh;        /* Current row # */
  2075.     *col = outregs.h.dl;        /* Current column # */
  2076.     }
  2077.  
  2078. /************************************************************************/
  2079. /*                                    */
  2080. /*    Routine:    read_uint                    */
  2081. /*                                    */
  2082. /*    Read an unsigned integer from the user.                */
  2083. /*                                    */
  2084. /*    Parameters:    "existing"-    The value to use if the user    */
  2085. /*                    types a function key prior to    */
  2086. /*                    terminating the number (CR).    */
  2087. /*            "result"-    A pointer to the UINT to store    */
  2088. /*                    the result in.            */
  2089. /*            "size"    -    The numeric field size.        */
  2090. /*            "c"    -    The actual character that was    */
  2091. /*                    typed.                */
  2092. /*                                    */
  2093. /*    Returns:    TRUE if a non-digit (non-CR), was pressed.  The    */
  2094. /*            "existing" value will be displayed.  FALSE if a    */
  2095. /*            new value is read (i.e. 0 or more digits    */
  2096. /*            followed by a CR).                */
  2097. /*                                    */
  2098. /************************************************************************/
  2099.  
  2100. BOOL    read_uint(existing,result,size,c)
  2101.     UINT    existing,*result,size,c;
  2102.     {
  2103.     BOOL    r_value;
  2104.     char    buf[81];
  2105.     UINT    count;
  2106.  
  2107.     position(cursor_row,cursor_col);
  2108.     show_attr(' ',size,0x70);    /* Reverse video data entry field */
  2109.  
  2110.     count = 0;            /* No digits as yet */
  2111.  
  2112.     FOREVER
  2113.         {
  2114.         if(c == '\r')        /* Was it a carriage return? */
  2115.             {
  2116.             if(count)    /* Have we had any digits yet? */
  2117.                 {
  2118.                 buf[count] = NULL;    /* Yes, terminate */
  2119.                 *result = atoi(buf);    /* Convert value */
  2120.                 r_value = FALSE;    /* Signal success */
  2121.                 break;
  2122.                 }
  2123.             else        /* Nothing yet, return "existing" */
  2124.                 {
  2125.                 *result = existing;
  2126.                 r_value = FALSE;    /* Signal success */
  2127.                 break;
  2128.                 }
  2129.             }
  2130.         else if(c == BS)    /* Was it a backspace? */
  2131.             {
  2132.             if(count)    /* Anything to remove? */
  2133.                 {
  2134.                 count--;    /* Backup */
  2135.                 position(cursor_row,cursor_col + count);
  2136.                 show_char(' ',1);
  2137.                 }
  2138.             }
  2139.         else if((c >= '0') && (c <= '9'))    /* Was it a digit? */
  2140.             {
  2141.             buf[count] = c;    /* Remember digit for later */
  2142.             show_char(c,1);    /* Echo character */
  2143.             if(++count != size) position(cursor_row,
  2144.                             cursor_col + count);
  2145.             else
  2146.                 {
  2147.                 buf[count] = NULL;
  2148.                 *result = atoi(buf);    /* Full field, quit */
  2149.                 r_value = FALSE;    /* Signal success */
  2150.                 break;
  2151.                 }
  2152.             }
  2153.         else            /* Must be an error/function key */
  2154.             {
  2155.             *result = existing;
  2156.             r_value = TRUE;
  2157.             break;
  2158.             }
  2159. /*
  2160.  *    Get the next character.
  2161.  */
  2162.         while(! (c = peek_key())) ;    /* Wait for something */
  2163.         if((c == '\r') || (c == BS) || isdigit(c)) get_key();
  2164.         }
  2165. /*
  2166.  *    Time to return, restore field to normal and display whatever result we
  2167.  *    have decided on.
  2168.  */
  2169.     position(cursor_row,cursor_col);
  2170.     show_attr(' ',size,0x07);    /* re-reverse-video field */
  2171.     itoa(*result,buf,10);        /* Convert result to ASCII */
  2172.     buf[size] = NULL;        /* Terminate at field width */
  2173.     show_string(buf);        /* Display result */
  2174.     position(cursor_row,cursor_col);
  2175.     return r_value;
  2176.     }
  2177.  
  2178. /************************************************************************/
  2179. /*                                    */
  2180. /*    Routine:    restore_video                    */
  2181. /*                                    */
  2182. /*    Restore original video mode and default cursor size.        */
  2183. /*                                    */
  2184. /*    Parameters:    None.                        */
  2185. /*                                    */
  2186. /*    Returns:    None.                        */
  2187. /*                                    */
  2188. /************************************************************************/
  2189.  
  2190. void    restore_video()
  2191.     {
  2192.     position(0,0);            /* Position cursor at origin */
  2193.     show_attr(' ',(25 * 80),0x07);    /* Erase screen, nuke reverse video */
  2194.  
  2195.     if(old_vmode != 7) set_vmode(old_vmode);    /* If ! MDA/Hercules */
  2196. /*
  2197.  *    Restore cursor size to what it was.
  2198.  */
  2199.     inregs.h.ch = old_cursor_start;    /* Original cursor start */
  2200.     inregs.h.cl = old_cursor_end;    /* Original cursor ending */
  2201.     inregs.h.ah = 0x01;        /* 0x01: Set cursor size */
  2202.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  2203.     }
  2204.  
  2205. /************************************************************************/
  2206. /*                                    */
  2207. /*    Routine:    scroll_window                    */
  2208. /*                                    */
  2209. /*    Scroll screen window (top 24 lines), up one line.        */
  2210. /*                                    */
  2211. /*    Parameters:    None.                        */
  2212. /*                                    */
  2213. /*    Returns:    None.                        */
  2214. /*                                    */
  2215. /************************************************************************/
  2216.  
  2217. void    scroll_window()
  2218.     {
  2219.     inregs.h.ah = 0x06;        /* 0x06: Scroll window up */
  2220.     inregs.h.al = 1;        /* # of lines to scroll */
  2221.     inregs.h.ch = 0;        /* Upper row # */
  2222.     inregs.h.cl = 0;        /* Left column # */
  2223.     inregs.h.dh = 23;        /* Lower row # */
  2224.     inregs.h.dl = 79;        /* Right column # */
  2225.     inregs.h.bh = 0x07;        /* Attribute: normal white on black */
  2226.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  2227.     }
  2228.  
  2229. /************************************************************************/
  2230. /*                                    */
  2231. /*    Routine:    send                        */
  2232. /*                                    */
  2233. /*    Send a morse character out the speaker.                */
  2234. /*                                    */
  2235. /*    Parameters:    "str" -        A pointer to a string which    */
  2236. /*                    consists of '.' for morse dots,    */
  2237. /*                    and '-'s for morse dashes.    */
  2238. /*                                    */
  2239. /*    Returns:    None.                        */
  2240. /*                                    */
  2241. /************************************************************************/
  2242.  
  2243. void    send(str)
  2244.     char    *str;
  2245.     {
  2246.     UINT    dash;
  2247.  
  2248.     if(! *str) return;        /* Zero length string */
  2249.  
  2250.     dash = 3 * max_speed;        /* Dash is three times length of dot */
  2251.  
  2252.     FOREVER
  2253.         {
  2254.         sound_on();        /* Begin dot/dash */
  2255.         if(*str++ == '.') wait_tick(max_speed);    /* Dot time */
  2256.         else wait_tick(dash);
  2257.         sound_off();        /* Finish dot/dash */
  2258.         if(! *str) return;    /* If all done */
  2259.         wait_tick(max_speed);    /* Otherwise inter-dot/dash gap */
  2260.         }
  2261.     }
  2262.  
  2263. /************************************************************************/
  2264. /*                                    */
  2265. /*    Routine:    send_window                    */
  2266. /*                                    */
  2267. /*    Send the next legal character from the window out as a morse    */
  2268. /*    code character.  This routine is coroutine with "fill_window"    */
  2269. /*    (and the routine it calls, "put_window").            */
  2270. /*                                    */
  2271. /*    Parameters:    None.                        */
  2272. /*                                    */
  2273. /*    Returns:    None.                        */
  2274. /*                                    */
  2275. /************************************************************************/
  2276.  
  2277. void    send_window()
  2278.     {
  2279.     BOOL    was_space;
  2280.     char    *s;
  2281.     UINT    c;
  2282.  
  2283.     if(empty_window) return;    /* If nothing to send */
  2284.  
  2285.     was_space = FALSE;        /* Set to TRUE if a space found */
  2286.  
  2287.     c = read_char();        /* Read next char off window */
  2288.     FOREVER
  2289.         {
  2290.         if(cursor_row == insert_row)
  2291.             {
  2292.             cursor_col++;    /* Update cursor column # */
  2293.             if(cursor_col == insert_col)    /* Window now empty */
  2294.                 {
  2295.                 empty_window = TRUE;
  2296.                 break;
  2297.                 }
  2298.             }
  2299.         else
  2300.             {
  2301.             if(cursor_col < 79) cursor_col++;
  2302.             else
  2303.                 {
  2304.                 full_window = FALSE;    /* Enable coroutine */
  2305.                 cursor_col = 0;
  2306.                 cursor_row++;
  2307.                 if((cursor_row == insert_row) &&
  2308.                     (cursor_col == insert_col))
  2309.                     {
  2310.                     empty_window = TRUE;
  2311.                     break;
  2312.                     }
  2313.                 }
  2314.             }
  2315.         if(c == ' ')        /* If character was a space */
  2316.             {
  2317.             was_space = TRUE;
  2318.             position(cursor_row,cursor_col);
  2319.             c = read_char();    /* Read next char off window */
  2320.             continue;    /* Until find a non space */
  2321.             }
  2322.         else    break;        /* Otherwise, we can send this one */
  2323.         }
  2324.  
  2325.     if(c == ' ')            /* If all we got was spaces */
  2326.         {
  2327.         position(cursor_row,cursor_col);    /* Update cursor */
  2328.         return;
  2329.         }
  2330.  
  2331.     s = codes[c - ' '].morse;    /* Get morse coding to send */
  2332.  
  2333.     if(was_space || was_empty)    /* Do we need a inter-word gap? */
  2334.         {
  2335.         if(empty_window) was_empty = TRUE;    /* Remember */
  2336.         else was_empty = FALSE;
  2337.         wait_tick((7 * max_speed) + (2 * delay));
  2338.         }
  2339.     else                /* Otherwise, put a inter-char gap */
  2340.         {
  2341.         if(empty_window) was_empty = TRUE;    /* Remember */
  2342.         else was_empty = FALSE;
  2343.         wait_tick((3 * max_speed) + delay);
  2344.         }
  2345.     send(s);            /* Finally, send the character */
  2346.  
  2347.     position(cursor_row,cursor_col);    /* Update cursor position */
  2348.     }
  2349.  
  2350. /************************************************************************/
  2351. /*                                    */
  2352. /*    Routine:    set_delay                    */
  2353. /*                                    */
  2354. /*    Used by the "setup" function to select the code speed delay    */
  2355. /*    value.  This allows an additional inter-character delay to be    */
  2356. /*    inserted whose length is equal to the delay value times 55    */
  2357. /*    milliseconds.  An additional inter-word delay is also inserted    */
  2358. /*    which is twice as long as the inter-character delay.        */
  2359. /*                                    */
  2360. /*    Parameters:    "unused"-    An unused parameter.        */
  2361. /*            "c"    -    The actual character that was    */
  2362. /*                    typed.                */
  2363. /*                                    */
  2364. /*    Returns:    None.                        */
  2365. /*                                    */
  2366. /************************************************************************/
  2367.  
  2368. void    set_delay(unused,c)
  2369.     UINT    unused,c;
  2370.     {
  2371.     UINT    i;
  2372.  
  2373.     if(read_uint(delay,&i,2,c)) return;    /* 2 digit field width */
  2374.     delay = i;            /* Update delay count */
  2375.     show_speed();            /* Update command line display */
  2376.     }
  2377.  
  2378. /************************************************************************/
  2379. /*                                    */
  2380. /*    Routine:    set_freq                    */
  2381. /*                                    */
  2382. /*    Used by the "setup" function to set the sidetone oscillator    */
  2383. /*    frequency.                            */
  2384. /*                                    */
  2385. /*    Parameters:    "unused"-    An unused parameter.        */
  2386. /*            "c"    -    The actual character that was    */
  2387. /*                    typed.                */
  2388. /*                                    */
  2389. /*    Returns:    None.                        */
  2390. /*                                    */
  2391. /************************************************************************/
  2392.  
  2393. void    set_freq(unused,c)
  2394.     UINT    unused,c;
  2395.     {
  2396.     UINT    i;
  2397.  
  2398.     if(read_uint(frequency,&i,4,c)) return;    /* 4 digit field width */
  2399.     init_sound(frequency = i);    /* Update frequency value */
  2400.     }
  2401.  
  2402. /************************************************************************/
  2403. /*                                    */
  2404. /*    Routine:    set_group_size                    */
  2405. /*                                    */
  2406. /*    Used by the "setup" function to set the code group size for the    */
  2407. /*    TRAINER function.  If a group size of zero is entered the code    */
  2408. /*    group size is random up to the maximum (MAX_CG_SIZE).        */
  2409. /*                                    */
  2410. /*    Parameters:    "unused"-    An unused parameter.        */
  2411. /*            "c"    -    The actual character that was    */
  2412. /*                    typed.                */
  2413. /*                                    */
  2414. /*    Returns:    None.                        */
  2415. /*                                    */
  2416. /************************************************************************/
  2417.  
  2418. void    set_group_size(unused,c)
  2419.     UINT    unused,c;
  2420.     {
  2421.     UINT    i;
  2422.  
  2423.     if(read_uint(group_size,&i,1,c)) return;
  2424.     group_size = i;            /* Update code group size */
  2425.     }
  2426.  
  2427. /************************************************************************/
  2428. /*                                    */
  2429. /*    Routine:    set_max_speed                    */
  2430. /*                                    */
  2431. /*    Used by the "setup" function to select the maximum rate at    */
  2432. /*    which code will be sent.  Code may be sent at slower than this    */
  2433. /*    rate by having a non-zero "delay" value.            */
  2434. /*                                    */
  2435. /*    Parameters:    "rate"    -    The desired maximum rate.  0    */
  2436. /*                    indicates a speed of 22WPM is    */
  2437. /*                    desired (equal to a "max_speed"    */
  2438. /*                    value of 1).  1 indicates 11WPM    */
  2439. /*                    or a "max_speed" value of 2,    */
  2440. /*                    and so on up to a value of 3    */
  2441. /*                    which means a speed of 5WPM and    */
  2442. /*                    value for "max_speed" of 4.    */
  2443. /*            "c"    -    The actual character that was    */
  2444. /*                    typed (must be CR to be valid).    */
  2445. /*                                    */
  2446. /*    Returns:    None.                        */
  2447. /*                                    */
  2448. /************************************************************************/
  2449.  
  2450. void    set_max_speed(rate,c)
  2451.     UINT    rate,c;
  2452.     {
  2453.     if(c != '\r') return;        /* Must be CR */
  2454.     switch_selector(rate);        /* Update "setup" screen display */
  2455.     max_speed = rate + 1;        /* Update maximum speed */
  2456.     show_speed();            /* Update speed on command line */
  2457.     }
  2458.  
  2459. /************************************************************************/
  2460. /*                                    */
  2461. /*    Routine:    set_speed_adapt                    */
  2462. /*                                    */
  2463. /*    Used by the "setup" function to select the code speed        */
  2464. /*    adaptation rate.  The basic idea is that more misses while in    */
  2465. /*    TRAINER mode means that the code rate should slow down and vice    */
  2466. /*    versa.                                */
  2467. /*                                    */
  2468. /*    Parameters:    "rate"    -    The desired adaptation rate.  0    */
  2469. /*                    indicates no adaptation is    */
  2470. /*                    desired, 1-3 indicate that    */
  2471. /*                    slow to fast adaptation is    */
  2472. /*                    desired respectively.        */
  2473. /*            "c"    -    The actual character that was    */
  2474. /*                    typed (must be CR to be valid).    */
  2475. /*                                    */
  2476. /*    Returns:    None.                        */
  2477. /*                                    */
  2478. /************************************************************************/
  2479.  
  2480. void    set_speed_adapt(rate,c)
  2481.     UINT    rate,c;
  2482.     {
  2483.     if(c != '\r') return;        /* Must be CR */
  2484.     switch_selector(rate);        /* Update "setup" screen display */
  2485.     speed_adapt = rate;        /* Update speed adaptation rate */
  2486.     }
  2487.  
  2488. /************************************************************************/
  2489. /*                                    */
  2490. /*    Routine:    set_vmode                    */
  2491. /*                                    */
  2492. /*    Set the video display mode.  Uses the ROM BIOS service 0x10.    */
  2493. /*                                    */
  2494. /*    Parameters:    "mode" -    The video display mode to set.    */
  2495. /*                                    */
  2496. /*    Returns:    None.                        */
  2497. /*                                    */
  2498. /************************************************************************/
  2499.  
  2500. void    set_vmode(mode)
  2501.     UINT    mode;
  2502.     {
  2503.     inregs.h.ah = 0x00;        /* 0x00: Set video mode */
  2504.     inregs.h.al = mode;        /* The mode to set */
  2505.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  2506.     }
  2507.  
  2508. /************************************************************************/
  2509. /*                                    */
  2510. /*    Routine:    setup                        */
  2511. /*                                    */
  2512. /*    Configure program parameters.                    */
  2513. /*                                    */
  2514. /*    Parameters:    None.                        */
  2515. /*                                    */
  2516. /*    Returns:    None.                        */
  2517. /*                                    */
  2518. /************************************************************************/
  2519.  
  2520. struct    screen    setup_help1[] =        /* Morse setup help display #1 */
  2521.     {
  2522.         {1,30,"SETUP HELP (PAGE 1)" },
  2523.         {3,2,
  2524. "This function allows you to control the various MORSE program options."},
  2525.         {5,2,
  2526. "To change an option you use the cursor arrow keys to position the cursor"},
  2527.         {6,2,
  2528. "just to the left of the desired option.  If the option is either ON/OFF (or"},
  2529.         {7,2,
  2530. "ENABLED/DISABLED), you use the carriage return key (ENTER on some"},
  2531.         {8,2,
  2532. "keyboards), to toggle the option back and forth.  If the option requires a"},
  2533.          {9,2,
  2534. "numeric input just enter the digits followed by a carriage return (if the"},
  2535.         {10,2,
  2536. "field is not completely full)."},
  2537.         {12,2,
  2538. "The top window on the screen controls the \"enabled\" status of the specific"},
  2539.         {13,2,
  2540. "Morse Codes.  If a triangle is present to the left of the ASCII equivalent"},
  2541.         {14,2,
  2542. "the character is enabled.  You may change the enabled status either by"},
  2543.         {15,2,
  2544. "individually toggling characters, or by using one of the three enabling"},
  2545.         {16,2,
  2546. "functions.  These are located in the lower right portion of the window and"},
  2547.         {17,2,
  2548. "allow you to enable all the characters, enable only those required by the"},
  2549.         {18,2,
  2550. "FCC amateur radio examinations, or disable them all.  You use the enabling"},
  2551.         {19,2,
  2552. "functions by positioning the cursor next to the desired function and typing"},
  2553.         {20,2,
  2554. "carriage return."},
  2555.         {22,28,"(CONTINUED ON NEXT PAGE)"},
  2556.         {0,0,NULL }
  2557.     };
  2558.  
  2559. struct    screen    setup_help2[] =        /* Morse setup help display #2 */
  2560.     {
  2561.         {1,30,"SETUP HELP (PAGE 2)" },
  2562.         {3,2,
  2563. "The three windows located in the lower right corner of the screen are used"},
  2564.         {4,2,
  2565. "to adjust the Morse Code speed and oscillator frequency."},
  2566.         {6,2,
  2567. "The speed adjustment consists of two components:  A basic character speed"},
  2568.         {7,2,
  2569. "(5,7,11 or 22 WPM), and an adjustable \"delay\" which is added to the inter-"},
  2570.         {8,2,
  2571. "character time.  The same delay is also added to the inter-word time, but is"},
  2572.          {9,2,
  2573. "doubled in effect.  The idea behind this is to always have the characters"},
  2574.         {10,2,
  2575. "sent at the same rate (and thus sound the same), but to vary the spacing"},
  2576.         {11,2,
  2577. "between characters and words.  It is suggested that you set the character"},
  2578.         {12,2,
  2579. "speed to the fastest rate you ever anticipate using and adjust the delay"},
  2580.         {13,2,
  2581. "from a large value down as you improve.  The speed adaptation feature"},
  2582.         {14,2,
  2583. "of the CODE TRAINING function can be used to do this for you automatically."},
  2584.         {16,2,
  2585. "The three windows located in the lower left corner of the screen are used"},
  2586.         {17,2,
  2587. "to control options which are particular to the CODE TRAINING function."},
  2588.         {18,2,
  2589. "These include the code group size, how fast to adapt the code speed \"delay\""},
  2590.         {19,2,
  2591. "in response to changes in your accuracy, and whether to adapt the choices of"},
  2592.         {20,2,
  2593. "which characters are in the code groups to those you miss more often."},
  2594.         {0,0,NULL }
  2595.     };
  2596.  
  2597. void    setup()
  2598.     {
  2599.     int    c;
  2600.  
  2601.     FOREVER
  2602.         {
  2603.         cursor_off();        /* Turn cursor off */
  2604.         erase();        /* Erase screen */
  2605. /*
  2606.  *    Display command line.
  2607.  */
  2608.         position(24,2);        /* Position to 25 line */
  2609.         show_string("[SETUP]           F1:HELP");
  2610.         position(24,33);
  2611.         switch(state)
  2612.             {
  2613.             case    MAINMENU:    /* Came from MAIN MENU */
  2614.                 show_string("F2:MAIN MENU");
  2615.                 break;
  2616.  
  2617.             case    KEYBOARD:    /* Came from CODE KEYBOARD */
  2618.                 show_string("F2:CODE KEYBOARD");
  2619.                 break;
  2620.  
  2621.             case    FROMFILE:    /* Came from CODE FILE */
  2622.                 show_string("F2:CODE FILE");
  2623.                 break;
  2624.  
  2625.             case    TRAINER:    /* Came from CODE TRAINING */
  2626.                 show_string("F2:CODE TRAINER");
  2627.                 break;
  2628.  
  2629.             default:
  2630.                 break;
  2631.             }
  2632.         position(24,53);
  2633.         show_string("F10:QUIT");
  2634.         show_speed();        /* Show current code speed/delay */
  2635.         init_setup();        /* Init. "setup_info" data/screen */
  2636. /*
  2637.  *    Position the cursor to the origin cell.
  2638.  */
  2639.         cursor_row = 19;
  2640.         cursor_field_col = 3;
  2641.         cursor_key(FUNC_RIGHT);    /* Force actual cursor position calc */
  2642.         cursor_on();        /* We can turn cursor ON now */
  2643.  
  2644.         FOREVER
  2645.             {
  2646.             while(! (c = get_key())) ;    /* Wait for user */
  2647.             switch(c)    /* See what we are to do */
  2648.                 {
  2649.                 case    FUNC_UP:
  2650.                 case    FUNC_DOWN:
  2651.                 case    FUNC_LEFT:
  2652.                 case    FUNC_RIGHT:
  2653.                     cursor_key(c);    /* Move cursor */
  2654.                     break;
  2655.  
  2656.                 case    FUNC_F1:    /* Help screens */
  2657.                     help(setup_help1);
  2658.                     help(setup_help2);
  2659.                     break;
  2660.  
  2661.                 case    FUNC_F2:    /* Return to whoever */
  2662.                     return;
  2663.  
  2664.                 case    FUNC_F10:    /* Terminate */
  2665.                     terminate();
  2666.  
  2667.                 default:        /* Do something */
  2668.                 (*setup_info[cursor_row][cursor_field_col].
  2669.                     field_func)(setup_info[cursor_row]
  2670.                         [cursor_field_col].field_arg,
  2671.                             c);
  2672.                     break;
  2673.                 }
  2674.             if(c == FUNC_F1) break;
  2675.             }
  2676.         }
  2677.     }
  2678.  
  2679. /************************************************************************/
  2680. /*                                    */
  2681. /*    Routine:    show_attr                    */
  2682. /*                                    */
  2683. /*    Display a specified # of copies of a single character starting    */
  2684. /*    at the current cursor position, using the specified display    */
  2685. /*    attribute.                            */
  2686. /*                                    */
  2687. /*    Parameters:    "c"    -    The character to display.    */
  2688. /*            "number" -    The desired # of copies.    */
  2689. /*            "attr"    -    Text mode display attribute.    */
  2690. /*                                    */
  2691. /*    Returns:    None.                        */
  2692. /*                                    */
  2693. /************************************************************************/
  2694.  
  2695. void    show_attr(c,number,attr)
  2696.     UINT    c,number,attr;
  2697.     {
  2698.     inregs.h.ah = 0x09;        /* 0x09: Write character/attribute */
  2699.     inregs.h.al = c;        /* The character to write */
  2700.     inregs.h.bh = 0;        /* Always use page zero */
  2701.     inregs.h.bl = attr;        /* Text mode display attribute */
  2702.     inregs.x.cx = number;        /* # of times to write character */
  2703.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  2704.     }
  2705.  
  2706. /************************************************************************/
  2707. /*                                    */
  2708. /*    Routine:    show_char                    */
  2709. /*                                    */
  2710. /*    Display a specified # of copies of a single character starting    */
  2711. /*    at the current cursor position.                    */
  2712. /*                                    */
  2713. /*    Parameters:    "c"    -    The character to display.    */
  2714. /*            "number" -    The desired # of copies.    */
  2715. /*                                    */
  2716. /*    Returns:    None.                        */
  2717. /*                                    */
  2718. /************************************************************************/
  2719.  
  2720. void    show_char(c,number)
  2721.     UINT    c,number;
  2722.     {
  2723.     inregs.h.ah = 0x0A;        /* 0x0A: Write character */
  2724.     inregs.h.al = c;        /* The character to write */
  2725.     inregs.h.bh = 0;        /* Always use page zero */
  2726.     inregs.x.cx = number;        /* # of times to write character */
  2727.     int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  2728.     }
  2729.  
  2730. /************************************************************************/
  2731. /*                                    */
  2732. /*    Routine:    show_screen                    */
  2733. /*                                    */
  2734. /*    Display a series of strings at specified positions on the    */
  2735. /*    screen.                                */
  2736. /*                                    */
  2737. /*    Parameters:    "list"    -    A pointer to an array of    */
  2738. /*                    "screen" structures.        */
  2739. /*                                    */
  2740. /*    Returns:    None.                        */
  2741. /*                                    */
  2742. /************************************************************************/
  2743.  
  2744. void    show_screen(list)
  2745.     struct    screen    list[];
  2746.     {
  2747.     struct    screen    *sptr;
  2748. /*
  2749.  *    Loop through the display list until a NULL display string is found.
  2750.  */
  2751.     sptr = list;
  2752.     while(sptr->text)
  2753.         {
  2754.         position(sptr->row,sptr->col);    /* Start location for text */
  2755.         show_string(sptr->text);    /* Show text */
  2756.         sptr++;
  2757.         }
  2758.     }
  2759.  
  2760. /************************************************************************/
  2761. /*                                    */
  2762. /*    Routine:    show_speed                    */
  2763. /*                                    */
  2764. /*    Display the current code speed and delay factor on the command    */
  2765. /*    line in the following format:                    */
  2766. /*                                    */
  2767. /*            "SPEED:XX(YY)WPM"                */
  2768. /*                                    */
  2769. /*    Where XX is the base rate and YY represents the number of 55 ms    */
  2770. /*    delays to toss in between characters (also the # of 110 ms    */
  2771. /*    delays between words...)                    */
  2772. /*                                    */
  2773. /*    Parameters:    None.                        */
  2774. /*                                    */
  2775. /*    Returns:    None.                        */
  2776. /*                                    */
  2777. /************************************************************************/
  2778.  
  2779. void    show_speed()
  2780.     {
  2781.     BOOL    was_cursor;
  2782.     char    buf[81];
  2783.     UINT    row,col;
  2784.  
  2785.     read_position(&row,&col);    /* Read current cursor position */
  2786.     was_cursor = cursor_state;    /* Remember whether ON or OFF */
  2787.     cursor_off();            /* Turn cursor off */
  2788.     position(24,64);        /* Place to write speed */
  2789.     show_char(' ',16);        /* Blank out beforehand */
  2790.     show_string("SPEED:");
  2791.     switch(max_speed)
  2792.         {
  2793.         case    1:        /* Base rate is 22 WPM */
  2794.             show_string("22(");
  2795.             break;
  2796.  
  2797.         case    2:        /* Base rate is 11 WPM */
  2798.             show_string("11(");
  2799.             break;
  2800.  
  2801.         case    3:        /* Base rate is 7 WPM */
  2802.             show_string("7(");
  2803.             break;
  2804.  
  2805.         case    4:        /* Base rate is 5 WPM */
  2806.             show_string("5(");
  2807.             break;
  2808.  
  2809.         default:
  2810.             break;
  2811.         }
  2812.     show_string(itoa(delay,buf,10));    /* Current value of delay */
  2813.     show_string(")WPM");
  2814.     position(row,col);        /* Restore cursor position */
  2815.     if(was_cursor) cursor_on();    /* And turn cursor back on if needed */
  2816.     }
  2817.  
  2818. /************************************************************************/
  2819. /*                                    */
  2820. /*    Routine:    show_string                    */
  2821. /*                                    */
  2822. /*    Display the specified string at the current cursor position and    */
  2823. /*    advance the cursor.                        */
  2824. /*                                    */
  2825. /*    Parameters:    "string" -    A pointer to the string to    */
  2826. /*                    display.            */
  2827. /*                                    */
  2828. /*    Returns:    None.                        */
  2829. /*                                    */
  2830. /************************************************************************/
  2831.  
  2832. void    show_string(string)
  2833.     char    *string;
  2834.     {
  2835.     inregs.h.ah = 0x0E;        /* 0x0E: Write character as TTY */
  2836.     inregs.h.bh = 0;        /* Always use page zero */
  2837.     while(*string)
  2838.         {
  2839.         inregs.h.al = *string++;    /* Character to write */
  2840.         int86(0x10,&inregs,&outregs);    /* Interrupt 0x10 */
  2841.         }
  2842.     }
  2843.  
  2844. /************************************************************************/
  2845. /*                                    */
  2846. /*    Routine:    sign_on                        */
  2847. /*                                    */
  2848. /*    Clear screen and show startup animation and titles.        */
  2849. /*                                    */
  2850. /*    Parameters:    None.                        */
  2851. /*                                    */
  2852. /*    Returns:    None.                        */
  2853. /*                                    */
  2854. /************************************************************************/
  2855.  
  2856. char    image[25][80];            /* Screen image for dissolves */
  2857. struct    screen    startup[] =
  2858.     {
  2859.         { 2,18,    "-.-. --.-    -.. .   -. --... -.-. -.-. -..." },
  2860.         { 4,23,    "$   $   $$$   $$$$    $$$   $$$$$" },
  2861.         { 5,23, "$$ $$  $   $  $   $  $   $  $" },
  2862.         { 6,23, "$$$$$  $   $  $   $  $      $" },
  2863.         { 7,23, "$ $ $  $   $  $$$$    $$$   $$$$$" },
  2864.         { 8,23, "$   $  $   $  $  $       $  $" },
  2865.         { 9,23, "$   $  $   $  $   $  $   $  $" },
  2866.         { 10,23,"$   $   $$$   $   $   $$$   $$$$$" },
  2867.         { 12,19,"A MORSE CODE KEYBOARD AND TRAINING PROGRAM" },
  2868.         { 13,25,"WRITTEN BY KIRK BAILEY, N7CCB" },
  2869.         { 17,20,"THIS PROGRAM IS COMPLETELY PUBLIC DOMAIN" }
  2870.     };
  2871.  
  2872. void    sign_on()
  2873.     {
  2874.     char    *cptr;
  2875.     UINT    left,right,top,bottom,i,j,k;
  2876. /*
  2877.  *    Fill "image" video overlay with desired startup screen.
  2878.  */
  2879.     for(cptr = &image[0][0], i = 0; i < (80 * 25); i++)
  2880.         {
  2881.         *cptr++ = ' ';        /* Erase "image" overlay */
  2882.         }
  2883. /*
  2884.  *    Put title border in "image" video overlay.
  2885.  */
  2886.     top = 1; bottom = 18;
  2887.     left = 14; right = 65;
  2888.     image[top][left] = 0xC9;
  2889.     image[top][right] = 0xBB;
  2890.     image[bottom][left] = 0xC8;
  2891.     image[bottom][right] = 0xBC;
  2892.     for(i = left + 1; i < right; i++)
  2893.         {
  2894.         image[top][i] = 0xCD;
  2895.         image[bottom][i] = 0xCD;
  2896.         }
  2897.     for(i = top + 1; i < bottom; i++)
  2898.         {
  2899.         image[i][left] = 0xBA;
  2900.         image[i][right] = 0xBA;
  2901.         }
  2902. /*
  2903.  *    Put titles in "image" video overlay.
  2904.  */
  2905.     for(i = 0; i < (sizeof(startup) / sizeof(struct screen)); i++)
  2906.         {
  2907.         cptr = startup[i].text;
  2908.         for(j = startup[i].col; *cptr; j++)
  2909.             {
  2910.             if(*cptr == '$') *cptr = 0xB2;    /* Use block instead */
  2911.             image[startup[i].row][j] = *cptr++;
  2912.             }
  2913.         }
  2914. /*
  2915.  *    Display version date.
  2916.  */
  2917.     cptr = VERSION;
  2918.     for(i = 0; *cptr; i++) image[15][36 + i] = *cptr++;
  2919. /*
  2920.  *    Completely fill screen from middle out.
  2921.  */
  2922.     top = 11;
  2923.     bottom = 12;
  2924.     left = 33; right = 46;
  2925.  
  2926.     for(i = 0; i <= 11; i++, top--, bottom++, left -= 3, right += 3)
  2927.         {
  2928.         position(top,left);
  2929.         show_char(0xDB,right - left + 1);
  2930.         position(bottom,left);
  2931.         show_char(0xDB,right - left + 1);
  2932.  
  2933.         for(j = top + 1; j < bottom; j++)
  2934.             {
  2935.             position(j,left);
  2936.             show_char(0xDB,3);
  2937.             position(j,right - 2);
  2938.             show_char(0xDB,3);
  2939.             }
  2940.         }
  2941. /*
  2942.  *    Do a dissolve from full screen to desired startup page "image".
  2943.  */
  2944.     top = 11;
  2945.     bottom = 12;
  2946.     left = 33; right = 46;
  2947.  
  2948.     for(i = 0; i <= 11; i++, top--, bottom++, left -= 3, right += 3)
  2949.         {
  2950.         for(j = left; j <= right; j++)
  2951.             {
  2952.             position(top,j);
  2953.             show_char(image[top][j],1);
  2954.             }
  2955.         for(j = left; j <= right; j++)
  2956.             {
  2957.             position(bottom,j);
  2958.             show_char(image[bottom][j],1);
  2959.             }
  2960.         for(j = top + 1; j < bottom; j++)
  2961.             {
  2962.             for(k = 0; k < 3; k++)
  2963.                 {
  2964.                 position(j,left + k);
  2965.                 show_char(image[j][left + k],1);
  2966.                 }
  2967.             for(k = 0; k < 3; k++)
  2968.                 {
  2969.                 position(j,right - k);
  2970.                 show_char(image[j][right - k],1);
  2971.                 }
  2972.             }
  2973.         }
  2974. /*
  2975.  *    Kick in the reverse video 25 line.
  2976.  */
  2977.     position(24,0);            /* Position to start of 25 line */
  2978.     show_attr(' ',80,0x70);        /* Reverse video the line */
  2979.     }
  2980.  
  2981. /************************************************************************/
  2982. /*                                    */
  2983. /*    Routine:    simple_border                    */
  2984. /*                                    */
  2985. /*    Draw a double line border across the top and down both sides to    */
  2986. /*    connect with the command line across the bottom.  Also add a    */
  2987. /*    a double line across the screen two lines from the top to allow    */
  2988. /*    a title to be placed on the second line.            */
  2989. /*                                    */
  2990. /*    Parameters:    None.                        */
  2991. /*                                    */
  2992. /*    Returns:    None.                        */
  2993. /*                                    */
  2994. /************************************************************************/
  2995.  
  2996. void    simple_border()
  2997.     {
  2998.     position(0,0); show_char(0xC9,1);
  2999.     position(0,1); show_char(0xCD,78);
  3000.     position(0,79); show_char(0xBB,1);
  3001.  
  3002.     for(cursor_row = 1; cursor_row < 24; cursor_row++)
  3003.         {
  3004.         position(cursor_row,0);
  3005.         if(cursor_row == 2) show_char(0xCC,1);
  3006.         else show_char(0xBA,1);
  3007.  
  3008.         position(cursor_row,79);
  3009.         if(cursor_row == 2) show_char(0xB9,1);
  3010.         else show_char(0xBA,1);
  3011.         }
  3012.     position(2,1); show_char(0xCD,78);
  3013.     }
  3014.  
  3015. /************************************************************************/
  3016. /*                                    */
  3017. /*    Routine:    sound_off                    */
  3018. /*                                    */
  3019. /*    Disconnect the 8253 timer from the internal speaker (ie. turn    */
  3020. /*    speaker off).                            */
  3021. /*                                    */
  3022. /*    Parameters:    None.                        */
  3023. /*                                    */
  3024. /*    Returns:    None.                        */
  3025. /*                                    */
  3026. /************************************************************************/
  3027.  
  3028. void    sound_off()
  3029.     {
  3030.     outp(0x61,inp(0x61) & 0xFC);    /* Clear bottom two bits in PPI */
  3031.     }
  3032.  
  3033. /************************************************************************/
  3034. /*                                    */
  3035. /*    Routine:    sound_on                    */
  3036. /*                                    */
  3037. /*    Connect the 8253 timer to the internal speaker and produce a    */
  3038. /*    tone whose frequency was set by the most recent "init_sound"    */
  3039. /*    call.                                */
  3040. /*                                    */
  3041. /*    Parameters:    None.                        */
  3042. /*                                    */
  3043. /*    Returns:    None.                        */
  3044. /*                                    */
  3045. /************************************************************************/
  3046.  
  3047. void    sound_on()
  3048.     {
  3049.     outp(0x61,inp(0x61) | 0x03);    /* Set bottom two bits in PPI */
  3050.     }
  3051.  
  3052. /************************************************************************/
  3053. /*                                    */
  3054. /*    Routine:    switch_selector                    */
  3055. /*                                    */
  3056. /*    Do screen updates for "setup" display of a selector switch.    */
  3057. /*    This is a collection of 4 fields, only one of which may be    */
  3058. /*    selected at any given time.                    */
  3059. /*                                    */
  3060. /*    Parameters:    "setting"-    The switch position being    */
  3061. /*                    selected.  See the GLOBAL    */
  3062. /*                    DEFINITION section for the    */
  3063. /*                    macro definitions of the legal    */
  3064. /*                    positions (the "field_arg"    */
  3065. /*                    component of the "setup_info"    */
  3066. /*                    data structure).        */
  3067. /*                                    */
  3068. /*    Returns:    None.                        */
  3069. /*                                    */
  3070. /************************************************************************/
  3071.  
  3072. void    switch_selector(setting)
  3073.     UINT    setting;
  3074.     {
  3075.     UINT    off_row1,off_row2,off_row3;
  3076. /*
  3077.  *    Figure out which switch positions need to be turned off.
  3078.  */
  3079.     switch(setting)
  3080.         {
  3081.         case    SELECT0:    /* Top switch position */
  3082.             off_row1 = cursor_row + 1;
  3083.             off_row2 = cursor_row + 2;
  3084.             off_row3 = cursor_row + 3;
  3085.             break;
  3086.  
  3087.         case    SELECT1:    /* Second position from the top */
  3088.             off_row1 = cursor_row - 1;
  3089.             off_row2 = cursor_row + 1;
  3090.             off_row3 = cursor_row + 2;
  3091.             break;
  3092.  
  3093.         case    SELECT2:    /* Second position from the bottom */
  3094.             off_row1 = cursor_row - 2;
  3095.             off_row2 = cursor_row - 1;
  3096.             off_row3 = cursor_row + 1;
  3097.             break;
  3098.  
  3099.         case    SELECT3:    /* Bottom switch position */
  3100.             off_row1 = cursor_row - 3;
  3101.             off_row2 = cursor_row - 2;
  3102.             off_row3 = cursor_row - 1;
  3103.             break;
  3104.  
  3105.         default:
  3106.             break;
  3107.         }
  3108. /*
  3109.  *    Turn ON/OFF the appropriate switch positions.
  3110.  */
  3111.     cursor_off();            /* Turn OFF the cursor during update */
  3112.     position(off_row1,cursor_col); show_char(' ',1);
  3113.     position(off_row2,cursor_col); show_char(' ',1);
  3114.     position(off_row3,cursor_col); show_char(' ',1);
  3115.     position(cursor_row,cursor_col); show_char(0x10,1);
  3116.     cursor_on();            /* Turn cursor back ON */
  3117.     }
  3118.  
  3119. /************************************************************************/
  3120. /*                                    */
  3121. /*    Routine:    terminate                    */
  3122. /*                                    */
  3123. /*    Restore computer to original status and exit.            */
  3124. /*                                    */
  3125. /*    Parameters:    None.                        */
  3126. /*                                    */
  3127. /*    Returns:    None.                        */
  3128. /*                                    */
  3129. /************************************************************************/
  3130.  
  3131. void    terminate()
  3132.     {
  3133.     sound_off();            /* Turn off noise */
  3134.     restore_video();        /* Put screen back together */
  3135.     exit(FALSE);            /* And exit without prejudice */
  3136.     }
  3137.  
  3138. /************************************************************************/
  3139. /*                                    */
  3140. /*    Routine:    toggle_char                    */
  3141. /*                                    */
  3142. /*    Toggle the enabled status of morse code characters (the MORSE    */
  3143. /*    FILE and MORSE TRAINER sections use this status to see what    */
  3144. /*    can be translated into morse code).                */
  3145. /*                                    */
  3146. /*    Parameters:    "i"    -    An index into the "codes" data    */
  3147. /*                    structure.            */
  3148. /*            "c"    -    The actual character that was    */
  3149. /*                    typed (must be CR to be valid).    */
  3150. /*                                    */
  3151. /*    Returns:    None.                        */
  3152. /*                                    */
  3153. /************************************************************************/
  3154.  
  3155. void    toggle_char(i,c)
  3156.     UINT    i,c;
  3157.     {
  3158.     if(c != '\r') return;
  3159.     position(cursor_row,cursor_col);
  3160.     if(codes[i].enabled = !codes[i].enabled) show_char(0x10,1);
  3161.     else show_char(' ',1);
  3162.     }
  3163.  
  3164. /************************************************************************/
  3165. /*                                    */
  3166. /*    Routine:    toggle_code_adapt                */
  3167. /*                                    */
  3168. /*    Used by the "setup" function to enable and disable code        */
  3169. /*    adaptation.  The basic idea is that characters which are missed    */
  3170. /*    often should be presented more often (in TRAINER mode), to    */
  3171. /*    enhance learning them.                        */
  3172. /*                                    */
  3173. /*    Parameters:    "unused"-    An unused parameter.        */
  3174. /*            "c"    -    The actual character that was    */
  3175. /*                    typed (must be CR to be valid).    */
  3176. /*                                    */
  3177. /*    Returns:    None.                        */
  3178. /*                                    */
  3179. /************************************************************************/
  3180.  
  3181. void    toggle_code_adapt(unused,c)
  3182.     UINT    unused,c;
  3183.     {
  3184.     if(c != '\r') return;        /* Must be CR */
  3185.     position(cursor_row,cursor_col);
  3186.     if(code_adapt = !code_adapt) show_string("\x10  ENABLED ");
  3187.     else show_string("   DISABLED");
  3188.     position(cursor_row,cursor_col);
  3189.     }
  3190.  
  3191. /************************************************************************/
  3192. /*                                    */
  3193. /*    Routine:    training                    */
  3194. /*                                    */
  3195. /*    Perform the interactive morse code training stuff.  Note that    */
  3196. /*    two fields in the "codes" data structure are used to accumulate    */
  3197. /*    the total #'s of each character sent and the total # of errors    */
  3198. /*    of each character.  This data is used by the "do_code_adapt"    */
  3199. /*    function to adjust the character choices made for code groups.    */
  3200. /*    In a similar fashion, the "total_attempts" amd "total_errors"    */
  3201. /*    variables are used to gather information about the overall    */
  3202. /*    progress of the user for the "do_speed_adapt" function.        */
  3203. /*                                    */
  3204. /*    Parameters:    None.                        */
  3205. /*                                    */
  3206. /*    Returns:    None.                        */
  3207. /*                                    */
  3208. /************************************************************************/
  3209.  
  3210. struct    screen    trainer_help[] =    /* Morse code trainer help display */
  3211.     {
  3212.         {1,31,"CODE TRAINING HELP" },
  3213.         {3,2,
  3214. "This function \"sends\" random Morse Codes which you then echo with the"},
  3215.         {4,2,
  3216. "ASCII equivalent on the keyboard.  The characters from which the random"},
  3217.         {5,2,
  3218. "code groups are constructed are restricted to those which are \"enabled\"."},
  3219.         {6,2,
  3220. "The SETUP (F3), function may be used to modify which characters are enabled."},
  3221.         {7,2,
  3222. "If you type the wrong ASCII code (or type nothing prior to the next code"},
  3223.          {8,2,
  3224. "being completely sent), the corresponding Morse Code will be repeated until"},
  3225.         {9,2,
  3226. "you do correctly echo it.  You may skip echoing a particular code with the"},
  3227.         {10,2,
  3228. "SKIP function key (F9).  If you wish to take a break use the HELP function"},
  3229.         {11,2,
  3230. "(F1), which preserves information about your progress."},
  3231.         {13,2,
  3232. "Since some Morse Codes have no direct ASCII equivalents, ASCII characters"},
  3233.         {14,2,
  3234. "have been chosen to represent them for the purposes of the MORSE program."},
  3235.         {15,2,
  3236. "See the SETUP function for the specific ASCII to Morse conversions which are"},
  3237.         {16,2,
  3238. "used.  The SETUP function may also be used to control the code group size"},
  3239.         {17,2,
  3240. "(or it may be set to random), the code speed, and the oscillator frequency."},
  3241.         {19,2,
  3242. "The CODE TRAINING function may also be set to \"adapt\" to your progress."},
  3243.         {20,2,
  3244. "Using options controlled by the SETUP function you may have the code speed"},
  3245.         {21,2,
  3246. "be automatically adjusted based on your accuracy and have codes which you"},
  3247.         {22,2,
  3248. "miss often appear more frequently."},
  3249.         {0,0,NULL }
  3250.     };
  3251.  
  3252. void    training()
  3253.     {
  3254.     int    c;
  3255.     UINT    i,j,code_sample_count,speed_sample_count,t_state;
  3256.     UINT    key,low,mid,high;
  3257.  
  3258.     state = TRAINER;        /* Currently feedback training */
  3259.  
  3260. /*
  3261.  *    The following code implements a 8 state, state machine.  It handles the
  3262.  *    1 character "fall-behind" for the user echoing the sent code and
  3263.  *    "correctly" recovers when an error is detected.
  3264.  */
  3265.     t_state = 0;            /* Initialization state */
  3266.     FOREVER
  3267.         {
  3268.         switch(t_state)
  3269.             {
  3270.             case    0:    /* Initialization state */
  3271. /*    Erase screen */        erase();
  3272. /*    Position to 25 line */    position(24,2);
  3273.                 show_string(
  3274. "[CODE TRAINING]   F1:HELP F2:MENU F3:SETUP F9:SKIP F10:QUIT");
  3275. /*    Show speed/delay */    show_speed();
  3276. /*    Erase/init window */    erase_window();
  3277. /*
  3278.  *    Initialize the index table into the "codes" structure that is used to
  3279.  *    generate random (and other) code groups.
  3280.  */
  3281.                 for(i = enabled_codes = 0; i < (sizeof(codes) /
  3282.                     sizeof(struct code)); i++)
  3283.                     {
  3284. /*    If code is to be used */    if(codes[i].enabled)
  3285.                         {
  3286. /*    No errors to start with */        codes[i].errors = 0;
  3287. /*    No characters sent to start with */    codes[i].attempts = 0;
  3288. /*    The ASCII equivalent */            index[enabled_codes].code =
  3289.                                 i +' ';
  3290.                         enabled_codes++;
  3291.                         }
  3292.                     }
  3293. /*
  3294.  *    Setup initial probabilities so that all codes are equally likely.
  3295.  */
  3296.                 for(i = 0; i < enabled_codes; i++)
  3297.                     {
  3298.                     index[i].base = (UINT) ((i * 32767L) /
  3299.                                 enabled_codes);
  3300.                     }
  3301. /*
  3302.  *    The following three statement initialize the information needed for the
  3303.  *    code/speed adaptation stuff.
  3304.  */
  3305.                 sample_attempts = 0;
  3306.                 sample_errors = 0;
  3307.                 code_sample_count = speed_sample_count = 0;
  3308.  
  3309. /*    Fall into state 1 */    t_state = 1;
  3310.  
  3311.             case    1:
  3312. /*    Put inter-word space */    put_window(' ');
  3313. /*
  3314.  *    Figure out if it is time to do the code/speed adaptation stuff and
  3315.  *    perform if needed.
  3316.  */
  3317.                 if(code_adapt)
  3318.                     {
  3319.                     if(++code_sample_count > CODE_SAMPLE)
  3320.                         {
  3321.                         code_sample_count = 0;
  3322.                         do_code_adapt();
  3323.                         }
  3324.                     }
  3325.                 if(++speed_sample_count > SPEED_SAMPLE)
  3326.                     {
  3327.                     speed_sample_count = 0;
  3328.                     do_speed_adapt();
  3329.                     }
  3330. /*
  3331.  *    Figure out how long the next code group should be and find out its
  3332.  *    contents.
  3333.  */
  3334. /*    If non-random */    if(group_size)    i = group_size;
  3335. /*    If random */        else i = (rand() % MAX_CG_SIZE) + 1;
  3336. /*
  3337.  *    Figure out the contents of the code group using binary search on the
  3338.  *    "index" structure to adjust the probabilities appropriately.
  3339.  */
  3340.                 for(j = 0; j < i; j++)
  3341.                     {
  3342.                     if(! enabled_codes) code_group[j]= '^';
  3343.                     else
  3344.                         {
  3345.                         key = rand();
  3346.                         low = 0;
  3347.                         high = enabled_codes;
  3348.                         while((high - low) > 1)
  3349.                             {
  3350.                             mid = (high + low) / 2;
  3351.                             if(key <= index[mid].
  3352.                                 base)
  3353.                                 high = mid;
  3354.                             else low = mid;
  3355.                             }
  3356.                         code_group[j] = index[low].
  3357.                                     code;
  3358.                         }
  3359.                     }
  3360. /*    Reset error status */    had_error = FALSE;
  3361. /*    Character counter */    j = 0;
  3362. /*    Fall into state 2 */    t_state = 2;
  3363.  
  3364.             case    2:
  3365. /*    Anything left? */    if(j >= i)
  3366.                     {
  3367. /*    No, wait rest of inter-word */    wait_tick((4 * max_speed) + delay);
  3368.                     t_state = 1;
  3369.                     break;
  3370.                     }
  3371. /*
  3372.  *    Time to begin sending out the code group.
  3373.  */
  3374.                 code_sent = code_group[j];
  3375.                 send(codes[code_sent - ' '].morse);
  3376. /*    Fall into state 3 */    t_state = 3;
  3377.  
  3378.             case    3:
  3379. /*    Inter-char interval */    wait_tick((3 * max_speed) + delay);
  3380. /*    Function key? */    if((c = peek_key()) < 0)
  3381.                     {
  3382.                     t_state = 6;
  3383.                     break;
  3384.                     }
  3385. /*    Code echoed ok? */    if(! code_sent)
  3386.                     {
  3387. /*    Yes, advance to next */        t_state = 7;
  3388.                     break;
  3389.                     }
  3390. /*    Code echoed wrong */    else if(had_error)
  3391.                     {
  3392. /*    Yes, resend it */        t_state = 2;
  3393.                     break;
  3394.                     }
  3395. /*
  3396.  *    The above two cases handled a correct echo (or skip), and an incorrect
  3397.  *    echo.  Now we handle the case when nothing was echoed by sending the
  3398.  *    next character in the group (if there is one), and still waiting for
  3399.  *    a correct echo of the first character.
  3400.  */
  3401. /*    Any more in group? */    if(j >= (i - 1))
  3402.                     {
  3403. /*    Nope, that was it */        t_state = 5;
  3404.                     break;
  3405.                     }
  3406. /*    Fall into state 4 */    t_state = 4;
  3407.  
  3408.             case    4:
  3409. /*
  3410.  *    The previous character has not yet been echoed, but go ahead
  3411.  *    and send the next one anyway.  The user has until the next one is
  3412.  *    finished being sent to correctly echo the previous one.
  3413.  */
  3414.                 send(codes[code_group[j + 1] - ' '].morse);
  3415. /*    Function key? */    if((c = peek_key()) < 0)
  3416.                     {
  3417.                     t_state = 6;
  3418.                     break;
  3419.                     }
  3420. /*    Code echoed ok? */    if(code_sent)
  3421.                     {
  3422. /*    No, tell user wrong */        put_window(0xB1);
  3423. /*    And flag error */        had_error = TRUE;
  3424. /*    Wait inter-word time */        wait_tick((7 * max_speed) +
  3425.                                 (2 * delay));
  3426. /*    If user finally got it */    if(! code_sent)
  3427.                         {
  3428. /*    Advance to next char */            t_state = 7;
  3429.                         break;
  3430.                         }
  3431. /*    Otherwise, resend it */        t_state = 2;
  3432.                     break;
  3433.                     }
  3434. /*
  3435.  *    The previous character WAS echoed OK, update everything so we are
  3436.  *    waiting for the echo of the lookahead character.
  3437.  */
  3438. /*    Record error if any */    if(had_error)
  3439.                     {
  3440.                     had_error = FALSE;
  3441.                     codes[code_group[j] - ' '].errors++;
  3442.                     sample_errors++;
  3443.                     }
  3444. /*    Record char sent */    codes[code_group[j] - ' '].attempts++;
  3445.                 sample_attempts++;
  3446. /*    Update char counter */    j++;
  3447. /*    Update code to echo */    code_sent = code_group[j];
  3448.                 t_state = 3;
  3449.                 break;
  3450.  
  3451.             case    5:
  3452. /*
  3453.  *    Wait remainder of inter-word delay time.
  3454.  */
  3455.                 wait_tick((4 * max_speed) + delay);
  3456. /*    Function key? */    if((c = peek_key()) < 0)
  3457.                     {
  3458.                     t_state = 6;
  3459.                     break;
  3460.                     }
  3461. /*    Code echoed ok? */    if(code_sent)
  3462.                     {
  3463. /*    No, tell user wrong */        put_window(0xB1);
  3464. /*    And flag error */        had_error = TRUE;
  3465.                     t_state = 2;
  3466.                     break;
  3467.                     }
  3468. /*    Was OK, start group */    t_state = 1;
  3469.                 break;
  3470.  
  3471.             case    6:
  3472. /*
  3473.  *    This state handles function key inputs.
  3474.  */
  3475. /*    Remove from buffer */    get_key();
  3476. /*    And see what to do */    switch(c)
  3477.                     {
  3478. /*    Help info */            case    FUNC_F1:
  3479.                         help(trainer_help);
  3480. /*    Erase screen */                erase();
  3481. /*    Position to 25 line */            position(24,2);
  3482.                         show_string(
  3483. "[CODE TRAINING]   F1:HELP F2:MENU F3:SETUP F9:SKIP F10:QUIT");
  3484. /*    Show speed/delay */            show_speed();
  3485. /*    Erase/init window */            erase_window();
  3486.                         t_state = 2;
  3487.                         break;
  3488.  
  3489. /*    Main menu */            case    FUNC_F2:
  3490.                         return;
  3491.  
  3492. /*    Setup */            case    FUNC_F3:
  3493.                         setup();
  3494.                         t_state = 0;
  3495.                         break;
  3496.  
  3497. /*    Quit MORSE */            case    FUNC_F10:
  3498.                         terminate();
  3499.  
  3500.                     default:
  3501. /*    Unknown function key */            t_state = 2;
  3502.                         break;
  3503.                     }
  3504.                 break;
  3505.  
  3506.             case    7:
  3507. /*
  3508.  *    This state updates the character pointer within a code group and
  3509.  *    records any errors detected.
  3510.  */
  3511.                 if(had_error)
  3512.                     {
  3513.                     had_error = FALSE;
  3514.                     codes[code_group[j] - ' '].
  3515.                                 errors++;
  3516.                     sample_errors++;
  3517.                     }
  3518. /*    Record char sent */    codes[code_group[j] - ' '].attempts++;
  3519.                 sample_attempts++;
  3520. /*    Advance char counter */    j++;
  3521.                 t_state = 2;
  3522.                 break;
  3523.  
  3524.             default:
  3525. /*    Should never happen */    t_state = 0;
  3526.                 break;
  3527.             }
  3528.         }
  3529.     }
  3530.  
  3531. /************************************************************************/
  3532. /*                                    */
  3533. /*    Routine:    wait_tick                    */
  3534. /*                                    */
  3535. /*    Waits the specified # of 18.2 Hz (55 millisecond), system clock    */
  3536. /*    tick intervals before returning.  Note that the actual time    */
  3537. /*    waited is influenced by how "close" the current clock tick is    */
  3538. /*    to expiring and the interval waited may thus be almost one tick    */
  3539. /*    shorter than that desired.  This can be compensated for by only    */
  3540. /*    calling "wait_tick" soon after a tick has expired (by issuing a    */
  3541. /*    preliminary synchronization "wait_tick" call prior to the calls    */
  3542. /*    where relative timing is important).                */
  3543. /*                                    */
  3544. /*    Parameters:    "count"    -    # of intervals to wait.        */
  3545. /*                                    */
  3546. /*    Returns:    None.                        */
  3547. /*                                    */
  3548. /************************************************************************/
  3549.  
  3550. void    wait_tick(count)
  3551.     UINT    count;
  3552.     {
  3553.     UINT    old_tick;
  3554.  
  3555.     if(! count) return;
  3556.  
  3557.     inregs.h.ah = 0x00;        /* 0x00: Read current clock count */
  3558.     int86(0x1A,&inregs,&outregs);    /* Interrupt 0x1A */
  3559.     old_tick = outregs.x.dx;    /* Low order word of clock tick */
  3560.  
  3561.     while(count--)
  3562.         {
  3563.         FOREVER
  3564.             {
  3565.             inregs.h.ah = 0x00;    /* 0x00: Read clock count */
  3566.             int86(0x1A,&inregs,&outregs);    /* Interrupt 0x1A */
  3567.             if(old_tick != outregs.x.dx) break;    /* If done */
  3568.             fill_window();    /* Buffer input chars while waiting */
  3569.             }
  3570.         old_tick = outregs.x.dx;    /* Remember new "old" count */
  3571.         }
  3572.     }
  3573.