/* * UnixCW - Unix CW (Morse code) training program * Copyright (C) 2001, 2002 Simon Baldwin (simonb@caldera.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * cwlib.c - C library of low-level Morse code services. * */ /* Include files. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if (defined(__unix__) || defined(unix)) && !defined(USG) # include #endif #if defined(HAVE_STRING_H) # include #endif /* HAVE_STRING_H */ #if defined(HAVE_STRINGS_H) # include #endif /* HAVE_STRINGS_H */ #if defined(HAVE_SYS_KD_H) # include /* Linux, UnixWare */ #else /* not HAVE_SYS_KD_H */ # if defined(HAVE_SYS_VTKD_H) # include /* OpenServer */ # else /* not HAVE_SYS_VTKD_H */ # if defined(HAVE_SYS_KBIO_H) # include /* FreeBSD */ # endif /* not HAVE_SYS_KBIO_H */ # endif /* not HAVE_SYS_VTKD_H */ #endif /* not HAVE_SYS_KD_H */ #if defined(HAVE_SYS_SOUNDCARD_H) # include #elif defined(HAVE_SOUNDCARD_H) # include #else /* not HAVE_SYS_SOUNDCARD_H */ # error "Neither sys/soundcard.h nor soundcard.h header file available" #endif #ifdef BSD # define ERR_NO_SUPPORT EPROTONOSUPPORT #else # define ERR_NO_SUPPORT EPROTO #endif /* Use single precision variants if available */ #if defined(HAVE_SINF) # define sin(x) sinf(x) #endif #if defined(HAVE_FLOORF) # define floor(x) floorf(x) #endif /* Library include file for the library we are implementing. */ #include "cwlib.h" #define MAJOR_VERSION 2 #define MINOR_VERSION 2 #define UNIXCWVERSION "cwlib version 2.2" #define COPYRIGHT "Copyright (C) 2001, 2002 Simon Baldwin\n" \ "This program comes with ABSOLUTELY NO WARRANTY; for details\n" \ "please see the file 'COPYING' supplied with the source code.\n" \ "This is free software, and you are welcome to redistribute it\n" \ "under certain conditions; again, see 'COPYING' for details.\n" \ "This program is released under the GNU General Public License." #define ASC_NUL '\0' /* End of string */ #define ASC_SPACE ' ' /* ASCII space char */ /* Tone and timing magic numbers. */ #define TONE_MAGIC 1190000 /* Kernel tone delay magic number */ #define DOT_MAGIC 1200000 /* Dot length magic number. The Dot length is 1200000/WPM Usec */ #define TONE_SILENT 0 /* 0Hz = silent 'tone' */ #define USECS_PER_SEC 1000000 /* Microseconds in a second */ #define STRAIGHTKEY_TIMEOUT \ 500000 /* Soundcard straight key timeout */ /* Fixed and general soundcard parameters. */ #define DSP_SETFRAGMENT 7 /* Sound fragment size, 128 (2^7) */ #define DSP_FORMAT AFMT_U8 /* Sound format */ #define DSP_CHANNELS 1 /* Sound in mono only */ #define DSP_RATE 8192 /* Sound sampling rate (~=2xMaxFreq) */ /* True/False macros, for use with int-type (lazy person) "booleans". */ #define FALSE (0) #define TRUE (!FALSE) /* Success/Error macros for return codes from public library routines. */ #define RC_SUCCESS CWLIB_SUCCESS #define RC_ERROR CWLIB_ERROR /* Other general macros. */ #ifdef BSD #define INITIAL_CONSOLE "/dev/console" #else #define INITIAL_CONSOLE "/dev/tty1" #endif /* initial sound device/mixer */ #if defined(__OpenBSD__) # define INITIAL_DSP "/dev/audio" #else # define INITIAL_DSP "/dev/dsp" #endif #define INITIAL_MIXER "/dev/mixer" #define INITIAL_SEND_SPEED 12 /* Initial send speed in WPM */ #define INITIAL_RECEIVE_SPEED 12 /* Initial receive speed in WPM */ #define INITIAL_FREQUENCY 800 /* Initial tone in Hz */ #define INITIAL_VOLUME 70 /* Initial volume */ #define INITIAL_GAP 0 /* Initial gap setting */ #define INITIAL_TOLERANCE 50 /* Initial tolerance setting */ #define INITIAL_WEIGHTING 0 /* Initial weighting setting */ #define INITIAL_ADAPTIVE FALSE /* Initial adaptive receive setting */ #define INITIAL_THRESHOLD (DOT_MAGIC / INITIAL_RECEIVE_SPEED) * 2 /* Initial adaptive speed threshold */ #define INITIAL_NOISE_THRESHOLD (DOT_MAGIC / CW_MAX_SPEED) / 2 /* Initial noise filter threshold */ /* * Library variables, indicating the user-selected parameters for generating * Morse code output and receiving Morse code input. */ static int cw_send_speed= INITIAL_SEND_SPEED; /* Initially 12 WPM */ static int cw_frequency = INITIAL_FREQUENCY; /* Initially 800 Hz */ static int cw_volume = INITIAL_VOLUME; /* Initially 70 % */ static int cw_gap = INITIAL_GAP; /* Initially no 'Farnsworth' gap */ static int cw_receive_speed = INITIAL_RECEIVE_SPEED; /* Initially 12 WPM, fixed speed */ static int cw_tolerance = INITIAL_TOLERANCE; /* Initially +/-50% on timings */ static int cw_weighting = INITIAL_WEIGHTING; /* Initially no weighting */ static int cw_adaptive_receive_enabled = INITIAL_ADAPTIVE; /* Initially do fixed speed receive */ static int cw_noise_spike_threshold = INITIAL_NOISE_THRESHOLD; /* Initially ignore any tone < 10mS */ /* * Library variables containing the paths and descriptors for the various * sound devices we use. */ static char *cw_console_device = INITIAL_CONSOLE; /* Initially "/dev/tty1" */ static int cw_console_descriptor= -1; /* Initially null */ static int cw_console_open = FALSE;/* Notes if console is open or closed */ static char *cw_sound_device = INITIAL_DSP; /* Initially /dev/dsp */ static int cw_sound_descriptor = -1; /* Dsp device file descriptor */ static int cw_sound_open = FALSE;/* Notes if dsp is open or closed */ static char *cw_mixer_device = INITIAL_MIXER; /* Initially /dev/mixer */ /* * Library variables indicating the parameters of any soundcard setup. */ static int cw_sound_generate_frequency = TONE_SILENT; /* Background tone generation freq */ static int cw_sound_sample_rate = 0; /* Dsp device sample rate */ static int cw_sound_saved_vol = 0; /* Saved mixer PCM volume setting */ /* * Sound output control flags, to enable soundcard, console, or both. * By default, console output is disabled, and soundcard enabled. This * is not backward-compatible with early library versions, but since * most systems these days have a soundcard, and it's a lot easier to * use from X than a console device for KIOCSOUND too, in a nod to * progression, this version of the library does things in a modern * fashion. */ static int cw_sound_soundcard_on= TRUE; /* Soundcard output control */ static int cw_sound_console_on = FALSE;/* Console output control */ /* * Library variable which is automatically maintained from the Morse input * stream, rather than being settable by the user. */ static int cw_adaptive_receive_threshold = INITIAL_THRESHOLD; /* Initially 2-dot threshold for adaptive speed */ /* * The following variables must be recalculated each time any of the above * Morse parameters associated with speeds, gap, tolerance, or threshold * change. Keeping these in step means that we then don't have to spend time * calculating them on the fly. * * Since they have to be kept in sync, the problem of how to have them * calculated on first call if none of the above parameters has been * changed is taken care of with a synchronization flag. Doing this saves * us from otherwise having to have a 'library initialize' function. */ static int cw_in_sync = FALSE;/* Synchronization flag */ /* Sending parameters: */ static int cw_send_dot_length = 0; /* Length of a send Dot, in Usec */ static int cw_send_dash_length = 0; /* Length of a send Dash, in Usec */ static int cw_end_of_ele_delay = 0; /* Extra delay at the end of element */ static int cw_end_of_char_delay = 0; /* Extra delay at the end of a char */ static int cw_additional_delay = 0; /* More delay at the end of a char */ static int cw_end_of_word_delay = 0; /* Extra delay at the end of a word */ static int cw_adjustment_delay = 0; /* More delay at the end of a word */ /* Receiving parameters: */ static int cw_receive_dot_length= 0; /* Length of a receive Dot, in Usec */ static int cw_receive_dash_length = 0; /* Length of a receive Dash, in Usec */ static int cw_dot_range_minimum = 0; /* Shortest dot period allowable */ static int cw_dot_range_maximum = 0; /* Longest dot period allowable */ static int cw_dash_range_minimum= 0; /* Shortest dot period allowable */ static int cw_dash_range_maximum= 0; /* Longest dot period allowable */ static int cw_eoe_range_minimum = 0; /* Shortest end of ele allowable */ static int cw_eoe_range_maximum = 0; /* Longest end of ele allowable */ static int cw_eoc_range_minimum = 0; /* Shortest end of char allowable */ static int cw_eoc_range_maximum = 0; /* Longest end of char allowable */ /* * Debugging flags variable. */ static int cw_debug_flags = 0; /* No debug unless requested */ /* * Morse code characters table. This table allows lookup of the Morse * shape of a given alphanumeric character. Shapes are held as a string, * with '-' representing dash, and '.' representing dot. The table ends * with a NULL entry. */ typedef struct { const unsigned char character; /* The character represented */ const char *representation;/* Dot-dash shape of the character */ } cw_entry_t; static const cw_entry_t cw_table[] = { /* ASCII 7bit letters */ { 'A', ".-" }, { 'B', "-..." }, { 'C', "-.-." }, { 'D', "-.." }, { 'E', "." }, { 'F', "..-." }, { 'G', "--." }, { 'H', "...." }, { 'I', ".." }, { 'J', ".---" }, { 'K', "-.-" }, { 'L', ".-.." }, { 'M', "--" }, { 'N', "-." }, { 'O', "---" }, { 'P', ".--." }, { 'Q', "--.-" }, { 'R', ".-." }, { 'S', "..." }, { 'T', "-" }, { 'U', "..-" }, { 'V', "...-" }, { 'W', ".--" }, { 'X', "-..-" }, { 'Y', "-.--" }, { 'Z', "--.." }, /* Numerals */ { '0', "-----" }, { '1', ".----" }, { '2', "..---" }, { '3', "...--" }, { '4', "....-" }, { '5', "....." }, { '6', "-...." }, { '7', "--..." }, { '8', "---.." }, { '9', "----." }, /* Punctuation */ { '"', ".-..-." }, { '\'',".----." }, { '$', "...-..-"}, { '(', "-.--." }, { ')', "-.--.-" }, { '+', ".-.-." }, { ',', "--..--" }, { '-', "-....-" }, { '.', ".-.-.-" }, { '/', "-..-." }, { ':', "---..." }, { ';', "-.-.-." }, { '=', "-...-" }, { '?', "..--.." }, { '_', "..--.-" }, { '@', ".--.-." }, /* Cwdaemon special characters */ { '<', "...-.-" }, { '>', "-...-.-"}, { '!', "...-." }, { '&', ".-..." }, { '*', ".-.-." }, /* ISO 8859-1 accented characters */ { 0334,"..--" }, /* U with diaresis */ { 0304,".-.-" }, /* A with diaeresis */ { 0307,"-.-.." }, /* C with cedilla */ { 0326,"---." }, /* O with diaresis */ { 0311,"..-.." }, /* E with acute */ { 0310,".-..-" }, /* E with grave */ { 0300,".--.-" }, /* A with grave */ { 0321,"--.--" }, /* N with tilde */ /* ISO 8859-2 accented characters */ { 0252,"----" }, /* S with cedilla */ { 0256,"--..-" }, /* Z with dot above */ /* Sentinel end of table value */ { ASC_NUL, NULL } }; /* * External keying function. It is useful for a client to be able to have * this library control an external keying device, for example, an oscill- * ator, or a transmitter. Here is where we keep the address of a function * that is passed to us for this purpose. */ static void (*cw_kk_key_callback) (int) = NULL; /* Callback function for key control */ /* * External ptt function. It is useful for a client to be able to have * this library control an external ptt device, for example, an amplifier, * or a transmitter with separate ptt. Here is where we keep the address * of a function that is passed to us for this purpose. */ static void (*cw_ptt_key_callback ) (int) = NULL; /* Callback function for ptt control */ /* * The library registers a single central SIGALRM handler. This handler will * call all of the functions on a list sequentially on each SIGALRM received. * This is where the list is kept. We also use a flag to tell us if the * SIGALRM handler is installed, and a place to keep the old SIGALRM * disposition, just so we can tidy it back up to how it was when the library * decides it can stop handling SIGALRM for a while. */ #define SIGALRM_HANDLERS 32 /* More than enough handlers */ static void (*cw_request_handlers[ SIGALRM_HANDLERS ]) (); /* List of low level handlers */ static int cw_sigalrm_handler_installed = FALSE; /* Flag indicating handler installed */ static struct sigaction cw_sigalrm_original_disposition; /* Saved SIGALRM disposition */ /* * Tone queue. This is a circular list of tone durations and frequencies * pending, and a pair of indexes, head (enqueue) and tail (dequeue) to * manage additions and asynchronous sending. */ #define TONE_QUEUE_CAPACITY 3000 /* ~= 5 minutes at 12 WPM */ #define TONE_QUEUE_HIGH_WATER_MARK \ 2900 /* Refuse characters if <100 free*/ typedef struct { int usec; /* Tone duration in usecs */ int frequency; /* Frequency of the tone */ } cw_queued_tone_t; static cw_queued_tone_t cw_tone_queue[ TONE_QUEUE_CAPACITY ]; static int cw_tq_head = 0; /* Tone queue head index */ static int cw_tq_tail = 0; /* Tone queue head index */ /* * It's useful to have the tone queue dequeue function call a client- * supplied callback routine when the amount of data in the queue drops * below a defined low water mark. This routine can then refill the * buffer, as required. */ static int cw_tq_low_water_mark = 0; /* Low water mark definition */ static void (*cw_tq_low_water_callback) (void) = NULL; /* Callback function for low water */ /* * Receive buffering. This is a fixed-length representation, filled in * as tone on/off timings are taken. */ #define RECEIVE_CAPACITY 256 /* Way longer than any representation */ static char cw_receive_representation_buffer[ RECEIVE_CAPACITY ]; static int cw_rr_current = 0; /* Receive buffer current location */ static struct timeval cw_rr_start_timestamp = {0, 0}; /* Tone start timestamp */ static struct timeval cw_rr_end_timestamp = {0, 0}; /* Tone end timestamp */ /* * Receive adaptive speed tracking. We keep a small array of dot lengths, * and a small array of dash lengths. We also keep a running sum of the * elements of each array, and an index to the current array position. */ #define AVERAGE_ARRAY_LENGTH 4 /* Keep four dot/dash lengths */ static int cw_dot_tracking_array[ AVERAGE_ARRAY_LENGTH ]; static int cw_dash_tracking_array[ AVERAGE_ARRAY_LENGTH ]; /* Dot and dash length arrays */ static int cw_dt_dot_index = 0; static int cw_dt_dash_index = 0; /* Circular indexes into the arrays */ static int cw_dt_dot_tracking_sum = 0; static int cw_dt_dash_tracking_sum = 0; /* Running sum of array members */ /* * Iambic keyer status. The keyer functions maintain the current known state * of the paddles, and latch FALSE-to-TRUE transitions while busy, to form the * iambic effect. For Curtis mode B, the keyer also latches any point where * both paddle states are TRUE at the same time. */ static int cw_ik_dot_paddle = FALSE;/* Current dot paddle state */ static int cw_ik_dash_paddle = FALSE;/* Current dash paddle state */ static int cw_ik_dot_latch = FALSE;/* Dot FALSE->TRUE latch */ static int cw_ik_dash_latch = FALSE;/* Dash FALSE->TRUE latch */ static int cw_ik_curtis_b_latch = FALSE;/* Curtis Dot&&Dash latch */ /* * Iambic keyer "Curtis" mode A/B selector. Mode A and mode B timings differ * slightly, and some people have a preference for one or the other. Mode A * is a bit less timing-critical, so we'll make that the default. */ static int cw_ik_curtis_mode_b = FALSE; /* * Straight key status. Just a key-up or key-down indication. Real simple. */ static int cw_sk_key_down = FALSE;/* Indicates key up or down */ /** * cw_version() * * Returns the version number of the library. Version numbers are returned * as an int, composed of Major_version << 16 | Minor_version. */ int cw_version () { return MAJOR_VERSION << 16 | MINOR_VERSION; } /** * cw_license() * * Prints a short library licensing message to stdout. */ void cw_license () { printf ("%s, ", UNIXCWVERSION); printf ("%s\n", COPYRIGHT); } /** * cw_set_debug_flags() * * Sets a value for the library debug flags. Debug output is generally * strings printed on stderr. There is no validation of flags. */ void cw_set_debug_flags (int new_value) { cw_debug_flags = new_value; } /** * cw_get_debug_flags() * * Retrieves library debug flags. If no flags are set, then on first * call, it will check the environment variable CWLIB_DEBUG, and if it * is available, will set debug flags to its value. The provides a way * for a program to set the debug flags without needing to make any source * code changes. */ int cw_get_debug_flags () { static int cwlib_debug_checked = FALSE;/* Initialization flag */ char *debug_value; /* Value of CWLIB_DEBUG */ /* If already done, simply ignore the call. */ if (!cwlib_debug_checked) { /* Do not overwrite any debug flags already set. */ if (cw_debug_flags == 0) { /* * Set the debug flags from CWLIB_DEBUG. If it is * an invalid numeric, treat it as 0; there is no * error checking. */ debug_value = getenv ("CWLIB_DEBUG"); if (debug_value != NULL) cw_debug_flags = atoi (debug_value); } /* Set checked flag, so we never do this again. */ cwlib_debug_checked = TRUE; } /* Return the flags setting. */ return cw_debug_flags; } /** * cw_get_[speed|frequency|volume|gap|tolerance]_limits() * * Return the limits on the speed, frequency, volume, gap, and tolerance * parameters. Normal values are speed 4-60 WPM, frequency 0-10,000 Hz, * volume 0-70 %, gap 0-20 dots, and tolerance 0-90 %. */ void cw_get_speed_limits (int *min_speed, int *max_speed) { if (min_speed != NULL) *min_speed = CW_MIN_SPEED; if (max_speed != NULL) *max_speed = CW_MAX_SPEED; } void cw_get_frequency_limits (int *min_frequency, int *max_frequency) { if (min_frequency != NULL) *min_frequency = CW_MIN_FREQUENCY; if (max_frequency != NULL) *max_frequency = CW_MAX_FREQUENCY; } void cw_get_volume_limits (int *min_volume, int *max_volume) { if (min_volume != NULL) *min_volume = CW_MIN_VOLUME; if (max_volume != NULL) *max_volume = CW_MAX_VOLUME; } void cw_get_gap_limits (int *min_gap, int *max_gap) { if (min_gap != NULL) *min_gap = CW_MIN_GAP; if (max_gap != NULL) *max_gap = CW_MAX_GAP; } void cw_get_tolerance_limits (int *min_tolerance, int *max_tolerance) { if (min_tolerance != NULL) *min_tolerance = CW_MIN_TOLERANCE; if (max_tolerance != NULL) *max_tolerance = CW_MAX_TOLERANCE; } /** * cw_sync_parameters_internal() * * Synchronize the dot, dash, end of element, end of character, and end * of word timings and ranges to new values of Morse speed, 'Farnsworth' * gap, or receive tolerance. */ static void cw_sync_parameters_internal () { /* Do nothing if we are already synchronized with speed/gap. */ if (!cw_in_sync) { /* * Send parameters: * * Calculate the length of a Dot as 1200000 / speed in wpm, * and the length of a Dash as three Dot lengths. */ cw_send_dot_length = (DOT_MAGIC / cw_send_speed) + (cw_weighting * 500); cw_send_dash_length = 3 * cw_send_dot_length; /* * An end of element length is one Dot, end of character is * three Dots total, and end of word is seven Dots total. */ cw_end_of_ele_delay = (DOT_MAGIC / cw_send_speed) - (cw_weighting * 500); cw_end_of_char_delay = 3 * cw_send_dot_length - cw_end_of_ele_delay; cw_end_of_word_delay = 7 * cw_send_dot_length - cw_end_of_char_delay; cw_additional_delay = cw_gap * cw_send_dot_length; /* * For 'Farnsworth', there also needs to be an adjustment * delay added to the end of words, otherwise the rhythm * is lost on word end. I don't know if there is an * "official" value for this, but 2.33 or so times the * gap is the correctly scaled value, and seems to sound * okay. * * Thanks to Michael D. Ivey for * identifying this in earlier versions of cwlib. */ cw_adjustment_delay = (7 * cw_additional_delay) / 3; /* Print out if debug requested. */ if (cw_get_debug_flags () & CW_DEBUG_PARAMETERS) fprintf (stderr, "cw: send usec timings <%d>:" " %d, %d, %d, %d, %d, %d, %d\n", cw_send_speed, cw_send_dot_length, cw_send_dash_length, cw_end_of_ele_delay, cw_end_of_char_delay, cw_end_of_word_delay, cw_additional_delay, cw_adjustment_delay); /* * Receive parameters: * * First, depending on whether we are set for fixed speed or * adaptive speed, calculate either the threshold from the * receive speed, or the receive speed from the threshold, * knowing that the threshold is always, effectively, two * dot lengths. */ if (cw_adaptive_receive_enabled) cw_receive_speed = DOT_MAGIC / (cw_adaptive_receive_threshold / 2); else cw_adaptive_receive_threshold = 2 * DOT_MAGIC / cw_receive_speed; /* * Calculate the basic receive dot and dash lengths. */ cw_receive_dot_length = DOT_MAGIC / cw_receive_speed; cw_receive_dash_length = 3 * cw_receive_dot_length; /* * Set the ranges of respectable timing elements depending * very much on whether we are required to adapt to the * incoming Morse code speeds. */ if (cw_adaptive_receive_enabled) { /* * For adaptive timing, calculate the Dot and Dash * timing ranges as zero to two Dots is a Dot, and * anything, anything at all, larger than this is a * Dash. */ cw_dot_range_minimum = 0; cw_dot_range_maximum = 2 * cw_receive_dot_length; cw_dash_range_minimum = cw_dot_range_maximum; cw_dash_range_maximum = INT_MAX; /* * Make the inter-element gap be anything up to the * adaptive threshold lengths - that is two Dots. And * the end of character gap is anything longer than * that, and shorter than two receive thresholds (that * is, four Dots). */ cw_eoe_range_minimum = cw_dot_range_minimum; cw_eoe_range_maximum = cw_dot_range_maximum; cw_eoc_range_minimum = cw_eoe_range_maximum; cw_eoc_range_maximum = 4 * cw_receive_dot_length; } else { /* * For fixed speed receiving, calculate the Dot timing * range as the Dot length +/- dot*tolerance%, and the * Dash timing range as the Dash length including * +/- dot*tolerance% as well. */ cw_dot_range_minimum = cw_receive_dot_length - (cw_receive_dot_length * cw_tolerance) / 100; cw_dot_range_maximum = cw_receive_dot_length + (cw_receive_dot_length * cw_tolerance) / 100; cw_dash_range_minimum = cw_receive_dash_length - (cw_receive_dot_length * cw_tolerance) / 100; cw_dash_range_maximum = cw_receive_dash_length + (cw_receive_dot_length * cw_tolerance) / 100; /* * Make the inter-element gap the same as the Dot range. * Make the inter-character gap, expected to be three * Dots, the same as Dash range at the lower end, but * make it the same as the Dash range _plus_ the * 'Farnsworth' delay at the top of the range. * * Any gap longer than this is by implication inter * -word. */ cw_eoe_range_minimum = cw_dot_range_minimum; cw_eoe_range_maximum = cw_dot_range_maximum; cw_eoc_range_minimum = cw_dash_range_minimum; cw_eoc_range_maximum = cw_dash_range_maximum + cw_additional_delay + cw_adjustment_delay; } /* Print out if debug requested. */ if (cw_get_debug_flags () & CW_DEBUG_PARAMETERS) fprintf (stderr, "cw: receive usec timings <%d>: " "%d-%d, %d-%d, %d-%d, %d-%d, %d\n", cw_receive_speed, cw_dot_range_minimum, cw_dot_range_maximum, cw_dash_range_minimum, cw_dash_range_maximum, cw_eoe_range_minimum, cw_eoe_range_maximum, cw_eoc_range_minimum, cw_eoc_range_maximum, cw_adaptive_receive_threshold); /* Set the parameters in sync flag. */ cw_in_sync = TRUE; } } /** * cw_[gs]et_[send_speed|receive_speed|frequency|volume|gap|tolerance]() * * Get and set routines for all the Morse code parameters available to * control the library. Set routines return 0 on success, or -1 on failure, * with errno set to indicate the problem, usually EINVAL, except for * cw_set_receive_speed, which returns EINVAL if the new value is * invalid, or EPERM if the receive mode is currently set for adaptive * receive speed tracking. Get routines simply return the current value. * * The default values of the parameters where none are explicitly set are * send/receive speed 12 WPM, volume 70 %, frequency 800 Hz, gap 0 dots, and * tolerance 50 %. Note that volume settings are not fully possible for * the console speaker; in this case, volume settings greater than zero * indicate console speaker sound is on, and setting volume to zero will * turn off console speaker sound. */ int cw_set_send_speed (int new_value) { if (new_value < CW_MIN_SPEED || new_value > CW_MAX_SPEED) { errno = EINVAL; return RC_ERROR; } if (new_value != cw_send_speed) { cw_send_speed = new_value; /* Changes of speed require resynchronization. */ cw_in_sync = FALSE; cw_sync_parameters_internal (); } return RC_SUCCESS; } int cw_set_receive_speed (int new_value) { if (cw_adaptive_receive_enabled) { errno = EPERM; return RC_ERROR; } else { if (new_value < CW_MIN_SPEED || new_value > CW_MAX_SPEED) { errno = EINVAL; return RC_ERROR; } } if (new_value != cw_receive_speed) { cw_receive_speed = new_value; /* Changes of speed require resynchronization. */ cw_in_sync = FALSE; cw_sync_parameters_internal (); } return RC_SUCCESS; } int cw_set_frequency (int new_value) { if (new_value < CW_MIN_FREQUENCY || new_value > CW_MAX_FREQUENCY) { errno = EINVAL; return RC_ERROR; } cw_frequency = new_value; return RC_SUCCESS; } int cw_set_volume (int new_value) { if (new_value < CW_MIN_VOLUME || new_value > CW_MAX_VOLUME) { errno = EINVAL; return RC_ERROR; } cw_volume = new_value; return RC_SUCCESS; } int cw_set_gap (int new_value) { if (new_value < CW_MIN_GAP || new_value > CW_MAX_GAP) { errno = EINVAL; return RC_ERROR; } if (new_value != cw_gap) { cw_gap = new_value; /* Changes of gap require resynchronization. */ cw_in_sync = FALSE; cw_sync_parameters_internal (); } return RC_SUCCESS; } int cw_set_tolerance (int new_value) { if (new_value < CW_MIN_TOLERANCE || new_value > CW_MAX_TOLERANCE) { errno = EINVAL; return RC_ERROR; } if (new_value != cw_tolerance) { cw_tolerance = new_value; /* Changes of tolerance require resynchronization. */ cw_in_sync = FALSE; cw_sync_parameters_internal (); } return RC_SUCCESS; } int cw_set_weighting (int new_value) { if (new_value < CW_MIN_WEIGHTING || new_value > CW_MAX_WEIGHTING) { errno = EINVAL; return RC_ERROR; } if (new_value != cw_weighting) { cw_weighting = new_value; /* Changes of tolerance require resynchronization. */ cw_in_sync = FALSE; cw_sync_parameters_internal (); } return RC_SUCCESS; } int cw_get_send_speed () { return cw_send_speed; } int cw_get_receive_speed () { return cw_receive_speed; } int cw_get_frequency () { return cw_frequency; } int cw_get_volume () { return cw_volume; } int cw_get_gap () { return cw_gap; } int cw_get_tolerance () { return cw_tolerance; } /** * cw_[gs]et_console_file() * * Get and set routines for the path to the console device through which the * library generates PC speaker sound. The set routine does not return * any indication of whether the device is a valid console; use * cw_console_possible() to test to see if the value passed in might * be an acceptable console device. * * The default console file is /dev/tty1. */ void cw_set_console_file (char *new_value) { cw_console_device = new_value; } char * cw_get_console_file () { return cw_console_device; } /** * cw_[gs]et_soundcard_file() * * Get and set routines for the path to the sound device through which the * library generates soundcard sound. The set routine does not return * any indication of whether the device is a valid soundcard; use * cw_soundcard_possible() to test to see if the value passed in might * be an acceptable soundcard device. * * The default soundcard file is /dev/dsp. */ void cw_set_soundcard_file (char *new_value) { cw_sound_device = new_value; } char * cw_get_soundcard_file () { return cw_sound_device; } /** * cw_[gs]et_soundmixer_file() * * Get and set routines for the path to the mixer device that the library * may use to gate soundcard sound. The library uses the mixer device * where the main sound device does not support the PCM volume control * ioctl call; this tends to occur on newer OSS drivers, but not on older * ones, or on ALSA in OSS emulation mode. The set routine does not return * any indication of whether the device is a valid mixer. * * The default sound mixer file is /dev/mixer. */ void cw_set_soundmixer_file (char *new_value) { cw_mixer_device = new_value; } char * cw_get_soundmixer_file () { return cw_mixer_device; } /** * cw_soundcard_possible() * * Return success status if it appears that the library will be able to * generate tones through a sound card. If it appears that a sound card * may not work, the routine returns -1 to indicate failure. * * The function tests only that the given sound card file exists and is * writable. */ int cw_soundcard_possible () { if (cw_sound_open) return RC_SUCCESS; if (cw_sound_device == NULL) { errno = EINVAL; return RC_ERROR; } if (access (cw_sound_device, W_OK) == -1) { return RC_ERROR; } return RC_SUCCESS; } /** * cw_console_possible() * * Return success status if it appears that the library will be able to * generate tones through the console speaker. If it appears that console * sound may not work, the routine returns -1 to indicate failure. * * The function tests that the given console file exists, and that it will * accept the KIOCSOUND ioctl. */ int cw_console_possible () { #ifdef KIOCSOUND int file_descriptor; if (cw_console_open) return RC_SUCCESS; if (cw_console_device == NULL) { errno = EINVAL; return RC_ERROR; } file_descriptor = open (cw_console_device, O_WRONLY); if (file_descriptor == -1) { return RC_ERROR; } if (ioctl (file_descriptor, KIOCSOUND, 0) == -1) { close (file_descriptor); return RC_ERROR; } close (file_descriptor); return RC_SUCCESS; #else return RC_ERROR; #endif } /** * cw_set_adaptive_receive_internal() * * Set the value of the flag that controls whether, on receive, the receive * functions do fixed speed receive, or track the speed of the received Morse * code by adapting to the input stream. */ static void cw_set_adaptive_receive_internal (int flag) { int index; /* Averaging arrays index */ /* Look for change of adaptive receive state. */ if ((cw_adaptive_receive_enabled && !flag) || (!cw_adaptive_receive_enabled && flag)) { cw_adaptive_receive_enabled = flag; /* Changing the flag forces a change in low-level parameters. */ cw_in_sync = FALSE; cw_sync_parameters_internal (); /* * If we have just switched to adaptive mode, (re-)initialize * the averages array to the current dot/dash lengths, so that * initial averages match the current speed. And reset the * running sums too. */ if (cw_adaptive_receive_enabled) { for (index = 0; index < AVERAGE_ARRAY_LENGTH; index++) { cw_dot_tracking_array[index] = cw_receive_dot_length; cw_dash_tracking_array[index] = cw_receive_dash_length; } cw_dt_dot_tracking_sum = cw_receive_dot_length * AVERAGE_ARRAY_LENGTH; cw_dt_dash_tracking_sum = cw_receive_dash_length * AVERAGE_ARRAY_LENGTH; } } } /** * cw_enable_adaptive_receive() * cw_disable_adaptive_receive() * cw_get_adaptive_receive_state() * * Enable and disable adaptive receive speeds. If adaptive speed tracking * is enabled, the receive functions will attempt to automatically adjust * the receive speed setting to match the speed of the incoming Morse code. * If it is disabled, the receive functions will use fixed speed settings, * and reject incoming Morse which is not at the expected speed. The * cw_get_adaptive_receive_state function returns TRUE if adaptive speed * tracking is enabled, FALSE otherwise. The default state is adaptive * speed tracking disabled. */ void cw_enable_adaptive_receive () { cw_set_adaptive_receive_internal (TRUE); } void cw_disable_adaptive_receive () { cw_set_adaptive_receive_internal (FALSE); } int cw_get_adaptive_receive_state () { return cw_adaptive_receive_enabled; } /** * cw_enable_iambic_curtis_mode_b() * cw_disable_iambic_curtis_mode_b() * cw_get_iambic_curtis_mode_b_state() * * Normally, the iambic keying functions will emulate Curtis 8044 Keyer * mode A. In this mode, when both paddles are pressed together, the last * dot or dash being sent on release is completed, and nothing else is sent. * In mode B, when both paddles are pressed together, the last dot or dash * being sent on release is completed, then an opposite element is also sent. * Some operators prefer mode B, but timing is more critical in this mode. * The default mode is Curtis mode A. */ void cw_enable_iambic_curtis_mode_b () { cw_ik_curtis_mode_b = TRUE; } void cw_disable_iambic_curtis_mode_b () { cw_ik_curtis_mode_b = FALSE; } int cw_get_iambic_curtis_mode_b () { return cw_ik_curtis_mode_b; } /** * cw_[gs]et_noise_spike_threshold() * * Set and get the period shorter than which, on receive, received tones are * ignored. This allows the receive tone functions to apply noise cancelling * for very short apparent tones. For useful results the value should never * exceed the dot length of a dot at maximum speed; 20,000 Usec (the dot * length at 60WPM). Setting a noise threshold of zero turns off receive * tone noise cancelling. The default noise spike threshold is 10,000 Usec. */ int cw_set_noise_spike_threshold (int threshold) { if (threshold < 0) { errno = EINVAL; return RC_ERROR; } cw_noise_spike_threshold = threshold; return RC_SUCCESS; } int cw_get_noise_spike_threshold () { return cw_noise_spike_threshold; } /** * cw_get_send_parameters() * cw_get_receive_parameters() * * Return the low-level timing parameters calculated from the speed, gap, * and tolerance set. Parameter values are returned in usecs. Use NULL * for the pointer argument to any parameter value not required. */ void cw_get_send_parameters (int *dot_usecs, int *dash_usecs, int *end_of_element_usecs, int *end_of_character_usecs, int *end_of_word_usecs, int *additional_usecs, int *adjustment_usecs) { cw_sync_parameters_internal (); if (dot_usecs != NULL) *dot_usecs = cw_send_dot_length; if (dash_usecs != NULL) *dash_usecs = cw_send_dash_length; if (end_of_element_usecs != NULL) *end_of_element_usecs = cw_end_of_ele_delay; if (end_of_character_usecs != NULL) *end_of_character_usecs = cw_end_of_char_delay; if (end_of_word_usecs != NULL) *end_of_word_usecs = cw_end_of_word_delay; if (additional_usecs != NULL) *additional_usecs = cw_additional_delay; if (adjustment_usecs != NULL) *adjustment_usecs = cw_adjustment_delay; } void cw_get_receive_parameters (int *dot_usecs, int *dash_usecs, int *dot_min_usecs, int *dot_max_usecs, int *dash_min_usecs, int *dash_max_usecs, int *end_of_element_min_usecs, int *end_of_element_max_usecs, int *end_of_character_min_usecs, int *end_of_character_max_usecs, int *adaptive_threshold) { cw_sync_parameters_internal (); if (dot_usecs != NULL) *dot_usecs = cw_receive_dot_length; if (dash_usecs != NULL) *dash_usecs = cw_receive_dash_length; if (dot_min_usecs != NULL) *dot_min_usecs = cw_dot_range_minimum; if (dot_max_usecs != NULL) *dot_max_usecs = cw_dot_range_maximum; if (dash_min_usecs != NULL) *dash_min_usecs = cw_dash_range_minimum; if (dash_max_usecs != NULL) *dash_max_usecs = cw_dash_range_maximum; if (end_of_element_min_usecs != NULL) *end_of_element_min_usecs = cw_eoe_range_minimum; if (end_of_element_max_usecs != NULL) *end_of_element_max_usecs = cw_eoe_range_maximum; if (end_of_character_min_usecs != NULL) *end_of_character_min_usecs = cw_eoc_range_minimum; if (end_of_character_max_usecs != NULL) *end_of_character_max_usecs = cw_eoc_range_maximum; if (adaptive_threshold != NULL) *adaptive_threshold = cw_adaptive_receive_threshold; } /** * cw_sound_get_pcm_volume_internal() * * Return the PCM channel volume setting for the soundcard. This function * tries to apply the PCM volume read ioctl() to the main soundcard device * first. If this fails, it retries using the PCM channel of the given * mixer device, if it has one. The function returns the retrieved volume * set on success. */ static int cw_sound_get_pcm_volume_internal (int *volume) { int read_volume; /* Returned volume setting */ int file_descriptor; /* Mixer file descriptor */ int device_mask; /* Mixer channels mask */ /* Try to use the main /dev/dsp device for ioctls first. */ if (ioctl (cw_sound_descriptor, MIXER_READ(SOUND_MIXER_PCM), &read_volume) == 0) { /* Found the volume easily; return it. */ *volume = read_volume; return RC_SUCCESS; } /* Try the mixer PCM channel volume instead. */ file_descriptor = open (cw_mixer_device, O_RDONLY | O_NONBLOCK); if (file_descriptor == -1) { fprintf (stderr, "cw: open "); perror (cw_mixer_device); return RC_ERROR; } /* Check the available mixer channels. */ if (ioctl (file_descriptor, SOUND_MIXER_READ_DEVMASK, &device_mask) == -1) { perror ("cw: ioctl SOUND_MIXER_READ_DEVMASK"); close (file_descriptor); return RC_ERROR; } if (device_mask & SOUND_MASK_PCM) { /* Read the PCM volume from the mixer. */ if (ioctl (file_descriptor, MIXER_READ(SOUND_MIXER_PCM), &read_volume) == -1) { perror ("cw: ioctl MIXER_READ(SOUND_MIXER_PCM)"); close (file_descriptor); return RC_ERROR; } /* Return the volume just read. */ *volume = read_volume; close (file_descriptor); return RC_SUCCESS; } else { if (device_mask & SOUND_MASK_VOLUME) { /* If in extreme difficulty, try main volume. */ if (ioctl (file_descriptor, MIXER_READ(SOUND_MIXER_VOLUME), &read_volume) == -1) { perror ("cw: ioctl" " MIXER_READ(SOUND_MIXER_VOLUME)"); close (file_descriptor); return RC_ERROR; } /* Return the volume just read. */ *volume = read_volume; close (file_descriptor); return RC_SUCCESS; } } /* There seems to be no way to control volumes. */ errno = EINVAL; perror ("cw: mixer DEVMASK lacks volume controls"); close (file_descriptor); return RC_ERROR; } /** * cw_sound_set_pcm_volume_internal() * * Set the PCM channel volume setting for the soundcard. This function * tries to apply the PCM volume write ioctl() to the main soundcard device * first. If this fails, it retries using the PCM channel of the given * mixer device, if it has one. */ static int cw_sound_set_pcm_volume_internal (int volume) { int file_descriptor; /* Mixer file descriptor */ int device_mask; /* Mixer channels mask */ /* Try to use the main /dev/dsp device for ioctls first. */ if (ioctl (cw_sound_descriptor, MIXER_WRITE(SOUND_MIXER_PCM), &volume) == 0) return RC_SUCCESS; /* Try the mixer PCM channel volume instead. */ file_descriptor = open (cw_mixer_device, O_RDWR | O_NONBLOCK); if (file_descriptor == -1) { fprintf (stderr, "cw: open "); perror (cw_mixer_device); return RC_ERROR; } /* Check the available mixer channels. */ if (ioctl (file_descriptor, SOUND_MIXER_READ_DEVMASK, &device_mask) == -1) { perror ("cw: ioctl SOUND_MIXER_READ_DEVMASK"); close (file_descriptor); return RC_ERROR; } if (device_mask & SOUND_MASK_PCM) { /* Write the PCM volume to the mixer. */ if (ioctl (file_descriptor, MIXER_WRITE(SOUND_MIXER_PCM), &volume) == -1) { perror ("cw: ioctl MIXER_WRITE(SOUND_MIXER_PCM)"); close (file_descriptor); return RC_ERROR; } /* PCM volume written successfully. */ close (file_descriptor); return RC_SUCCESS; } else { if (device_mask & SOUND_MASK_VOLUME) { /* If in extreme difficulty, try main volume. */ if (ioctl (file_descriptor, MIXER_WRITE(SOUND_MIXER_VOLUME), &volume) == -1) { perror ("cw: ioctl" " MIXER_WRITE(SOUND_MIXER_VOLUME)"); close (file_descriptor); return RC_ERROR; } /* Main volume written successfully. */ close (file_descriptor); return RC_SUCCESS; } } /* There seems to be no way to control volumes. */ errno = EINVAL; perror ("cw: mixer DEVMASK lacks volume controls"); close (file_descriptor); return RC_ERROR; } /** * cw_sound_soundcard_open_internal() * * Open and initialize the sound device for Morse code tones. This function * sets suitable sampling rates and formats, and notes the file descriptor * and the sample rate set for the device in library variables. */ static int cw_sound_soundcard_open_internal () { int file_descriptor; /* Sound file descriptor */ int parameter; /* General sound ioctl arg */ /* Ignore the call if the sound device is already open. */ if (cw_sound_open) return RC_SUCCESS; /* Open the given soundcard device file, for write only. */ file_descriptor = open (cw_sound_device, O_WRONLY | O_NONBLOCK); if (file_descriptor == -1) { fprintf (stderr, "cw: open "); perror (cw_sound_device); return RC_ERROR; } /* * Live a little dangerously, by trying to set the fragment size * of the card. We'll try for a relatively short fragment of 128 * bytes. This gives us a little better granularity over the * amounts of audio data we write periodically to the soundcard * output buffer. We may not get the requested fragment size at * all, and may be stuck with the default. The argument has the * format 0xMMMMSSSS - fragment size is 2^SSSS, and setting 0x7FFF * for MMMM allows as many fragments as the driver can support. */ parameter = 0x7FFF << 16 | DSP_SETFRAGMENT; if (ioctl (file_descriptor, SNDCTL_DSP_SETFRAGMENT, ¶meter) == -1) { perror ("cw: ioctl SNDCTL_DSP_SETFRAGMENT"); close (file_descriptor); return RC_ERROR; } /* Set the audio format to 8-bit unsigned. */ parameter = DSP_FORMAT; if (ioctl (file_descriptor, SNDCTL_DSP_SETFMT, ¶meter) == -1) { perror ("cw: ioctl SNDCTL_DSP_SETFMT"); close (file_descriptor); return RC_ERROR; } if (parameter != DSP_FORMAT) { errno = ERR_NO_SUPPORT; perror ("cw: sound AFMT_U8 not supported"); close (file_descriptor); return RC_ERROR; } /* Set up mono mode - a single audio channel. */ parameter = DSP_CHANNELS; if (ioctl (file_descriptor, SNDCTL_DSP_CHANNELS, ¶meter) == -1) { perror ("cw: ioctl SNDCTL_DSP_CHANNELS"); close (file_descriptor); return RC_ERROR; } if (parameter != DSP_CHANNELS) { errno = ERR_NO_SUPPORT; perror ("cw: sound mono not supported"); close (file_descriptor); return RC_ERROR; } /* * Set up a standard sampling rate based on the notional correct * value, and retain the one we actually get in the library variable. */ cw_sound_sample_rate = DSP_RATE; if (ioctl (file_descriptor, SNDCTL_DSP_SPEED, &cw_sound_sample_rate) == -1) { perror ("cw: ioctl SNDCTL_DSP_SPEED"); close (file_descriptor); return RC_ERROR; } if (cw_sound_sample_rate != DSP_RATE) { if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: dsp sample_rate -> %d\n", cw_sound_sample_rate); } /* Query fragment size just to get the driver buffers set. */ if (ioctl (file_descriptor, SNDCTL_DSP_GETBLKSIZE, ¶meter) == -1) { perror ("cw: ioctl SNDCTL_DSP_GETBLKSIZE"); close (file_descriptor); return RC_ERROR; } if (parameter != 1 << DSP_SETFRAGMENT) { if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: dsp fragment size not set, %d\n", parameter); } /* * Save the opened file descriptor in a library variable. Do it * now rather than later since the volume setting functions will * try to use it. */ cw_sound_descriptor = file_descriptor; /* Save the current volume setting of the sound device. */ if (cw_sound_get_pcm_volume_internal (&cw_sound_saved_vol) == RC_ERROR) { close (cw_sound_descriptor); cw_sound_descriptor = -1; return RC_ERROR; } /* Set the mixer volume to zero, so the card is silent initially. */ if (cw_sound_set_pcm_volume_internal (0) == RC_ERROR) { close (cw_sound_descriptor); cw_sound_descriptor = -1; return RC_ERROR; } /* Print out requested debug on sound file open. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: dsp opened\n"); /* Note sound as now open for business, and return success. */ cw_sound_open = TRUE; return RC_SUCCESS; } /** * cw_sound_soundcard_close_internal() * * Flush the soundcard output buffer, and close the dsp device file. */ static int cw_sound_soundcard_close_internal () { /* Only attempt to close the sound device if it is already open. */ if (!cw_sound_open) return RC_SUCCESS; /* Stop any and all current tone output. */ if (ioctl (cw_sound_descriptor, SNDCTL_DSP_RESET, 0) == -1) { /* This will make the close take forever... */ perror ("cw: ioctl SNDCTL_DSP_RESET"); } /* Restore the saved volume. */ if (cw_sound_set_pcm_volume_internal (cw_sound_saved_vol) == RC_ERROR) return RC_ERROR; /* Close the file descriptor, and note as closed. */ close (cw_sound_descriptor); cw_sound_descriptor = -1; cw_sound_open = FALSE; /* Print out requested debug on sound file close. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: dsp flushed and closed\n"); return RC_SUCCESS; } /** * cw_sound_generate_internal() * * If we are currently charged with generating soundcard tone data, put * a quantum of audio, at the current sampling rate, into the DSP device. * The quantum is at most about a second's worth of audio data - the * function stops at this point, or when it finds that the soundcard * output buffer is full. The function checks the current sound generate * frequency, and goes idle if this is set to TONE_SILENT prior to a call. * * One second of audio should be sufficient for all current uses of the * tone queue. For CW at the slowest rate supported by the library * (4 WPM), a dash is 900 ms (3 dots, of 1200 / 4 ms), so for normal * use by the sending and iambic keying functions, there will not be a * case where dequeued tones happen less frequently than once per second, * and since dequeueing tones calls this function, the soundcard data * buffer cannot underrun. The problem case is where the straight key * is held down for more than one second. To avoid underruns, the * straight key functions can use the timeout callback to regularly call * this function, ensuring that the soundcard buffer remains populated. */ static int cw_sound_generate_internal () { static const int amplitude = 100; /* Wave amplitude multiplier */ static float phase_offset = 0.0; /* Wave shape phase offset */ static int current_frequency = TONE_SILENT; /* Note of freq on last call */ audio_buf_info info_buf; /* Soundcard buffer info */ unsigned char *buffer; /* Sound fragment buffer */ int fragment; /* Sound fragment counter */ int index; /* Sound fragment index */ int max_bytes; /* Limit on audio data added */ int bytes; /* Count of audio data added */ float offset_abs, offset_term;/* Wave phase temporaries */ /* Print out some debug trace on a call to generate audio data. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: dsp generate request, %d Hz\n", cw_sound_generate_frequency); /* Look first for a switch to silence in the generated tone. */ if (cw_sound_generate_frequency == TONE_SILENT) { if (current_frequency != TONE_SILENT) { /* Stop generating soundcard tones, and go idle. */ cw_sound_soundcard_close_internal (); /* Clear the current generated tone and phase. */ current_frequency = TONE_SILENT; phase_offset = 0.0; /* Trace this for debugging purposes. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: dsp goes idle\n"); } } else { /* Now handle any change of tone generation variable. */ if (cw_sound_generate_frequency != current_frequency) { /* * To fully reset to a new tone frequency, close the * audio device, then reopen it. This seems a little * blunt, but it's the recommended action according * to the OSS doc. */ if (current_frequency != TONE_SILENT) cw_sound_soundcard_close_internal (); if (cw_sound_soundcard_open_internal () == RC_ERROR) { /* Unable to reopen the soundcard. */ return RC_ERROR; } /* Reset the current generated tone and phase. */ current_frequency = cw_sound_generate_frequency; phase_offset = 0.0; /* Trace this too for debugging purposes. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: dsp changes to %d Hz\n", current_frequency); } } /* * Now generate soundcard tone data depending on the value of * the current frequency as determined or reset above. */ if (current_frequency != TONE_SILENT) { /* Get the current buffer status of the soundcard. */ if (ioctl (cw_sound_descriptor, SNDCTL_DSP_GETOSPACE, &info_buf) == -1) { perror ("cw: ioctl SNDCTL_DSP_GETOSPACE"); return RC_ERROR; } /* Create a buffer for one sound fragment. */ buffer = malloc (info_buf.fragsize); /* * Put a limit on the data written on this pass. The limit * is set initially to be about one second of audio, but * if this is less than two fragments, and if the soundcard * buffer is totally empty (all fragments available), then * then it's increased to be at least two fragments. This * is because the OSS driver optimizes operation by waiting * in most cases until at least two fragments contain data. */ max_bytes = cw_sound_sample_rate; if (max_bytes < 2 * info_buf.fragsize) { if (info_buf.fragments == info_buf.fragstotal) max_bytes = 2 * info_buf.fragsize; } /* * Loop for each unfilled fragment, or until the maximum * number of bytes has been written. * * The tone generation code below is based heavily on work * by Paolo Cravero . */ for (fragment = 0, bytes = 0; fragment < info_buf.fragments && bytes < max_bytes; fragment++) { /* Create a fragment's worth of shaped wave data. */ for ( index = 0; index < info_buf.fragsize; index++) { buffer[ index ] = (char)(amplitude * sin (2 * M_PI * (float)current_frequency * (float)index / (float)cw_sound_sample_rate + phase_offset) + 128 + phase_offset); } /* Compute the phase of the last generated sample. */ offset_abs = 2 * M_PI * (float)current_frequency * (float)index / (float)cw_sound_sample_rate + phase_offset; /* Extract the normalized phase offset. */ offset_term = offset_abs / 2 / M_PI; phase_offset = offset_abs - floor (offset_term) * 2 * M_PI; /* Write the buffer data. */ if (write (cw_sound_descriptor, buffer, info_buf.fragsize) != info_buf.fragsize) { perror ("cw: soundcard write"); free (buffer); return RC_ERROR; } /* Update the count of bytes written on this call. */ bytes += info_buf.fragsize; } /* Done with the fragment buffer. */ free (buffer); /* Print out a little debug about the data generated. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: dsp data buffered, %d Hz, %d\n", cw_sound_generate_frequency, bytes); } /* Return success status. */ return RC_SUCCESS; } /** * cw_sound_soundcard_internal() * * Set up a tone on the soundcard. The function starts the generation of * any non-silent tone, and tries to keep that tone going on subsequent calls. * * If the input frequency is silent, the function will turn off audio output * at the card, but keep on generating tone data. This is an optmization; * the function is optimized for calls to either the same frequency, or to * silence (that is, on/off keying). Calls that change frequency require * the sound wave buffer to be flushed and reloaded, and this is a somewhat * lengthy process. */ static int cw_sound_soundcard_internal (int frequency) { static int current_frequency = TONE_SILENT; /* Note of freq on last call */ static int current_volume = 0; /* Note of volume last call */ int volume; /* Soundcard volume arg */ /* Print out requested debug on tone generation request. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: dsp request %d Hz, current %d Hz, " "volume %d %%, current %d %%\n", frequency, current_frequency, cw_volume, current_volume); /* Look first for a change to the current sound frequency. */ if (frequency != current_frequency) { /* If moving to silence, set the mixer volume to zero. */ if (frequency == TONE_SILENT) { /* Keep background tone data generation going. */ cw_sound_generate_internal (); /* Suppress sound by setting the volume to zero. */ volume = 0; } else { /* Reset generated tone, then prod data generation. */ cw_sound_generate_frequency = frequency; cw_sound_generate_internal (); /* * Set the volume according to the library variable. * Volume is held as a percentage, and supplied to * the card as two values, one per channel, in the * lower two bytes of the volume argument. */ volume = cw_volume << 8 | cw_volume; } /* * Set this mixer volume on the sound device, unless there * was some problem or another opening the soundcard file * in the first place. */ if (cw_sound_open && cw_sound_set_pcm_volume_internal (volume) == RC_ERROR) return RC_ERROR; /* Set this new frequency and volume as our current one. */ current_frequency = frequency; current_volume = cw_volume; } /* Look for changes to the main volume, but the same frequency. */ else if (cw_volume != current_volume) { /* Keep background tone data generation going. */ cw_sound_generate_internal (); /* Do nothing more if current frequency is silent. */ if (frequency != TONE_SILENT) { /* * Reset only the volume for the soundcard, * using the same setting trick as we used * above. */ volume = cw_volume << 8 | cw_volume; /* * Set this mixer volume on the sound device, * providing the sound card opened okay. */ if (cw_sound_open && cw_sound_set_pcm_volume_internal (volume) == RC_ERROR) return RC_ERROR; /* Set this new volume as our current one. */ current_volume = cw_volume; } } else { /* * No change in sound frequency or volume, but keep * the data generation going to keep soundcard * buffers from underrunning. */ cw_sound_generate_internal (); } /* Return successfully from the call. */ return RC_SUCCESS; } /** * cw_sound_console_open_internal() * * Open the console device for Morse code tones. Verify that the given * device can do KIOCSOUND ioctls before returning. */ #ifdef KIOCSOUND static int cw_sound_console_open_internal () { int file_descriptor; /* Sound file descriptor */ /* Ignore the call if the console device is already open. */ if (cw_console_open) return RC_SUCCESS; /* Open the console device file, for write only. */ file_descriptor = open (cw_console_device, O_WRONLY); if (file_descriptor == -1) { fprintf (stderr, "cw: open "); perror (cw_console_device); return RC_ERROR; } /* Check to see if the file can do console tones. */ if (ioctl (file_descriptor, KIOCSOUND, 0) == -1) { close (file_descriptor); return RC_ERROR; } /* Print out requested debug on console file open. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: console opened\n"); /* Save the opened file descriptor in a library variable. */ cw_console_descriptor = file_descriptor; /* Note console as now open for business, and return success. */ cw_console_open = TRUE; return RC_SUCCESS; } #endif /** * cw_sound_console_close_internal() * * Close the console device file. */ static int cw_sound_console_close_internal () { /* Only attempt to close the console device if it is already open. */ if (!cw_console_open) return RC_SUCCESS; /* Close the file descriptor, and note as closed. */ close (cw_console_descriptor); cw_console_descriptor = -1; cw_console_open = FALSE; /* Print out requested debug on sound file close. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: console closed\n"); return RC_SUCCESS; } /** * cw_sound_console_internal() * * Set up a tone on the console PC speaker. The function calls the * KIOCSOUND ioctl to start a particular tone generating in the kernel. * Once started, the console tone generation needs no maintenance. */ static int cw_sound_console_internal (int frequency) { #ifdef KIOCSOUND static int current_frequency = TONE_SILENT; /* Note of freq on last call */ static int current_volume = 0; /* Note of volume last call */ int kiocsound_arg; /* Argument for KIOCSOUND */ /* Print out requested debug on tone generation request. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: console request %d Hz, current %d Hz, " "volume %d %%, current %d %%\n", frequency, current_frequency, cw_volume, current_volume); /* * Look for changes to the current sound frequency, or changes to * the main volume where the volume goes either to, or from, zero. */ if (frequency != current_frequency || (cw_volume != current_volume && (cw_volume == 0 || current_volume == 0))) { /* * Calculate the correct argument for KIOCSOUND. There's * nothing we can do to control the volume, but if we find * the volume is set to zero, the one thing we can do is * to just turn off tones. A bit crude, but perhaps just * slightly better than doing nothing. */ if (cw_volume > 0) kiocsound_arg = (frequency != TONE_SILENT) ? TONE_MAGIC / frequency: 0; else kiocsound_arg = 0; /* Print out any requested debug on sound. */ if (cw_get_debug_flags () & CW_DEBUG_SOUND) fprintf (stderr, "cw: kiocsound %d Hz, %d\n", frequency, kiocsound_arg); /* If the console file is not open, open it now. */ if (!cw_console_open) cw_sound_console_open_internal (); /* Call the ioctl, and return any error status. */ if (ioctl (cw_console_descriptor, KIOCSOUND, kiocsound_arg) == -1) { perror ("cw: ioctl KIOCSOUND"); return RC_ERROR; } /* Set this new frequency and volume as our current one. */ current_frequency = frequency; current_volume = cw_volume; } /* Return successfully from the call. */ return RC_SUCCESS; #else return RC_ERROR; #endif } /** * cw_sound_release_internal() * * Release the hold that the library has on the soundcard and the console. * This function closes the console, closes the soundcard, and resets * background tone generation so that no work is done on tone creation * until the next tone is requested. This also ensures that the DSP * soundcard device remains closed until it is needed again. */ static int cw_sound_release_internal () { /* Close the console if it is open. */ if (cw_console_open) cw_sound_console_close_internal (); /* Silence the soundcard if there is current soundcard activity. */ if (cw_sound_generate_frequency != TONE_SILENT) { /* Tell the background tone generation to go idle. */ cw_sound_generate_frequency = TONE_SILENT; cw_sound_generate_internal (); } return RC_SUCCESS; } /** * cw_sound_internal() * * Start a tone of the given frequency, on either of, or both of, the sound * card and the console PC speaker. This function returns a status, but * routines running inside signal handlers are free to ignore this. The * function does nothing if silence is requested in the library flags. */ static int cw_sound_internal (int frequency) { int soundcard_error; /* Soundcard error status */ int console_error; /* Console error status */ /* If silence requested, then ignore the call. */ if (!(cw_get_debug_flags () & CW_DEBUG_SILENT)) { /* Call the appropriate main tone generating functions. */ soundcard_error = RC_SUCCESS; if (cw_sound_soundcard_on) soundcard_error = cw_sound_soundcard_internal (frequency); console_error = RC_SUCCESS; if (cw_sound_console_on) console_error = cw_sound_console_internal (frequency); /* Return an error status if either of the above failed. */ if (soundcard_error || console_error) return RC_ERROR; } /* Return a success status on success, or when running silently. */ return RC_SUCCESS; } /** * cw_set_console_sound() * cw_get_console_sound() * * Enable and disable console sound output, and return the current console * sound output setting. By default, console sound output is enabled. */ void cw_set_console_sound (int sound_state) { /* See if console sound is being disabled. */ if (cw_sound_console_on && !sound_state) { /* Send the console into silent mode. */ cw_sound_console_internal (TONE_SILENT); } /* Save the new control state. */ cw_sound_console_on = (sound_state != 0); } int cw_get_console_sound () { return cw_sound_console_on; } /** * cw_set_soundcard_sound() * cw_get_soundcard_sound() * * Enable and disable soundcard sound output, and return the current * soundcard sound output setting. By default, soundcard sound output * is disabled. */ void cw_set_soundcard_sound (int sound_state) { /* See if soundcard sound is being disabled. */ if (cw_sound_soundcard_on && !sound_state) { /* Silence the soundcard, and release it altogether. */ cw_sound_soundcard_internal (TONE_SILENT); cw_sound_release_internal (); } /* Save the new control state. */ cw_sound_soundcard_on = (sound_state != 0); } int cw_get_soundcard_sound () { return cw_sound_soundcard_on; } /** * cw_keying_callback() * * Register a function that should be called when a tone state changes * from key-up to key-down, or vice-versa. The argument passed out to * the registered function is the key state: TRUE for down, FALSE for up. * Calling this routine with an NULL function address disables keying * callbacks. */ void cw_keying_callback (void (*callback_func) (int)) { cw_kk_key_callback = callback_func; } /** * cw_ptt_callback() * * Register a function that should be called when a ptt event occurs. * The argument passed out to the registered function is the ptt state: * TRUE for ptt on, FALSE for ptt off. * Calling this routine with an NULL function address disables ptt * callbacks. */ void cw_ptt_callback(void (*callback_func) (int)) { cw_ptt_key_callback=callback_func; } /* * cw_ptt() * calls the provided ptt function via the callback handler if not NULL * */ void cw_ptt_control(int requested_ptt_state) { static int current_ptt_state = FALSE; /* Ignore the call if there is no change of keying state. */ if (current_ptt_state != requested_ptt_state) { if (cw_get_debug_flags () & CW_DEBUG_PTT) fprintf (stderr, "cw: ptt state %d->%d\n", current_ptt_state, requested_ptt_state); /* Set the new ptt state, and call any requested callback. */ current_ptt_state = requested_ptt_state; if (cw_ptt_key_callback != NULL) (*cw_ptt_key_callback) (current_ptt_state); } } /** * cw_key_control_internal() * * Control function that calls any requested keying callback only when * there is a change of keying state. This function filters successive * key-down or key-up actions into a single action. */ static void cw_key_control_internal (int requested_key_state) { static int current_key_state = FALSE; /* Maintain key control state */ /* Ignore the call if there is no change of keying state. */ if (current_key_state != requested_key_state) { /* Print out any requested debug on keying. */ if (cw_get_debug_flags () & CW_DEBUG_KEYING) fprintf (stderr, "cw: keying state %d->%d\n", current_key_state, requested_key_state); /* Set the new keying state, and call any requested callback. */ current_key_state = requested_key_state; if (cw_kk_key_callback != NULL) (*cw_kk_key_callback) (current_key_state); } } /** * cw_sigalrm_handler_internal() * * Common SIGALRM handler. This function calls the signal handlers of * the library subsystems, expecting them to ignore unexpected calls. */ static void cw_sigalrm_handler_internal (int signal_number) { int index; /* Low level handler index */ /* Avoid compiler warnings about unused argument. */ signal_number = 0; /* * Call the known functions that are interested in this signal. * Stop on the first free slot found; valid because the array is * filled in order from index 0, and there are no deletions. */ for (index = 0; index < SIGALRM_HANDLERS && cw_request_handlers[index] != NULL; index++) (*(cw_request_handlers[index])) (); } /** * cw_request_timeout_internal() * * Install the SIGALRM handler, if not yet installed. Add any given lower * level handler to the list of registered handlers. Then set an itimer * to expire after the requested number of usecs. */ static int cw_request_timeout_internal (int usec, void (*request_handler) ()) { int error; /* Error status */ int index; /* Low level handler index */ struct sigaction action; /* Sigaction structure */ struct itimerval itimer; /* Itimer control structure */ /* Don't install the handler if we have already done it. */ if (!cw_sigalrm_handler_installed) { /* * Register the SIGALRM handler routine, and keep the old * information so we can put it back when useful to do so. */ action.sa_handler = cw_sigalrm_handler_internal; action.sa_flags = SA_RESTART; sigemptyset (&action.sa_mask); error = sigaction (SIGALRM, &action, &cw_sigalrm_original_disposition); if (error == -1) { perror ("cw: sigaction"); return RC_ERROR; } /* Set the flag so we don't do this again. */ cw_sigalrm_handler_installed = TRUE; } /* * If it's not already present, and one was given, add the request * handler address to the list of known handlers. */ if (request_handler != NULL) { /* Search for this handler, or the first free entry. */ for (index = 0; index < SIGALRM_HANDLERS && cw_request_handlers[index] != request_handler && cw_request_handlers[index] != NULL; ) index++; /* * If the handler is already there, do no more. Otherwise, * add it to the list of lower level handlers. */ if (cw_request_handlers[index] != request_handler) { if (cw_request_handlers[index] != NULL) { errno = ENOMEM; perror ("cw: overflow cw_request_handlers"); return RC_ERROR; } cw_request_handlers[index] = request_handler; } } /* * Depending on the value of usec, either set an itimer, or send * ourselves SIGALRM right away. */ if (usec <= 0) { /* Send ourselves the SIGALRM immediately. */ if (kill (getpid (), SIGALRM) != 0) { perror ("cw: kill"); return RC_ERROR; } } else { /* * Set the itimer to produce a single interrupt after the given * duration. */ itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_usec = 0; itimer.it_value.tv_sec = usec / USECS_PER_SEC; itimer.it_value.tv_usec = usec % USECS_PER_SEC; error = setitimer (ITIMER_REAL, &itimer, NULL); if (error == -1) { perror ("cw: setitimer"); return RC_ERROR; } } return RC_SUCCESS; } /** * cw_release_timeouts_internal() * * Uninstall the SIGALRM handler, if installed. Return SIGALRM's disposition * for the system to the state we found it in before we installed our own * SIGALRM handler. */ static int cw_release_timeouts_internal () { int error; /* Error status */ struct itimerval itimer; /* Itimer control structure */ /* Ignore the call if we haven't installed our handler. */ if (cw_sigalrm_handler_installed) { /* Cancel any pending itimer setting. */ itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_usec = 0; itimer.it_value.tv_sec = 0; itimer.it_value.tv_usec = 0; error = setitimer (ITIMER_REAL, &itimer, NULL); if (error == -1) { perror ("cw: setitimer"); return RC_ERROR; } /* Put back the SIGALRM information saved earlier. */ error = sigaction (SIGALRM, &cw_sigalrm_original_disposition, NULL ); if (error == -1) { perror ("cw: sigaction"); return RC_ERROR; } /* * Clear the flag so we know to reinstall the handler when * we get the next timeout request. */ cw_sigalrm_handler_installed = FALSE; } return RC_SUCCESS; } /** * cw_check_signal_mask_internal() * * Check the signal mask of the process, and return an error, with errno * set to EDEADLK, if SIGALRM is blocked. */ static int cw_check_signal_mask_internal () { int error; /* Error status */ sigset_t emptyset; /* Empty sigset structure */ sigset_t currset; /* Sigset for current state */ /* Block a empty set of signals to obtain the current mask. */ sigemptyset (&emptyset); error = sigprocmask (SIG_BLOCK, &emptyset, &currset); if (error == -1) { perror ("cw: sigprocmask"); return RC_ERROR; } /* Check that SIGALRM is not blocked in the current mask. */ if (sigismember (&currset, SIGALRM)) { errno = EDEADLK; return RC_ERROR; } return RC_SUCCESS; } /** * cw_block_signal_internal() * * Block SIGALRM for the duration of certain critical sections, or unblock * after; passed TRUE to block SIGALRM, and FALSE to unblock. */ static int cw_block_signal_internal (int block) { int error; /* Error status */ sigset_t blockset; /* Block sigset structure */ /* Block SIGALRM for the process. */ sigemptyset (&blockset); sigaddset (&blockset, SIGALRM); error = sigprocmask (block ? SIG_BLOCK : SIG_UNBLOCK, &blockset, NULL); if (error == -1) { perror ("cw: sigprocmask"); return RC_ERROR; } return RC_SUCCESS; } /** * cw_signal_wait_internal() * * Wait for a signal, usually a SIGALRM. Assumes SIGALRM is not blocked. */ static int cw_signal_wait_internal () { int error; /* Error status */ sigset_t emptyset; /* Empty sigset structure */ sigset_t currset; /* Sigset for current state */ /* Block a empty set of signals to obtain the current mask. */ sigemptyset (&emptyset); error = sigprocmask (SIG_BLOCK, &emptyset, &currset); if (error == -1) { perror ("cw: sigprocmask"); return RC_ERROR; } /* Wait on the current mask. */ error = sigsuspend (&currset); if (error == -1 && errno != EINTR) { perror ("cw: sigsuspend"); return RC_ERROR; } return RC_SUCCESS; } /* * cw_dequeue_tone_internal implements the following (trivial) state graph: * * (queue empty) * +-------------------------------+ * | | * v (queue started) | * --> QS_IDLE ---------------> QS_BUSY --+ * ^ | * | | * +-----+ * (queue not empty) */ static enum {QS_IDLE,QS_BUSY} cw_dequeue_state = QS_IDLE; /* Indicates empty queue */ /** * cw_dequeue_tone_internal() * * Signal handler for itimer. Dequeue a tone request, and send the ioctl * to generate the tone. If the queue is empty when we get the signal, * then we're at the end of the work list, so set the dequeue state to * idle and return. */ static void cw_dequeue_tone_internal () { int usec; /* Next tone duration */ int frequency; /* Next tone frequency */ int queue_length; /* Queued tones at the start */ int now_length; /* Queued tones after scan */ /* Ignore the call if the current state is idle. */ if (cw_dequeue_state == QS_IDLE) return; /* See if the queue contains any pending tones. */ if (cw_tq_tail != cw_tq_head) { /* * Calculate the current queue length. Later on, we'll * compare with the length after we've scanned over every * tone we can omit, and use this to see if we've crossed * the low water mark, if any. */ if (cw_tq_head >= cw_tq_tail) queue_length = cw_tq_head - cw_tq_tail; else queue_length = cw_tq_head - cw_tq_tail + TONE_QUEUE_CAPACITY; /* * Advance over the tones list until we find the first tone * with a duration of more than zero usecs, or until the end * of the list. */ do { if (cw_tq_tail + 1 < TONE_QUEUE_CAPACITY) cw_tq_tail++; else cw_tq_tail = 0; } while (cw_tq_tail != cw_tq_head && cw_tone_queue[cw_tq_tail].usec == 0); /* Dequeue the next tone to send. */ usec = cw_tone_queue[cw_tq_tail].usec; frequency = cw_tone_queue[cw_tq_tail].frequency; /* Print out any requested debug on the tone started. */ if (cw_get_debug_flags () & CW_DEBUG_TONE_QUEUE) fprintf (stderr, "cw: dequeue tone %d usec, %d Hz\n", usec, frequency); /* * Start the tone. If the ioctl fails, there's nothing we * can do at this point, in the way of returning error codes. */ cw_sound_internal (frequency); /* * Notify the key control function that there might have * been a change of keying state (and then again, there * might not have been - it will sort this out for us). */ if (frequency != TONE_SILENT) cw_key_control_internal (TRUE); else cw_key_control_internal (FALSE); /* * If microseconds is zero, leave it at that. This way, a * queued tone of 0 usec implies leaving the sound in this * state, and 0 usec and 0 frequency leaves silence. */ if (usec > 0) /* * Request a timeout. If it fails, there's little * we can do at this point. But it shouldn't fail. */ cw_request_timeout_internal (usec, NULL); else { /* Autonomous dequeuing has finished for the moment. */ cw_dequeue_state = QS_IDLE; cw_release_timeouts_internal (); cw_sound_release_internal (); } /* * If there is a low water mark callback registered, and if * we passed under the water mark, call the callback here. * We want to be sure to call this late in the processing, * especially after setting the state to idle, since the most * likely action of this routine is to queue tones, and we * don't want to play with the state here after that. */ if (cw_tq_low_water_callback != NULL) { /* Calculate the current queue length. */ if (cw_tq_head >= cw_tq_tail) now_length = cw_tq_head - cw_tq_tail; else now_length = cw_tq_head - cw_tq_tail + TONE_QUEUE_CAPACITY; /* * If the length we originally calculated was above * the low water mark, and the one we have now is * below or equal to it, call the callback. */ if (queue_length > cw_tq_low_water_mark && now_length <= cw_tq_low_water_mark) (cw_tq_low_water_callback) (); } } else { /* * This is the end of the last tone on the queue, and since * we got a signal we know that it had a usec greater than * zero. So this is the time to return to silence. */ cw_sound_internal (TONE_SILENT); /* Notify the keying control function, as above. */ cw_key_control_internal (FALSE); /* * Set the flag that indicates that autonomous dequeueing * has finished for the moment. We need this set whenever * the queue indexes are equal and there is no pending * itimeout. */ cw_dequeue_state = QS_IDLE; cw_release_timeouts_internal (); cw_sound_release_internal (); } } /** * cw_queue_tone_internal() * * Enqueue a tone for specified frequency and number of microseconds. This * routine adds the new tone to the queue, and if necessary starts the * itimer process to have the tone sent. The routine returns 0 on success. * If the tone queue is full, the routine returns -1, with errno set to * EAGAIN. If the iambic keyer or straight key are currently busy, the * routine returns -1, with errno set to EBUSY. */ static int cw_queue_tone_internal (int usec, int frequency) { int new_tq_head; /* New value of head index */ /* * If the keyer or straight key are busy, return an error. This is * because they use the sound card/console tones and key control, * and will interfere with us if we try to use them at the same time. */ if (cw_keyer_busy () || cw_straightkey_busy ()) { errno = EBUSY; return RC_ERROR; } /* Calculate the new value of the queue head index. */ if (cw_tq_head + 1 < TONE_QUEUE_CAPACITY) new_tq_head = cw_tq_head + 1; else new_tq_head = 0; /* * If the new value is bumping against the tail index, then the * queue is currently full, so return EAGAIN. */ if (new_tq_head == cw_tq_tail) { errno = EAGAIN; return RC_ERROR; } /* Print out any requested debug on the tone being queued. */ if (cw_get_debug_flags () & CW_DEBUG_TONE_QUEUE) fprintf (stderr, "cw: enqueue tone %d usec, %d Hz\n", usec, frequency); /* Enqueue the new tone. */ cw_tone_queue[new_tq_head].usec = usec; cw_tone_queue[new_tq_head].frequency = frequency; /* * If there is currently no autonomous dequeue happening, set the * new head index and kick off the itimer process. Otherwise, * just set the new head index. */ if (cw_dequeue_state == QS_IDLE) { cw_tq_head = new_tq_head; cw_dequeue_state = QS_BUSY; cw_request_timeout_internal (0, cw_dequeue_tone_internal); } else cw_tq_head = new_tq_head; return RC_SUCCESS; } /** * cw_tone_queue_low_callback () * * Registers a function to be called automatically by the dequeue routine * whenever the tone queue falls to a given level. A NULL function pointer * suppresses callbacks. On success, the routine returns 0. If level * is invalid, the routine returns -1 with errno set to EINVAL. */ int cw_tone_queue_low_callback (void (*callback_func) (void), int level) { /* Check level for valid values. */ if (level < 0 || level >= TONE_QUEUE_CAPACITY - 1) { errno = EINVAL; return RC_ERROR; } /* Store the function and low water mark level. */ cw_tq_low_water_mark = level; cw_tq_low_water_callback = callback_func; return RC_SUCCESS; } /** * cw_block_callback() * * Blocks the callback from being called for a critical section of caller * code if block is TRUE, and unblocks the callback if block is FALSE. * Works by blocking SIGALRM; a block should always be matched by an * unblock, otherwise the tone queue will suspend forever. */ void cw_block_callback (int block) { cw_block_signal_internal (block); } /** * cw_tone_busy() * * Indicates if the tone sender is busy; returns TRUE if there are still * entries in the tone queue, FALSE if the queue is empty. */ int cw_tone_busy () { return (cw_dequeue_state != QS_IDLE); } /** * cw_tone_wait() * * Wait for the current tone to complete. The routine returns 0 on success. * If called with SIGALRM blocked, the routine returns -1, with errno set to * EDEADLK, to avoid indefinite waits. */ int cw_tone_wait () { int error; /* Error status */ int check_tq_tail; /* Comparison of tail index */ /* Check that SIGALRM is not blocked. */ error = cw_check_signal_mask_internal (); if (error) return error; /* Wait for the tail index to change or the dequeue to go idle. */ check_tq_tail = cw_tq_tail; while (cw_tq_tail == check_tq_tail && cw_dequeue_state != QS_IDLE) cw_signal_wait_internal (); return RC_SUCCESS; } /** * cw_tone_queue_wait() * * Wait for the tone queue to drain. The routine returns 0 on success. * If called with SIGALRM blocked, the routine returns -1, with errno set to * EDEADLK, to avoid indefinite waits. */ int cw_tone_queue_wait () { int error; /* Error status */ /* Check that SIGALRM is not blocked. */ error = cw_check_signal_mask_internal (); if (error) return error; /* Wait until the dequeue indicates it's hit the end of the queue. */ while (cw_dequeue_state != QS_IDLE) cw_signal_wait_internal (); return RC_SUCCESS; } /** * cw_tone_queue_wait_critical() * * Wait for the tone queue to drain until only as many tones as given * in level remain queued. This routine is for use by programs that want * to optimize themselves to avoid the cleanup that happens when the tone * queue drains completely; such programs have a short time in which to * add more tones to the queue. The routine returns 0 on success. If * called with SIGALRM blocked, the routine returns -1, with errno set to * EDEADLK, to avoid indefinite waits. */ int cw_tone_queue_wait_critical (int level) { int error; /* Error status */ int queue_length; /* Queued tones length */ /* Check that SIGALRM is not blocked. */ error = cw_check_signal_mask_internal (); if (error) return error; /* Calculate the current queue length. */ if (cw_tq_head >= cw_tq_tail) queue_length = cw_tq_head - cw_tq_tail; else queue_length = cw_tq_head - cw_tq_tail + TONE_QUEUE_CAPACITY; /* Wait until the queue length is at or below criticality. */ while (queue_length > level) { cw_signal_wait_internal (); /* Calculate the current queue length again. */ if (cw_tq_head >= cw_tq_tail) queue_length = cw_tq_head - cw_tq_tail; else queue_length = cw_tq_head - cw_tq_tail + TONE_QUEUE_CAPACITY; } return RC_SUCCESS; } /** * cw_tone_queue_full() * * Indicates if the tone queue is full, returning TRUE if full, FALSE if not. */ int cw_tone_queue_full () { int check_tq_head; /* Test value of head index */ /* See what would happen if we advance the head index. */ if (cw_tq_head + 1 < TONE_QUEUE_CAPACITY) check_tq_head = cw_tq_head + 1; else check_tq_head = 0; /* If it would meed the tail index, return TRUE. */ return (check_tq_head == cw_tq_tail); } /** * cw_get_tone_queue_capacity() * * Returns the number of entries the tone queue can accommodate. */ int cw_get_tone_queue_capacity () { /* * Since the head and tail indexes cannot be equal, the perceived * capacity for the client is always one less than the actual * declared queue size. */ return TONE_QUEUE_CAPACITY - 1; } /** * cw_get_tone_queue_length() * * Returns the number of entries currently pending in the tone queue. */ int cw_get_tone_queue_length () { if (cw_tq_head >= cw_tq_tail) return (cw_tq_head - cw_tq_tail); else return (cw_tq_head - cw_tq_tail + TONE_QUEUE_CAPACITY); } /** * cw_flush_tone_queue() * * Cancel all pending queued tones, and return to silence. If there is a * tone in progress, the function will wait until this last one has * completed, then silence the tones. * * This function may be called with SIGALRM blocked, in which case it * will empty the queue as best it can, then return without waiting for * the final tone to complete. In this case, it may not be possible to * guarantee silence after the call. */ void cw_flush_tone_queue () { /* Empty the queue, by setting the tail to the head. */ cw_tq_tail = cw_tq_head; /* If we can, wait until the dequeue goes idle. */ if (cw_check_signal_mask_internal () == RC_SUCCESS) cw_tone_queue_wait (); /* Force silence on the speaker anyway. */ cw_sound_internal (TONE_SILENT); /* Stop any background soundcard tone generation. */ cw_sound_release_internal (); } /** * cw_queue_tone() * * Provides primitive access to simple tone generation. This routine queues * a tone of given duration and frequency. The routine returns 0 on success. * If usec or frequency are invalid, it returns -1, with errno set to EINVAL. * If the sound card, console speaker, or keying function are busy, it * returns -1 with errno set to EBUSY. If the tone queue is full, it returns * -1 with errno set to EAGAIN. */ int cw_queue_tone (int usec, int frequency) { /* * Check the arguments given for realistic values. Note that * we do nothing here to protect the caller from setting up * neverending (0 usec) tones, if that's what they want to do. */ if (usec < 0 || frequency < 0 || frequency < CW_MIN_FREQUENCY || frequency > CW_MAX_FREQUENCY) { errno = EINVAL; return RC_ERROR; } /* Simplistically send the tone requested. */ return cw_queue_tone_internal (usec, frequency); } /** * cw_send_element_internal() * * Low level primitive to send a tone element of the given type, followed * by the standard inter-element silence. */ static int cw_send_element_internal (char element) { int error; /* Error status */ /* Synchronize low-level timings if required. */ cw_sync_parameters_internal (); /* Send either a dot or a dash element, depending on representation. */ if (element == CW_DOT_REPRESENTATION) { error = cw_queue_tone_internal (cw_send_dot_length, cw_frequency); if (error) return error; } else { if (element == CW_DASH_REPRESENTATION) { error = cw_queue_tone_internal (cw_send_dash_length, cw_frequency); if (error) return error; } } /* Send the inter-element gap. */ error = cw_queue_tone_internal (cw_end_of_ele_delay, TONE_SILENT); if (error) return error; return RC_SUCCESS; } /** * cw_send_[dot|dash|character_space|word_space]() * * Low level primitives, available to send single dots, dashes, character * spaces, and word spaces. The dot and dash routines always append the * normal inter-element gap after the tone sent. The cw_send_character_space * routine sends space timed to exclude the expected prior dot/dash * inter-element gap. The cw_send_word_space routine sends space timed to * exclude both the expected prior dot/dash inter-element gap and the prior * end of character space. These functions return 0 on success, or -1, with * errno set to EBUSY or EAGAIN on error. */ int cw_send_dot () { return cw_send_element_internal (CW_DOT_REPRESENTATION); } int cw_send_dash () { return cw_send_element_internal (CW_DASH_REPRESENTATION); } int cw_send_character_space () { /* Synchronize low-level timing parameters. */ cw_sync_parameters_internal (); /* * Delay for the standard end of character period, plus any * additional inter-character gap */ return cw_queue_tone_internal (cw_end_of_char_delay + cw_additional_delay, TONE_SILENT); } int cw_send_word_space () { /* Synchronize low-level timing parameters. */ cw_sync_parameters_internal (); /* * Send silence for the word delay period, plus any adjustment * that may be needed at end of word. */ return cw_queue_tone_internal (cw_end_of_word_delay + cw_adjustment_delay, TONE_SILENT); } /** * cw_get_character_count() * * Returns the number of characters represented in the character lookup * table. */ int cw_get_character_count () { const cw_entry_t *cwptr; /* Pointer to table entry */ int count; /* Return value */ /* Traverse the main lookup table, counting entries. */ for (count = 0, cwptr = cw_table; cwptr->character != ASC_NUL; cwptr++) count++; /* Return the character count. */ return count; } /** * cw_list_characters() * * Returns into list a string containing all of the Morse characters * represented in our table. The length of list must be at least one * greater than the number of characters represented in the character * lookup table, returned by cw_get_character_count. */ void cw_list_characters (unsigned char *list) { const cw_entry_t *cwptr; /* Pointer to table entry */ unsigned char *sptr; /* Output string pointer */ /* * Traverse the main lookup table, appending each character found * to the output string. */ for (sptr = list, cwptr = cw_table; cwptr->character != ASC_NUL; cwptr++) { *sptr = cwptr->character; sptr++; } /* Terminate the output string. */ *sptr = ASC_NUL; } /** * cw_get_maximum_representation_length() * * Returns the string length of the longest representation in the character * lookup table. */ int cw_get_maximum_representation_length () { const cw_entry_t *cwptr; /* Pointer to table entry */ int maximum; /* Return value */ /* Traverse the main lookup table, finding the longest. */ for (cwptr = cw_table, maximum = 0; cwptr->character != ASC_NUL; cwptr++) { if ((int)strlen (cwptr->representation) > maximum) maximum = strlen (cwptr->representation); } /* Return the character count. */ return maximum; } /** * cw_lookup_character_internal() * * Look up the given character, and return a pointer to the table entry * for the representation of that character. Returns NULL if there is * no table entry for the given character. */ static const cw_entry_t* cw_lookup_character_internal (unsigned char c) { static const cw_entry_t *lookup[ UCHAR_MAX ]; static int initialized = FALSE; /* Fast lookup table initialized on first function call */ const cw_entry_t *cwptr; /* Pointer to table entry */ /* If not yet initialized, then set up the faster lookup table */ if (!initialized) { /* Print any debug message on table initialization. */ if (cw_get_debug_flags () & CW_DEBUG_LOOKUPS) fprintf (stderr, "cw: initialize fast lookup table\n"); /* For each main table entry, create a fast table entry. */ for (cwptr = cw_table; cwptr->character != ASC_NUL; cwptr++) lookup[(unsigned int)cwptr->character] = cwptr; /* Set the initialized flag now that we built the table. */ initialized = TRUE; } /* * There is no differentiation in the table between upper and lower * case characters; everything is held as uppercase. So before we * do the lookup, we convert to ensure that both cases work. */ c = toupper (c); /* * Now use the table to lookup the table entry. Unknown characters * return NULL, courtesy of the fact that explicitly uninitialized * static variables are initialized to zero, so lookup[x] is NULL if * it's not assigned to in the above loop. */ cwptr = lookup[(unsigned int)c]; /* Print out debug message indicating the lookup. */ if (cw_get_debug_flags () & CW_DEBUG_LOOKUPS) { if (cwptr != NULL) fprintf (stderr, "cw: lookup '%c' returned <'%c':\"%s\">\n", c, cwptr->character, cwptr->representation); else if (isprint (c)) fprintf (stderr, "cw: lookup '%c' found nothing\n", c); else fprintf (stderr, "cw: lookup 0x%02x found nothing\n", c); } return cwptr; } /** * cw_lookup_character() * * Returns the string 'shape' of a given Morse code character. The routine * returns 0 on success, and fills in the string pointer passed in. On error, * it returns -1, and sets errno to ENOENT, indicating that the character * could not be found. The length of representation must be at least one * greater than the longest representation held in the character lookup table, * returned by cw_get_maximum_representation_length. */ int cw_lookup_character (unsigned char c, char *representation) { const cw_entry_t *cwptr; /* Pointer to table entry */ /* Lookup the character, and if found, return the string. */ cwptr = cw_lookup_character_internal (c); if (cwptr != NULL) { if (representation != NULL) strcpy (representation, cwptr->representation); return RC_SUCCESS; } /* Failed to find the requested character. */ errno = ENOENT; return RC_ERROR; } /** * cw_tokenize_representation_internal() * * Return a token value, in the range 2-255, for a lookup table representation. * The routine returns 0 if no valid token could be made from the string. To * avoid casting the value a lot in the caller (we want to use it as an array * index), we actually return an unsigned int. * * This token algorithm is designed ONLY for valid CW representations; that is, * strings composed of only '.' and '-', and in this case, strings shorter than * eight characters. The algorithm simply turns the representation into a * 'bitmask', based on occurrences of '.' and '-'. The first bit set in the * mask indicates the start of data (hence the 7-character limit). This mask * is viewable as an integer in the range 2 (".") to 255 ("-------"), and can * be used as an index into a fast lookup array. */ static unsigned int cw_tokenize_representation_internal (const char *representation) { unsigned int token; /* Return token value */ const char *sptr; /* Pointer through string */ /* * Our algorithm can handle only 7 characters of representation. * And we insist on there being at least one character, too. */ if (strlen (representation) > CHAR_BIT - 1 || strlen (representation) < 1) return 0; /* * Build up the token value based on the dots and dashes. Start the * token at 1 - the sentinel (start) bit. */ for (sptr = representation, token = 1; *sptr != ASC_NUL; sptr++) { /* * Belt-and-braces check that we don't lose the most * significant bit, and exceed 255 as a return token. */ if (token & (1 << (CHAR_BIT - 1))) return 0; /* Left-shift the sentinel (start) bit. */ token <<= 1; /* * If the next element is a dash, OR in another bit. If it is * not a dash or a dot, then there is an error in the repres- * entation string. */ if (*sptr == CW_DASH_REPRESENTATION) token |= 1; else if (*sptr != CW_DOT_REPRESENTATION) return 0; } /* Return the value resulting from our tokenization of the string. */ return token; } /** * cw_lookup_representation_internal() * * Look up the given representation, and return a pointer to the table entry * for the representation of that character. Returns NULL if there is * no table entry for the given character. */ static const cw_entry_t* cw_lookup_representation_internal (const char *representation) { static const cw_entry_t *lookup[ UCHAR_MAX ]; static int initialized = FALSE; /* Fast lookup table initialized on first function call */ static int complete = TRUE;/* Set to FALSE if there are any lookup table entries not in the fast lookup table */ const cw_entry_t *cwptr; /* Pointer to table entry */ unsigned int token; /* Tokenization code return */ /* If not yet initialized, then set up the tokenized lookup table. */ if (!initialized) { /* Print any debug message on table initialization. */ if (cw_get_debug_flags () & CW_DEBUG_LOOKUPS) fprintf (stderr, "cw: initialize token lookup table\n"); /* * For each main table entry, create a token entry. If the * tokenization of any entry fails, note that the table is not * complete and ignore that entry for now (for the current * lookup table, this should not happen). The tokenized table * speeds up lookups of representations by a factor of 5-10. */ for (cwptr = cw_table; cwptr->character != ASC_NUL; cwptr++) { token = cw_tokenize_representation_internal (cwptr->representation); if (token != 0) lookup[token] = cwptr; else complete = FALSE; } /* Print a debug warning if the table is not complete. */ if (cw_get_debug_flags () & CW_DEBUG_LOOKUPS && !complete) fprintf (stderr, "cw: token lookup table incomplete\n"); /* Set the initialized flag now that we built the table. */ initialized = TRUE; } /* Tokenize the representation to get an index for the fast lookup. */ token = cw_tokenize_representation_internal (representation); /* * If the tokenized lookup table is complete, we can simply believe * any token value that came back. That is, we just use what is at * the index 'token', since this is either the entry we want, or NULL. */ if (complete) cwptr = lookup[token]; else { /* * If the tokenized lookup table is not complete, the lookup * might still have found us the entry we are looking for. * Here, we'll check to see if it did. */ if (token != 0 && lookup[token]->representation != NULL && strcmp (lookup[token]->representation, representation) == 0) /* Found it in an incomplete table. */ cwptr = lookup[token]; else { /* * We have no choice but to search the table entry * by entry, sequentially, from top to bottom. */ for (cwptr = cw_table; cwptr->character != ASC_NUL; cwptr++) { if (strcmp (cwptr->representation, representation) == 0) break; } /* If we got to the end of the table, return NULL. */ if (cwptr->character == ASC_NUL) cwptr = NULL; } } /* Print out debug message indicating the lookup. */ if (cw_get_debug_flags () & CW_DEBUG_LOOKUPS) { if (cwptr != NULL) fprintf (stderr, "cw: lookup [0x%02x]'%s' returned <'%c':\"%s\">\n", token, representation, cwptr->character, cwptr->representation); else fprintf (stderr, "cw: lookup [0x%02x]'%s' found nothing\n", token, representation); } /* Finally, return anything we managed to get out of this. */ return cwptr; } /** * cw_check_representation() * * Checks that the given string is a valid Morse representation. A valid * string is one composed of only '.' and '-' characters. On success, the * routine returns 0. On error, it returns -1, with errno set to EINVAL. */ int cw_check_representation (const char *representation) { const char *sptr; /* Cw string pointer */ /* Check the characters in representation. */ for (sptr = representation; *sptr != ASC_NUL; sptr++) { if (*sptr != CW_DOT_REPRESENTATION && *sptr != CW_DASH_REPRESENTATION) { errno = EINVAL; return RC_ERROR; } } return RC_SUCCESS; } /** * cw_lookup_representation() * * Returns the character for a given Morse representation. On success, the * routine returns 0, and fills in unsigned char *c. On error, it returns * -1, and sets errno to EINVAL if any character of the representation is * invalid, or ENOENT to indicate that the representation could not be found. */ int cw_lookup_representation (const char *representation, unsigned char *c) { const cw_entry_t *cwptr; /* Pointer to table entry */ /* Check the characters in representation. */ if (cw_check_representation (representation) == RC_ERROR) { errno = EINVAL; return RC_ERROR; } /* Lookup the representation, and if found, return the character. */ cwptr = cw_lookup_representation_internal (representation); if (cwptr != NULL) { if (c != NULL) *c = cwptr->character; return RC_SUCCESS; } /* Failed to find the requested representation. */ errno = ENOENT; return RC_ERROR; } /** * cw_send_representation_internal() * * Send the given string as dots and dashes, adding the post-character * gap. */ static int cw_send_representation_internal (const char *representation, int partial) { int error; /* Error status */ const char *sptr; /* Cw string pointer */ /* * Before we let this representation loose on tone generation, we'd * really like to know that all of its tones will get queued up * successfully. The right way to do this is to calculate the * number of tones in our representation, then check that the * space exists in the tone queue. However, since the queue is * comfortably long, we can get away with just looking for a high * water mark. */ if (cw_get_tone_queue_length () >= TONE_QUEUE_HIGH_WATER_MARK) { errno = EAGAIN; return RC_ERROR; } /* Sound the elements of the cw equivalent. */ for (sptr = representation; *sptr != ASC_NUL; sptr++) { /* * Send a tone of dot or dash length, followed by the normal, * standard, inter-element gap. */ error = cw_send_element_internal (*sptr); if (error) return error; } /* * If this representation is stated as being 'partial', then suppress * any and all end of character delays. */ if (!partial) { error = cw_send_character_space (); if (error) return error; } return RC_SUCCESS; } /** * cw_send_representation() * * Checks, then sends the given string as dots and dashes. The representation * passed in is assumed to be a complete Morse character; that is, all post- * character delays will be added when the character is sent. On success, * the routine returns 0. On error, it returns -1, with errno set to EINVAL * if any character of the representation is invalid, EBUSY if the sound * card, console speaker, or keying system is busy, or EAGAIN if the tone * queue is full, or if there is insufficient space to queue the tones for * the representation. */ int cw_send_representation (const char *representation) { /* Check the characters in representation. */ if (cw_check_representation (representation) == RC_ERROR) { errno = EINVAL; return RC_ERROR; } /* Sound out the representation. */ return cw_send_representation_internal (representation, FALSE); } /** * cw_send_representation_partial() * * Check, then send the given string as dots and dashes. The representation * passed in is assumed to be only part of a larger Morse representation; * that is, no post-character delays will be added when the character is sent. * On success, the routine returns 0. On error, it returns -1, with errno * set to EINVAL if any character of the representation is invalid, EBUSY * if the sound card, console speaker, or keying system is busy, or EAGAIN * if the tone queue is full, or if there is insufficient space to queue * the tones for the representation. */ int cw_send_representation_partial (const char *representation) { /* Check the characters in representation. */ if (cw_check_representation (representation) == RC_ERROR) { errno = ENOENT; return RC_ERROR; } /* Sound out the representation. */ return cw_send_representation_internal (representation, TRUE); } /** * cw_send_character_internal() * * Lookup, and send a given ASCII character as cw. If 'partial' is set, the * end of character delay is not appended to the Morse sent. On success, * the routine returns 0, otherwise it returns an error. */ static int cw_send_character_internal (unsigned char c, int partial) { int error; /* Error status */ const cw_entry_t *cwptr; /* Pointer to table entry */ /* Handle space special case; delay end-of-word and return. */ if (c == ASC_SPACE) { error = cw_send_word_space (); if (error) return error; } else { /* Lookup the character, and sound it. */ cwptr = cw_lookup_character_internal (c); if (cwptr == NULL) { errno = ENOENT; return RC_ERROR; } error = cw_send_representation_internal (cwptr->representation, partial); if (error) return error; } return RC_SUCCESS; } /** * cw_check_character() * * Checks that the given character is validly sendable in Morse. If it is, * the routine returns 0. If not, the routine returns -1, with errno set * to ENOENT. */ int cw_check_character (unsigned char c) { /* * If the character is the space special-case, or if not, but it * is in the lookup table, return success. */ if (c == ASC_SPACE || cw_lookup_character_internal (c) != NULL) return RC_SUCCESS; /* Character is not sendable. */ errno = ENOENT; return RC_ERROR; } /** * cw_send_character() * * Lookup, and send a given ASCII character as Morse. The end of character * delay is appended to the Morse sent. On success, the routine returns 0. * On error, it returns -1, with errno set to ENOENT if the given character * is not a valid Morse character, EBUSY if the sound card, console speaker, * or keying system is busy, or EAGAIN if the tone queue is full, or if * there is insufficient space to queue the tones for the representation. * * This routine returns as soon as the character has been successfully * queued for send; that is, almost immediately. The actual sending takes * place in background processing. See cw_tone_wait and cw_tone_queue_wait * for ways to check the progress of sending. */ int cw_send_character (unsigned char c) { /* Check that the character is sendable. */ if (cw_check_character (c) == RC_ERROR) { errno = ENOENT; return RC_ERROR; } return cw_send_character_internal (c, FALSE); } /** * cw_send_character_partial() * * Lookup, and send a given ASCII character as Morse. The end of character * delay is not appended to the Morse sent by the function, to support the * formation of combination characters. On success, the routine returns 0. * On error, it returns -1, with errno set to ENOENT if the given character * is not a valid Morse character, EBUSY if the sound card, console speaker, * or keying system is busy, or EAGAIN if the tone queue is full, or if * there is insufficient space to queue the tones for the representation. * * This routine queues its arguments for background processing. See * cw_send_character for details of how to check the queue status. */ int cw_send_character_partial (unsigned char c) { /* Check that the character is sendable. */ if (cw_check_character (c) == RC_ERROR) { errno = ENOENT; return RC_ERROR; } return cw_send_character_internal (c, TRUE); } /** * cw_check_string() * * Checks that each character in the given string is validly sendable in Morse. * On success, the routine returns 0. On error, it returns -1, with errno set * to EINVAL. */ int cw_check_string (const unsigned char *string) { const unsigned char *sptr; /* String pointer */ /* * Check that each character in the string has a Morse representation, * or is the space special case. */ for (sptr = string; *sptr != ASC_NUL; sptr++) { if (*sptr != ASC_SPACE && cw_lookup_character_internal (*sptr) == NULL) { errno = EINVAL; return RC_ERROR; } } /* Each character of the string is sendable. */ return RC_SUCCESS; } /** * cw_send_string() * * Send a given ASCII string as cw. On success, the routine returns 0. * On error, it returns -1, with errno set to ENOENT if any character in the * string is not a valid Morse character, EBUSY if the sound card, console * speaker, or keying system is in use by the iambic keyer or the straight * key, or EAGAIN if the tone queue is full. If the tone queue runs out * of space part way through queueing the string, the function returns EAGAIN. * However, an indeterminate number of the characters from the string will * have already been queued. For safety, clients can ensure the tone queue * is empty before queueing a string, or use cw_send_character() if they * need finer control. * * This routine queues its arguments for background processing. See * cw_send_character for details of how to check the queue status. */ int cw_send_string (const unsigned char *string) { int error; /* Error status */ const cw_entry_t *cwptr; /* Pointer to table entry */ const unsigned char *sptr; /* String pointer */ /* * Initially, check that each character in the string has a Morse * representation, or is the space special case. */ if (cw_check_string (string) == RC_ERROR) { errno = ENOENT; return RC_ERROR; } /* Send every character in the string. */ for (sptr = string; *sptr != ASC_NUL; sptr++) { /* Handle space special case; delay end-of-word and return. */ if (*sptr == ASC_SPACE) { error = cw_send_word_space (); if (error) return error; } else { /* Lookup the character, and sound it. */ cwptr = cw_lookup_character_internal (*sptr); if (cwptr == NULL) { errno = ENOENT; return RC_ERROR; } error = cw_send_representation_internal (cwptr->representation, FALSE); if (error) return error; } } return RC_SUCCESS; } /* * The CW receive functions implement the following state graph: * * +----------------- RS_ERR_WORD <-------------------+ * |(clear) ^ | * | (delay=long)| | * | | | * +----------------- RS_ERR_CHAR <---------+ | * |(clear) ^ | | | * | | +-------------+ |(error, * | | (delay=short) | delay=long) * | (error,delay=short)| | * | | +-----------------------+ * | | | * +--------------------+ | | * | (noise)| | | * | | | | * v (start tone) | | | (end tone,noise) * --> RS_IDLE ------------> RS_IN_TONE ------------> RS_AFTER_TONE <------- + * | ^ ^ | | | ^ | | * | | (delay=short) +---------------+ | | | +-----------+ * | | +--------------+ (start tone) | | | (not ready, * | | | | | | | buffer dot, * | | +-------> RS_END_CHAR <--------------+ | | buffer dash) * | | | | (delay=short) | | * | +-------------------+ | | | * | |(clear) | | | * | | (delay=long)| | | * | | v | | * | +----------------- RS_END_WORD <-------------------+ | * | (clear) (delay=long) |(buffer dot, * | | buffer dash) * +-------------------------------------------------------+ */ static enum {RS_IDLE,RS_IN_TONE,RS_AFTER_TONE, RS_END_CHAR,RS_END_WORD, RS_ERR_CHAR,RS_ERR_WORD} cw_receive_state = RS_IDLE; /* Indicates receive state */ /** * cw_start_receive_tone() * * Called on the start of a receive tone. If the timestamp is NULL, the * current time is used. On success, the routine returns 0. On error, * it returns -1, with errno set to ERANGE if the call is directly after * another cw_start_receive_tone call or if an existing received character * has not been cleared from the buffer, or EINVAL if the timestamp passed * in is invalid. */ int cw_start_receive_tone (const struct timeval *timestamp) { /* * If the receive state is not idle or after a tone, this is a * state error. A receive tone start can only happen while we * are idle, or in the middle of a character. */ if (cw_receive_state != RS_IDLE && cw_receive_state != RS_AFTER_TONE) { errno = ERANGE; return RC_ERROR; } /* Validate and save the timestamp, or get one and then save it. */ if (timestamp != NULL) { if (timestamp->tv_sec < 0 || timestamp->tv_usec < 0 || timestamp->tv_usec >= USECS_PER_SEC) { errno = EINVAL; return RC_ERROR; } cw_rr_start_timestamp.tv_sec = timestamp->tv_sec; cw_rr_start_timestamp.tv_usec = timestamp->tv_usec; } else { if (gettimeofday (&cw_rr_start_timestamp, NULL) != 0) { perror ("cw: gettimeofday"); return RC_ERROR; } } /* Set state to indicate we are inside a tone. */ cw_receive_state = RS_IN_TONE; /* Print out any state change debug. */ if (cw_get_debug_flags () & CW_DEBUG_RECEIVE_STATES) fprintf (stderr, "cw: receive state ->%d\n", cw_receive_state); return RC_SUCCESS; } /** * cw_compare_timestamps_internal() * * Compare two timestamps, and return the difference between them in usecs, * taking care to clamp values which would overflow an int. This routine * always returns a +ve integer in the range 0 to INT_MAX. */ static int cw_compare_timestamps_internal (const struct timeval *earlier, const struct timeval *later) { int delta_usec; /* Time difference in usecs */ /* * Compare the timestamps, taking care on overflows. * * At 4 WPM, the dash length is 3*(1200000/4)=900,000 usecs, and * the word gap is 2,100,000 usecs. With the maximum Farnsworth * additional delay, the word gap extends to 8,100,000 usecs. * This fits into an int with a lot of room to spare, in fact, * an int can represent ~2000,000,000 usecs, or around 33 minutes. * This is way, way longer than we'd ever want to differentiate, * so if by some chance we see timestamps farther apart than this, * and it ought to be very, very unlikely, then we'll clamp the * return value to INT_MAX with a clear conscience. * * Note: passing nonsensical or bogus timevals in may result * in unpredictable results. Nonsensical includes timevals with * -ve tv_usec, -ve tv_sec, tv_usec >= 1,000,000, etc. To help * in this, we check all incoming timestamps for 'well-formedness'. * However, we assume the gettimeofday() call always returns good * timevals. All in all, timeval could probably be a better * thought-out structure. */ /* Calculate an initial delta, possibly with overflow. */ delta_usec = (later->tv_sec - earlier->tv_sec) * USECS_PER_SEC + later->tv_usec - earlier->tv_usec; /* Check specifically for overflow, and clamp if it did. */ if ((later->tv_sec - earlier->tv_sec) > (INT_MAX / USECS_PER_SEC) + 1 || delta_usec < 0) delta_usec = INT_MAX; return delta_usec; } /** * cw_identify_receive_tone_internal() * * Analyses a tone using the ranges provided by the low level timing * parameters. On success, it returns 0 and sends back either a dot or * a dash in representation. On error, it returns -1 with ERRNO set to * ENOENT if the tone is not recognizable as either a dot or a dash, * and sets the receive state to one of the error states, depending on * the tone length passed in. * * Note; for adaptive timing, the tone should _always_ be recognized as * a dot or a dash, because the ranges will have been set to cover 0 to * INT_MAX. */ static int cw_identify_receive_tone_internal (int element_usec, char *representation) { /* Synchronize low level timings if required */ cw_sync_parameters_internal (); /* * If the timing was, within tolerance, a dot, return a dot to the * caller. */ if (element_usec >= cw_dot_range_minimum && element_usec <= cw_dot_range_maximum) { *representation = CW_DOT_REPRESENTATION; return RC_SUCCESS; } /* Do the same for a dash. */ if (element_usec >= cw_dash_range_minimum && element_usec <= cw_dash_range_maximum) { *representation = CW_DASH_REPRESENTATION; return RC_SUCCESS; } /* * This element is not a dot or a dash, so we have an error case. * Depending on the timestamp difference, we pick which of the * error states to move to, and move to it. The comparison is * against the expected end-of-char delay. If it's larger, then * fix at word error, otherwise settle on char error. * * Note that we should never reach here for adaptive timing receive. */ if (element_usec > cw_eoc_range_maximum) cw_receive_state = RS_ERR_WORD; else cw_receive_state = RS_ERR_CHAR; /* Print out any state change debug. */ if (cw_get_debug_flags () & CW_DEBUG_RECEIVE_STATES) fprintf (stderr, "cw: receive state ->%d\n", cw_receive_state); /* Return ENOENT to the caller. */ errno = ENOENT; return RC_ERROR; } /** * cw_update_adaptive_tracking_internal() * * Updates the averages of dot and dash lengths, and recalculates the * adaptive threshold for the next receive tone. */ static void cw_update_adaptive_tracking_internal (int element_usec, char element) { int average_dot; /* Averaged dot length */ int average_dash; /* Averaged dash length */ /* We are not going to tolerate being called in fixed speed mode. */ if (!cw_adaptive_receive_enabled) return; /* * We will update the information held for either dots or dashes. * Which we pick depends only on what the representation of the * character was identified as earlier. */ if (element == CW_DOT_REPRESENTATION) { /* Update the dot data held for averaging. */ cw_dt_dot_tracking_sum -= cw_dot_tracking_array[cw_dt_dot_index]; cw_dot_tracking_array[cw_dt_dot_index++] = element_usec; cw_dt_dot_index %= AVERAGE_ARRAY_LENGTH; cw_dt_dot_tracking_sum += element_usec; } else { if (element == CW_DASH_REPRESENTATION) { /* Update the dash data held for averaging. */ cw_dt_dash_tracking_sum -= cw_dash_tracking_array[cw_dt_dash_index]; cw_dash_tracking_array[cw_dt_dash_index++] = element_usec; cw_dt_dash_index %= AVERAGE_ARRAY_LENGTH; cw_dt_dash_tracking_sum += element_usec; } } /* * Recalculate the adaptive threshold from the values currently * held in the averaging arrays. The threshold is calculated as * (avg dash length - avg dot length) / 2 + avg dot_length. */ average_dot = cw_dt_dot_tracking_sum / AVERAGE_ARRAY_LENGTH; average_dash = cw_dt_dash_tracking_sum / AVERAGE_ARRAY_LENGTH; cw_adaptive_receive_threshold = ( average_dash - average_dot ) / 2 + average_dot; /* * Resynchronize the low level timing data following recalculation. * If the resultant recalculated speed is outside the limits, * clamp the speed to the limit value and recalculate again. * * Resetting the speed directly really means unsetting adaptive * mode, resyncing to calculate the new threshold, which unfort- * unately recalculates everything else according to fixed speed; * so, we then have to reset adaptive and resyncing one more time, * to get all other timing parameters back to where they should be. */ cw_in_sync = FALSE; cw_sync_parameters_internal (); if (cw_receive_speed < CW_MIN_SPEED) { cw_receive_speed = CW_MIN_SPEED; cw_adaptive_receive_enabled = FALSE; cw_in_sync = FALSE; cw_sync_parameters_internal (); cw_adaptive_receive_enabled = TRUE; cw_in_sync = FALSE; cw_sync_parameters_internal (); } else { if (cw_receive_speed > CW_MAX_SPEED) { cw_receive_speed = CW_MAX_SPEED; cw_adaptive_receive_enabled = FALSE; cw_in_sync = FALSE; cw_sync_parameters_internal (); cw_adaptive_receive_enabled = TRUE; cw_in_sync = FALSE; cw_sync_parameters_internal (); } } } /** * cw_end_receive_tone() * * Called on the end of a receive tone. If the timestamp is NULL, the * current time is used. On success, the routine adds a dot or dash to * the receive representation buffer, and returns 0. On error, it * returns -1, with errno set to ERANGE if the call was not preceded by * a cw_start_receive_tone call, EINVAL if the timestamp passed in is not * valid, ENOENT if the tone length was out of bounds for the permissible * dot and dash lengths and fixed speed receiving is selected, ENOMEM if * the representation buffer is full, or EAGAIN if the tone was shorter * than the threshold for noise and was therefore ignored. */ int cw_end_receive_tone (const struct timeval *timestamp) { int error; /* Error status */ int element_usec; /* Time difference in usecs */ char representation; /* Tone dot or dash character */ struct timeval saved_end_timestamp; /* Safe copy of end timestamp */ /* The receive state is expected to be inside a tone. */ if (cw_receive_state != RS_IN_TONE) { errno = ERANGE; return RC_ERROR; } /* * Take a safe copy of the current end timestamp, in case we need * to put it back if we decide this tone is really just noise. */ saved_end_timestamp.tv_sec = cw_rr_end_timestamp.tv_sec; saved_end_timestamp.tv_usec = cw_rr_end_timestamp.tv_usec; /* Save the timestamp passed in, or get one. */ if (timestamp != NULL) { if (timestamp->tv_sec < 0 || timestamp->tv_usec < 0 || timestamp->tv_usec >= USECS_PER_SEC) { errno = EINVAL; return RC_ERROR; } cw_rr_end_timestamp.tv_sec = timestamp->tv_sec; cw_rr_end_timestamp.tv_usec = timestamp->tv_usec; } else { if (gettimeofday (&cw_rr_end_timestamp, NULL) != 0) { perror ("cw: gettimeofday"); return RC_ERROR; } } /* * Now we need to compare the timestamps to determine the length * of the tone. */ element_usec = cw_compare_timestamps_internal (&cw_rr_start_timestamp, &cw_rr_end_timestamp); /* * If the tone length is shorter than any noise cancelling threshold * that has been set, then ignore this tone. This means reverting * to the state before the call to cw_start_receive_tone. Now, by * rights, we should use an extra state, RS_IN_FIRST_TONE, say, so * that we know whether to go back to the idle state, or to after * tone. But to make things a touch simpler, here we can just look * at the current receive buffer pointer - if it's zero, we came * from idle, otherwise we came from after tone. */ if (cw_noise_spike_threshold > 0 && element_usec <= cw_noise_spike_threshold) { if (cw_rr_current == 0) cw_receive_state = RS_IDLE; else cw_receive_state = RS_AFTER_TONE; /* * Put the end tone timestamp back to how it was when we * came in to the routine. */ cw_rr_end_timestamp.tv_sec = saved_end_timestamp.tv_sec; cw_rr_end_timestamp.tv_usec = saved_end_timestamp.tv_usec; /* Print out any state change debug. */ if (cw_get_debug_flags () & CW_DEBUG_RECEIVE_STATES) fprintf (stderr, "cw: receive state ->%d\n", cw_receive_state); errno = EAGAIN; return RC_ERROR; } /* * At this point, we have to make a decision about the element * just received. Well use a routine that compares ranges to * tell us what it thinks this element is. If it can't decide, * it will hand us back an error which we return to the caller. * Otherwise, it returns a character, dot or dash, for us to * buffer. */ error = cw_identify_receive_tone_internal (element_usec, &representation); if (error) return error; /* * Update the averaging buffers so that the adaptive tracking of * received Morse speed stays up to date. But only do this if * we have set adaptive receiving; don't fiddle about trying to * track for fixed speed receive. */ if (cw_adaptive_receive_enabled) cw_update_adaptive_tracking_internal (element_usec, representation); /* Add the representation character to the receive buffer. */ cw_receive_representation_buffer[cw_rr_current++] = representation; /* * We just added a representation to the receive buffer. If it's * full, then we have to do something, even though it's unlikely. * What we'll do is make a unilateral declaration that if we get * this far, we go to end-of-char error state automatically. */ if (cw_rr_current == RECEIVE_CAPACITY - 1) { cw_receive_state = RS_ERR_CHAR; /* Print out any state change debug. */ if (cw_get_debug_flags () & CW_DEBUG_RECEIVE_STATES) fprintf (stderr, "cw: receive state ->%d\n", cw_receive_state); errno = ENOMEM; return RC_ERROR; } /* All is well. Move to the more normal after-tone state. */ cw_receive_state = RS_AFTER_TONE; /* Print out any state change debug. */ if (cw_get_debug_flags () & CW_DEBUG_RECEIVE_STATES) fprintf (stderr, "cw: receive state ->%d\n", cw_receive_state); return RC_SUCCESS; } /** * cw_receive_buffer_element_internal() * * Adds either a dot or a dash to the receive representation buffer. If * the timestamp is NULL, the current timestamp is used. The receive state * is updated as if we had just received a call to cw_end_receive_tone. */ static int cw_receive_buffer_element_internal (const struct timeval *timestamp, char element) { /* * The receive state is expected to be idle or after a tone in * order to use this routine. */ if (cw_receive_state != RS_IDLE && cw_receive_state != RS_AFTER_TONE) { errno = ERANGE; return RC_ERROR; } /* * This routine functions as if we have just seen a tone end, yet * without really seeing a tone start. To keep timing information * for routines that come later, we need to make sure that the * end of tone timestamp is set here. This is because the * receive representation routine looks at the time since the last * end of tone to determine whether we are at the end of a word, * or just at the end of a character. It doesn't matter that the * start of tone timestamp is never set - this is just for timing * the tone length, and we don't need to do that since we've * already been told whether this is a dot or a dash. */ if (timestamp != NULL) { if (timestamp->tv_sec < 0 || timestamp->tv_usec < 0 || timestamp->tv_usec >= USECS_PER_SEC) { errno = EINVAL; return RC_ERROR; } cw_rr_end_timestamp.tv_sec = timestamp->tv_sec; cw_rr_end_timestamp.tv_usec = timestamp->tv_usec; } else { if (gettimeofday (&cw_rr_end_timestamp, NULL) != 0) { perror ("cw: gettimeofday"); return RC_ERROR; } } /* Add the element to the receive representation buffer. */ cw_receive_representation_buffer[cw_rr_current++] = element; /* * We just added an element to the receive buffer. As above, if * it's full, then we have to do something, even though it's * unlikely to actually be full. */ if (cw_rr_current == RECEIVE_CAPACITY - 1) { cw_receive_state = RS_ERR_CHAR; /* Print out any state change debug. */ if (cw_get_debug_flags () & CW_DEBUG_RECEIVE_STATES) fprintf (stderr, "cw: receive state ->%d\n", cw_receive_state); errno = ENOMEM; return RC_ERROR; } /* * Since we effectively just saw the end of a tone, move to the * after-tone state. */ cw_receive_state = RS_AFTER_TONE; /* Print out any state change debug. */ if (cw_get_debug_flags () & CW_DEBUG_RECEIVE_STATES) fprintf (stderr, "cw: receive state ->%d\n", cw_receive_state); return RC_SUCCESS; } /** * cw_receive_buffer_dot() * cw_receive_buffer_dash() * * Adds either a dot or a dash to the receive representation buffer. If * the timestamp is NULL, the current timestamp is used. These routines * are for callers that have already determined whether a dot or dash was * received by a method other than calling the routines cw_start_receive_tone * and cw_end_receive_tone. On success, the relevant element is added to * the receive representation buffer. On error, the routines return -1, * with errno set to ERANGE if preceded by a cw_start_receive_tone call * with no matching cw_end_receive_tone or if an error condition currently * exists within the receive buffer, or ENOMEM if the receive representation * buffer is full. */ int cw_receive_buffer_dot (const struct timeval *timestamp) { return cw_receive_buffer_element_internal (timestamp, CW_DOT_REPRESENTATION); } int cw_receive_buffer_dash (const struct timeval *timestamp) { return cw_receive_buffer_element_internal (timestamp, CW_DASH_REPRESENTATION); } /** * cw_receive_representation() * * Returns the current buffered representation from the receive buffer. * On success, the function returns 0, and fills in representation with the * contents of the current representation buffer. On error, it returns -1, * with errno set to ERANGE if not preceded by a cw_end_receive_tone call, * a prior successful cw_receive_representation call, or a prior * cw_receive_buffer_dot or cw_receive_buffer_dash, EINVAL if the timestamp * passed in is invalid, or EAGAIN if the call is made too early to determine * whether a complete representation has yet been placed in the buffer * (that is, less than the inter-character gap period has elapsed since the * last cw_end_receive_tone or cw_receive_buffer_dot/dash call). end_of_word * indicates that the delay after the last tone received is longer that the * inter-word gap, and error_flag indicates that the representation was * terminated by an error condition. */ int cw_receive_representation (const struct timeval *timestamp, char *representation, int *end_of_word, int *error_flag) { int space_usec; /* Time difference in usecs */ struct timeval now_timestamp; /* The current time of day */ /* * If the the receive state indicates that we have in our possession * a completed representation at the end of word, just [re-]return it. */ if (cw_receive_state == RS_END_WORD || cw_receive_state == RS_ERR_WORD) { /* Return the representation buffered. */ if (end_of_word != NULL) *end_of_word = TRUE; if (error_flag != NULL) *error_flag = (cw_receive_state == RS_ERR_WORD); *representation = ASC_NUL; strncat (representation, cw_receive_representation_buffer, cw_rr_current); return RC_SUCCESS; } /* * If the receive state is also not end-of-char, and also not after * a tone, then we are idle or in a tone; in these cases, we return * ERANGE. */ if (cw_receive_state != RS_AFTER_TONE && cw_receive_state != RS_END_CHAR && cw_receive_state != RS_ERR_CHAR) { errno = ERANGE; return RC_ERROR; } /* * We now know the state is after a tone, or end-of-char, perhaps * with error. For all three of these cases, we're going to [re-] * compare the timestamp with the end of tone timestamp. This * could mean that in the case of end-of-char, we revise our * opinion on later calls to end-of-word. This is correct, since * it models reality. */ /* * If we weren't supplied with one, get the current timestamp, for * comparison against the latest end timestamp. */ if (timestamp != NULL) { if (timestamp->tv_sec < 0 || timestamp->tv_usec < 0 || timestamp->tv_usec >= USECS_PER_SEC) { errno = EINVAL; return RC_ERROR; } now_timestamp.tv_sec = timestamp->tv_sec; now_timestamp.tv_usec = timestamp->tv_usec; } else { if (gettimeofday (&now_timestamp, NULL) != 0) { perror ("cw: gettimeofday"); return RC_ERROR; } } /* * Now we need to compare the timestamps to determine the length * of the inter-tone gap. */ space_usec = cw_compare_timestamps_internal (&cw_rr_end_timestamp, &now_timestamp); /* Synchronize low level timings if required */ cw_sync_parameters_internal (); /* * If the timing was, within tolerance, a character space, then * that is what we'll call it. In this case, we complete the * representation and return it. */ if (space_usec >= cw_eoc_range_minimum && space_usec <= cw_eoc_range_maximum) { /* * If state is after tone, we can validly move at this point * to end of char. If it's not, then we're at end char or * at end char with error already, so leave it. */ if (cw_receive_state == RS_AFTER_TONE) cw_receive_state = RS_END_CHAR; /* Print out any state change debug. */ if (cw_get_debug_flags () & CW_DEBUG_RECEIVE_STATES) fprintf (stderr, "cw: receive state ->%d\n", cw_receive_state); /* Return the representation buffered. */ if (end_of_word != NULL) *end_of_word = FALSE; if (error_flag != NULL) *error_flag = (cw_receive_state == RS_ERR_CHAR); *representation = ASC_NUL; strncat (representation, cw_receive_representation_buffer, cw_rr_current); return RC_SUCCESS; } /* * If the timing indicated a word space, again we complete the * representation and return it. In this case, we also need to * inform the client that this looked like the end of a word, not * just a character. And, we don't care about the maximum period, * only that it exceeds the low end of the range. */ if (space_usec > cw_eoc_range_maximum) { /* * In this case, we have a transition to an end of word case. * If we were sat in an error case, we need to move to the * correct end of word state, otherwise, at after tone, we * go safely to the non-error end of word. */ if (cw_receive_state == RS_ERR_CHAR) cw_receive_state = RS_ERR_WORD; else cw_receive_state = RS_END_WORD; /* Print out any state change debug. */ if (cw_get_debug_flags () & CW_DEBUG_RECEIVE_STATES) fprintf (stderr, "cw: receive state ->%d\n", cw_receive_state); /* Return the representation buffered. */ if (end_of_word != NULL) *end_of_word = TRUE; if (error_flag != NULL) *error_flag = (cw_receive_state == RS_ERR_WORD); *representation = ASC_NUL; strncat (representation, cw_receive_representation_buffer, cw_rr_current); return RC_SUCCESS; } /* * If none of these conditions holds, then we cannot yet make a * judgement on what we have in the buffer, so return EAGAIN. */ errno = EAGAIN; return RC_ERROR; } /** * cw_receive_character() * * Returns the current buffered character from the representation buffer. * On success, the function returns 0, and fills in unsigned char *c with the * contents of the current representation buffer, translated into a character. * On error, it returns -1, with errno set to ERANGE if not preceded by a * cw_end_receive_tone call, a prior successful cw_receive_character * call, or a cw_receive_buffer_dot or cw_receive_buffer dash call, EINVAL * if the timestamp passed in is invalid, or EAGAIN if the call is made too * early to determine whether a complete character has yet been placed in the * buffer (that is, less than the inter-character gap period has elapsed since * the last cw_end_receive_tone or cw_receive_buffer_dot/dash call). * end_of_word indicates that the delay after the last tone received is * longer that the inter-word gap, and error_flag indicates that the character * was terminated by an error condition. */ int cw_receive_character (const struct timeval *timestamp, unsigned char *c, int *end_of_word, int *error_flag) { int error; /* Error status */ int lend_of_word; /* Local end of word flag */ int lerror_flag; /* Local error flag */ const cw_entry_t *cwptr; /* Pointer to table entry */ char representation[ RECEIVE_CAPACITY + 1 ]; /* Representation buffer */ /* See if we can obtain a representation from the receive routines. */ error = cw_receive_representation (timestamp, representation, &lend_of_word, &lerror_flag); if (error) return error; /* Look up the representation using the lookup functions. */ cwptr = cw_lookup_representation_internal (representation); if (cwptr == NULL) { errno = ENOENT; return RC_ERROR; } /* If we got this far, all is well, so return what we uncovered. */ if (c != NULL) *c = cwptr->character; if (end_of_word != NULL) *end_of_word = lend_of_word; if (error_flag != NULL) *error_flag = lerror_flag; return RC_SUCCESS; } /** * cw_clear_receive_buffer() * * Clears the receive representation buffer to receive tones again. This * routine must be called after successful, or terminating, * cw_receive_representation or cw_receive_character calls, to clear the * states and prepare the buffer to receive more tones. */ void cw_clear_receive_buffer () { cw_rr_current = 0; cw_receive_state = RS_IDLE; /* Print out any state change debug. */ if (cw_get_debug_flags () & CW_DEBUG_RECEIVE_STATES) fprintf (stderr, "cw: receive state ->%d\n", cw_receive_state); } /** * cw_get_receive_buffer_capacity() * * Returns the number of entries the receive buffer can accommodate. The * maximum number of character written out by cw_receive_representation is * the capacity + 1, the extra character being used for the terminating * NUL. */ int cw_get_receive_buffer_capacity () { return RECEIVE_CAPACITY; } /** * cw_get_receive_buffer_length() * * Returns the number of elements currently pending in the receive buffer. */ int cw_get_receive_buffer_length () { return cw_rr_current; } /* * cw_keyer_clock_internal implements the following state graph: * * +-----------------------------------------------------+ * | (all latches clear) | * | (dot latch) | * | +--------------------------+ * | | | * | v | * | +-------------> KS_IN_DOT_[A|B] -------> KS_AFTER_DOT_[A|B] * | |(dot paddle) ^ (delay) | * | | | |(dash latch/ * | | +------------+ | _B) * v | | | * --> KS_IDLE --+ +--------------------------+ * ^ | | | * | | | +-------------+(dot latch/ * | | | | _B) * | |(dash paddle) v (delay) | * | +-------------> KS_IN_DASH_[A|B] -------> KS_AFTER_DASH_[A|B] * | ^ | * | | | * | +--------------------------+ * | (dash latch) | * | (all latches clear) | * +-----------------------------------------------------+ */ static enum {KS_IDLE,KS_IN_DOT_A,KS_IN_DASH_A,KS_AFTER_DOT_A,KS_AFTER_DASH_A, KS_IN_DOT_B,KS_IN_DASH_B,KS_AFTER_DOT_B,KS_AFTER_DASH_B} cw_keyer_state = KS_IDLE; /* Indicates keyer state */ /** * cw_keyer_clock_internal() * * Informs the internal keyer states that the itimer expired, and we * received SIGALRM. */ static void cw_keyer_clock_internal () { /* Synchronize low level timing parameters if required. */ cw_sync_parameters_internal (); /* Decide what to do based on the current state. */ switch (cw_keyer_state) { /* Ignore calls if our state is idle. */ case KS_IDLE: return; /* * If we were in a dot, turn off tones and begin the after-dot * delay. Do much the same if we are in a dash. No routine * status checks are made since we are in a signal handler, * and can't readily return error codes to the client. */ case KS_IN_DOT_A: case KS_IN_DOT_B: cw_sound_internal (TONE_SILENT); cw_key_control_internal (FALSE); cw_request_timeout_internal (cw_end_of_ele_delay, NULL); if (cw_keyer_state == KS_IN_DOT_A) cw_keyer_state = KS_AFTER_DOT_A; else cw_keyer_state = KS_AFTER_DOT_B; /* Try to close sound early to mask speaker click. */ if (cw_keyer_state == KS_AFTER_DOT_A && !cw_ik_dash_latch && !cw_ik_dot_paddle) cw_sound_release_internal (); /* Print any required debug output. */ if (cw_get_debug_flags () & CW_DEBUG_KEYER_STATES) fprintf (stderr, "cw: keyer ->%d\n", cw_keyer_state); break; case KS_IN_DASH_A: case KS_IN_DASH_B: cw_sound_internal (TONE_SILENT); cw_key_control_internal (FALSE); cw_request_timeout_internal (cw_end_of_ele_delay, NULL); if (cw_keyer_state == KS_IN_DASH_A) cw_keyer_state = KS_AFTER_DASH_A; else cw_keyer_state = KS_AFTER_DASH_B; /* Try to close sound early to mask speaker click. */ if (cw_keyer_state == KS_AFTER_DASH_A && !cw_ik_dot_latch && !cw_ik_dash_paddle) cw_sound_release_internal (); /* Print any required debug output. */ if (cw_get_debug_flags () & CW_DEBUG_KEYER_STATES) fprintf (stderr, "cw: keyer ->%d\n", cw_keyer_state); break; /* * If we have just finished a dot or a dash and its post- * element delay, then reset the latches as appropriate. * Next, if in a _B state, go straight to the opposite * element state. If in an _A state, check the latch states; * if the opposite latch is set TRUE, then do the iambic * thing and alternate dots and dashes. If the same latch * is TRUE, repeat. And if nothing is true, then revert to * idling. */ case KS_AFTER_DOT_A: case KS_AFTER_DOT_B: if (!cw_ik_dot_paddle) cw_ik_dot_latch = FALSE; if (cw_keyer_state == KS_AFTER_DOT_B) { cw_sound_internal (cw_frequency); cw_key_control_internal (TRUE); cw_request_timeout_internal (cw_send_dash_length, NULL); cw_keyer_state = KS_IN_DASH_A; } else if (cw_ik_dash_latch) { cw_sound_internal (cw_frequency); cw_key_control_internal (TRUE); cw_request_timeout_internal (cw_send_dash_length, NULL); if (cw_ik_curtis_b_latch) { cw_ik_curtis_b_latch = FALSE; cw_keyer_state = KS_IN_DASH_B; } else cw_keyer_state = KS_IN_DASH_A; } else if (cw_ik_dot_latch) { cw_sound_internal (cw_frequency); cw_key_control_internal (TRUE); cw_request_timeout_internal (cw_send_dot_length, NULL); cw_keyer_state = KS_IN_DOT_A; } else { cw_keyer_state = KS_IDLE; cw_release_timeouts_internal (); cw_sound_release_internal (); } /* Print any required debug output. */ if (cw_get_debug_flags () & CW_DEBUG_KEYER_STATES) fprintf (stderr, "cw: keyer ->%d\n", cw_keyer_state); break; case KS_AFTER_DASH_A: case KS_AFTER_DASH_B: if (!cw_ik_dash_paddle) cw_ik_dash_latch = FALSE; if (cw_keyer_state == KS_AFTER_DASH_B) { cw_sound_internal (cw_frequency); cw_key_control_internal (TRUE); cw_request_timeout_internal (cw_send_dot_length, NULL); cw_keyer_state = KS_IN_DOT_A; } else if (cw_ik_dot_latch) { cw_sound_internal (cw_frequency); cw_key_control_internal (TRUE); cw_request_timeout_internal (cw_send_dot_length, NULL); if (cw_ik_curtis_b_latch) { cw_ik_curtis_b_latch = FALSE; cw_keyer_state = KS_IN_DOT_B; } else cw_keyer_state = KS_IN_DOT_A; } else if (cw_ik_dash_latch) { cw_sound_internal (cw_frequency); cw_key_control_internal (TRUE); cw_request_timeout_internal (cw_send_dash_length, NULL); cw_keyer_state = KS_IN_DASH_A; } else { cw_keyer_state = KS_IDLE; cw_release_timeouts_internal (); cw_sound_release_internal (); } /* Print any required debug output. */ if (cw_get_debug_flags () & CW_DEBUG_KEYER_STATES) fprintf (stderr, "cw: keyer ->%d\n", cw_keyer_state); break; } } /** * cw_keyer_paddle_event() * * Informs the internal keyer states that the keyer paddles have changed * state. The new paddle states are recorded, and if either transition from * FALSE to TRUE, paddle latches, for iambic functions, are also set. * On success, the routine returns 0. On error, it returns -1, with errno * set to EBUSY if the tone queue or straight key are using the sound card, * console speaker, or keying system. * * If appropriate, this routine starts the keyer functions sending the * relevant element. Element send and timing occurs in the background, so * this routine returns almost immediately. See cw_keyer_element_wait and * cw_keyer_wait for details about how to check the current status of * iambic keyer background processing. */ int cw_keyer_paddle_event (int dot_paddle_state, int dash_paddle_state) { /* * If the tone queue or the straight key are busy, this is going to * conflict with our use of the sound card, console sounder, and * keying system. So return an error status in this case. */ if (cw_straightkey_busy () || cw_tone_busy ()) { errno = EBUSY; return RC_ERROR; } /* Clean up and save the paddle states passed in. */ cw_ik_dot_paddle = (dot_paddle_state != 0); cw_ik_dash_paddle = (dash_paddle_state != 0); /* * Update the paddle latches if either paddle goes TRUE. The * latches are checked in the signal handler, so if the paddles go * back to FALSE during this element, the item still gets actioned. * The signal handler is also responsible for clearing down the * latches. */ if (cw_ik_dot_paddle) cw_ik_dot_latch = TRUE; if (cw_ik_dash_paddle) cw_ik_dash_latch = TRUE; /* * If in Curtis mode B, make a special check for both paddles TRUE * at the same time. This flag is checked by the signal handler, * to determine whether to add mode B trailing timing elements. */ if (cw_ik_curtis_mode_b && cw_ik_dot_paddle && cw_ik_dash_paddle) cw_ik_curtis_b_latch = TRUE; /* Print any required debug output. */ if (cw_get_debug_flags () & CW_DEBUG_KEYER_STATES) fprintf (stderr, "cw: keyer paddles %d,%d, latches %d,%d, curtis_b %d\n", cw_ik_dot_paddle, cw_ik_dash_paddle, cw_ik_dot_latch, cw_ik_dash_latch, cw_ik_curtis_b_latch); /* If the current state is idle, give the state process a nudge. */ if (cw_keyer_state == KS_IDLE) { if (cw_ik_dot_paddle) { /* Pretend we just finished a dash. */ if (cw_ik_curtis_b_latch) cw_keyer_state = KS_AFTER_DASH_B; else cw_keyer_state = KS_AFTER_DASH_A; cw_request_timeout_internal (0, cw_keyer_clock_internal); } else if (cw_ik_dash_paddle) { /* Pretend we just finished a dot. */ if (cw_ik_curtis_b_latch) cw_keyer_state = KS_AFTER_DOT_B; else cw_keyer_state = KS_AFTER_DOT_A; cw_request_timeout_internal (0, cw_keyer_clock_internal); } } /* Print any additional required debug output. */ if (cw_get_debug_flags () & CW_DEBUG_KEYER_STATES) fprintf (stderr, "cw: keyer ->%d\n", cw_keyer_state); return RC_SUCCESS; } /** * cw_keyer_dot_paddle_event() * cw_keyer_dash_paddle_event() * * Convenience functions to alter the state of just one of the two iambic * keyer paddles. The other paddle state of the paddle pair remains * unchanged. * * See cw_keyer_paddle_event for details of iambic keyer background * processing, and how to check its status. */ int cw_keyer_dot_paddle_event (int dot_paddle_state) { return cw_keyer_paddle_event (dot_paddle_state, cw_ik_dash_paddle); } int cw_keyer_dash_paddle_event (int dash_paddle_state) { return cw_keyer_paddle_event (cw_ik_dot_paddle, dash_paddle_state); } /** * cw_get_keyer_paddles() * * Returns the current saved states of the two paddles. */ void cw_get_keyer_paddles (int *dot_paddle_state, int *dash_paddle_state) { if (dot_paddle_state != NULL) *dot_paddle_state = cw_ik_dot_paddle; if (dash_paddle_state != NULL) *dash_paddle_state = cw_ik_dash_paddle; } /** * cw_get_keyer_paddle_latches() * * Returns the current saved states of the two paddle latches. A paddle * latches is set to TRUE when the paddle state becomes true, and is * cleared if the paddle state is FALSE when the element finishes sending. */ void cw_get_keyer_paddle_latches (int *dot_paddle_latch_state, int *dash_paddle_latch_state) { if (dot_paddle_latch_state != NULL) *dot_paddle_latch_state = cw_ik_dot_latch; if (dash_paddle_latch_state != NULL) *dash_paddle_latch_state = cw_ik_dash_latch; } /** * cw_keyer_busy() * * Indicates if the keyer is busy; returns TRUE if the keyer is going * through a dot or dash cycle, FALSE if the keyer is idle. */ int cw_keyer_busy () { return (cw_keyer_state != KS_IDLE); } /** * cw_keyer_element_wait() * * Waits until the end of the current element, dot or dash, from the * keyer. This routine returns 0 on success. On error, it returns -1, with * errno set to EDEADLK if SIGALRM is blocked. */ int cw_keyer_element_wait () { int error; /* Error status */ /* Check that SIGALRM is not blocked. */ error = cw_check_signal_mask_internal (); if (error) return error; /* * First wait for the state to move to idle (or just do nothing * if it's not), or to one of the after- states. */ while (cw_keyer_state != KS_IDLE && cw_keyer_state != KS_AFTER_DOT_A && cw_keyer_state != KS_AFTER_DOT_B && cw_keyer_state != KS_AFTER_DASH_A && cw_keyer_state != KS_AFTER_DASH_B) cw_signal_wait_internal (); /* * Now wait for the state to move to idle (unless it is, or was, * already), or one of the in- states, at which point we know * we're actually at the end of the element we were in when we * entered this routine. */ while (cw_keyer_state != KS_IDLE && cw_keyer_state != KS_IN_DOT_A && cw_keyer_state != KS_IN_DOT_B && cw_keyer_state != KS_IN_DASH_A && cw_keyer_state != KS_IN_DASH_B) cw_signal_wait_internal (); return RC_SUCCESS; } /** * cw_keyer_wait() * * Waits for the current keyer cycle to complete. The routine returns 0 * on success. On error, it returns -1, with errno set to EDEADLK if * SIGALRM is blocked or if either paddle state is TRUE. */ int cw_keyer_wait () { int error; /* Error status */ /* Check that SIGALRM is not blocked. */ error = cw_check_signal_mask_internal (); if (error) return error; /* * Check that neither paddle is TRUE; if either is, then the * signal cycle is going to continue forever, and we'll never * return from this routine. */ if (cw_ik_dot_paddle || cw_ik_dash_paddle) { errno = EDEADLK; return RC_ERROR; } /* Wait for the keyer state to go idle. */ while (cw_keyer_state != KS_IDLE) cw_signal_wait_internal (); return RC_SUCCESS; } /** * cw_straightkey_timeout_internal() * * Soundcard tone data is only buffered to last about a second on each * cw_sound_generate_internal() call, and holding down the straight key * for longer than this could cause a soundcard data underrun. To * guard against this, a timeout is generated every half-second or so * while the straight key is down. The timeout generates a chunk of * sound data for the soundcard. */ static void cw_straightkey_timeout_internal () { /* If the straight key is not down, ignore the call. */ if (cw_sk_key_down) { /* Generate another quantum of tone data. */ cw_sound_generate_internal (); /* Request another timeout call. */ cw_request_timeout_internal (STRAIGHTKEY_TIMEOUT, NULL); } } /** * cw_straightkey_event() * * Informs the library that the straight key has changed state. This routine * returns 0 on success. On error, it returns -1, with errno set to EBUSY * if the tone queue or iambic keyer are using the sound card, console * speaker, or keying control system. If key_state indicates no change of * state, the call is ignored. */ int cw_straightkey_event (int key_state) { /* * If the tone queue or the keyer are busy, we can't use the sound * card, console sounder, or the key control system. */ if (cw_tone_busy () || cw_keyer_busy ()) { errno = EBUSY; return RC_ERROR; } /* If the key state did not change, ignore the call. */ if ((cw_sk_key_down && !key_state) || (!cw_sk_key_down && key_state)) { /* Save the new key state. */ cw_sk_key_down = (key_state != 0); /* Print out any debug about the new key state. */ if (cw_get_debug_flags () & CW_DEBUG_STRAIGHTKEY) fprintf (stderr, "cw: straight key state ->%s\n", cw_sk_key_down ? "DOWN" : "UP"); /* * Do tones and keying, and set up timeouts and soundcard * activities to match the new key state. */ if (cw_sk_key_down) { cw_sound_internal (cw_frequency); cw_key_control_internal (TRUE); /* Start timeouts to keep soundcard tones running. */ cw_request_timeout_internal (STRAIGHTKEY_TIMEOUT, cw_straightkey_timeout_internal); } else { cw_sound_internal (TONE_SILENT); cw_key_control_internal (FALSE); /* * Indicate that we have finished with timeouts, * and also with the soundcard too. There's no way * of knowing when straight keying is completed, * so the only thing we can do here is to release * the timers and sound on each key up event. */ cw_release_timeouts_internal (); cw_sound_release_internal (); } } return RC_SUCCESS; } /** * cw_get_straightkey_state() * * Returns the current saved state of the straight key; TRUE if the key is * down, FALSE if up. */ int cw_get_straightkey_state () { return cw_sk_key_down; } /** * cw_straightkey_busy() * * Returns TRUE if the straight key is busy, FALSE if not. This routine is * just a pseudonym for cw_get_straightkey_state, and exists to fill a hole * in the API naming conventions. */ int cw_straightkey_busy () { return cw_sk_key_down; }