/*****************************************************************************
**
**  Copyright 1996, 1997 by Ernest R. Schreurs.
**  All rights reserved.
**  Use of this source code is allowed under the following conditions:
**  You must inform people that you based your work on this stuff.
**  If you charge a fee in any form for your product, you must inform people
**  that this stuff is available free of charge.
**  Refer to the documentation for more information.
**
*****************************************************************************/
#define MODULE  "WAV2CAS.C"
/*****************************************************************************
**  NAME: WAV2CAS.C
**
**  Author            : Ernest R. Schreurs
**  Date              : May 1, 1997
**  Release           : 01.00
**
**  Description       : This program will convert a .wav sound file to
**                      a format known as the .cas format.  It is assumed
**                      that the wav file contains a sample of a Classic Atari
**                      cassette tape.  This program converts the FSK sounds to
**                      the data they are representing.
**
*****************************************************************************/

/*****************************************************************************
==  INCLUDE FILES
*****************************************************************************/
#include <stdio.h>              /* For printf() and gets()              */
#include <ctype.h>              /* For isalnum() and toupper()          */
#include <stdlib.h>             /* For the exit function                */
#include <string.h>             /* String and memory stuff              */

/*****************************************************************************
==  DEFINED SYMBOLS
*****************************************************************************/

#ifndef FALSE
#define FALSE               0
#endif
#ifndef TRUE
#define TRUE                1
#endif

#ifndef NULL
#define NULL                0
#endif
#define PATH_LEN            128             /* Maximum path length          */

#define BUF_LEN             80              /* fgets buffer length          */
#define SUCCESS             1               /* Success is non-zero          */
#define FAILURE             0               /* Failure is zero              */

/*
**  The PCM data is read from the wave file into a PCM buffer.
**  The size of the buffer that is allocated is equal to the value defined
**  below.  It must be an even value.  The buffer is replenished when half
**  of the buffer is empty.  Therefore we also need to know what half of
**  the buffer size is.  Make sure these values agree, if changing one,
**  always change the other too.
*/
#define PCM_BUF_LEN         512             /* Size of PCM data buffer      */
#define PCM_BUF_HALF        256             /* Exactly half of above value  */
#define SAMPLE_CNT_TBL_LEN  3000L           /* Size of sample count table   */

/*
**  A tape record starts with a Pre-Record Write Tone.
**  Then we find bytes, usually 132, each starting with a startbit,
**  eight data bits, lsb first, followed by a stopbit.
**  This is all coded in mark and space tones, as defined below.
*/
#define FSK_MARK            1               /* Mark tone represents a 1     */
#define FSK_SPACE           0               /* Space tone represents a 0    */
#define FSK_1               1               /* A 1 bit is a mark            */
#define FSK_0               0               /* A 0 bit is a space           */
#define FSK_PRWT            1               /* PRWT tone is a mark          */
#define FSK_STARTBIT        0               /* Startbit is a space          */
#define FSK_STOPBIT         1               /* Stopbit is a mark            */

/*
**  Definitions for the mark and space tone frequencies.
*/
#define FSK_TONE_MARK       5327            /* Frequency of mark tone       */
#define FSK_TONE_SPACE      3995            /* Frequency of space tone      */

/*****************************************************************************
==  MACRO DEFINITIONS
*****************************************************************************/
/*
**  Macro for casting stuff to requirements of stupid
**  standard library functions.
*/

#define FGETS( buf, buf_len, file_ptr )                                 \
    (void *)fgets( (char *)buf, (int)buf_len, (FILE *)file_ptr )

#define STRLEN( str )                                                   \
    strlen( (const char *)(str) )

/*
**  Macro for getting input from the terminal,
**  allowing the user to exit with either control Z or
**  inputting the string ^Z to indicate intention of
**  terminating the program.
*/
#define GET_BUF()                                                           \
{                                                                           \
    if ( FGETS( buf, BUF_LEN, stdin )  ==  NULL )                           \
    {                                                                       \
        printf( "Terminated by ^Z\n" );                                     \
        exit(0);                                                            \
    }                                                                       \
    if ( memcmp( buf, "^Z", 2 ) == 0 || memcmp( buf, "^z", 2 ) == 0  )      \
    {                                                                       \
        printf( "Terminated by ^Z\n" );                                     \
        exit(0);                                                            \
    }                                                                       \
}

#define PRINT( lst )                                                        \
{                                                                           \
    if( diagnostics )                                                       \
    {                                                                       \
        printf lst;                                                         \
    }                                                                       \
}

/*****************************************************************************
==  TYPE and STRUCTURE DEFINITIONS
*****************************************************************************/
typedef     unsigned char   bool;   /* Boolean value                        */
typedef     unsigned char   ubyte;  /* Exactly eight bits, unsigned         */
typedef     short           int16;  /* At least 16 bits, signed             */
typedef     unsigned short  uint16; /* At least 16 bits, unsigned           */
typedef     long            int32;  /* At least 32 bits, signed             */
typedef     unsigned long   uint32; /* At least 32 bits, unsigned           */

/*
**  Cassette file header.
*/

typedef struct
{
    ubyte       cas_record_id[4];       /* Cassette record type             */
    ubyte       cas_len_lo;             /* Record length low byte           */
    ubyte       cas_len_hi;             /* Record length high byte          */
    ubyte       cas_aux1;               /* Type dependant data              */
    ubyte       cas_aux2;               /* Type dependant data              */
    ubyte       cas_data[8192];         /* Data                             */
} cas_blk;

/*
**  fsk_cnt stream.  The duration of each mark and space is counted in
**  pairs, since they alternate.
*/

typedef struct
{
    uint32      sample_pos;             /* Position of sample in .wav file  */
    uint32      sample_cnt_mark;        /* Number of samples in mark state  */
    uint32      sample_cnt_space;       /* Number of samples in space state */
} sample_cnt_blk;

/*****************************************************************************
==  IMPORTED VARIABLES
*****************************************************************************/
/*****************************************************************************
==  LOCAL ( HIDDEN ) VARIABLES
*****************************************************************************/
static FILE *   wav_file;               /* Wave file                        */
static FILE *   fsk_file;               /* FSK intermediate file            */
static FILE *   hex_file;               /* HEX intermediate file            */
static FILE *   cas_file;               /* Cassette image file              */

static cas_blk  cas_rec;                /* The cassette record buffer       */

static uint32   cnt_cur;                /* Current period count, rounded    */
static uint32   cnt_next;               /* Next period count                */
static uint32   cnt_prev;               /* Previous period count            */
static uint32   cnt_total;              /* Total sample count               */
static uint32   cnt_val;                /* Current period actual count      */

static bool     diagnostics;            /* Print diagnostic data            */
static bool     fsk;                    /* Write FSK table file             */
static bool     fsk_print_first;        /* Writing FSK first line           */
static uint32   sample_cnt_high;        /* High count limit for mark tone   */
static uint32   sample_cnt_irg;         /* Count limit for IRG detection    */
static uint32   sample_cnt_low;         /* Low count limit for mark tone    */
static uint32   sample_cnt_bit;         /* Expected sample count for one bit*/
static uint32   sample_cnt_bit_limit;   /* Low limit count for one bit      */
static uint32   sample_cnt_minimum;     /* Minimum count for complete period*/
static sample_cnt_blk * sample_cnt_tbl; /* Count table                      */
static uint32   sample_tbl_ndx;         /* Count table index                */
static uint16   sample_tbl_level;       /* Current level being counted      */

static ubyte    pcm[PCM_BUF_LEN];       /* Buffer with samples              */
static uint32   pcm_bytes;              /* Number of bytes read into buffer */
static uint32   pcm_cnt;                /* Buffer byte count                */
static uint32   pcm_ck_len;             /* Length of PCM data chunk in file */
static uint32   pcm_level_previous;     /* Previous sample level            */
static bool     pcm_level_rising;       /* Rising / falling                 */
static uint32   pcm_ndx;                /* Buffer index pointer             */
static uint32   pcm_ndx_end;            /* Buffer end pointer to free space */

static uint32   printed_bytes;          /* Number of bytes printed on line  */
static uint32   sample_rate;            /* Sampling rate                    */

/*****************************************************************************
==  EXPORTED VARIABLES
*****************************************************************************/
/*****************************************************************************
==  IMPORTED FUNCTIONS
*****************************************************************************/
/*****************************************************************************
==  LOCAL ( HIDDEN ) FUNCTIONS
*****************************************************************************/
static void     cleanup( void );
static uint32   period_sample_cnt( void );
static void     process_cnt( uint32 cnt, uint16 level );
static uint32   process_header( void );
static void     process_record( void );
static void     replenish_buffer( void );
static void     usage( char * cmd );
static uint32   wav2fsk( void );

/*****************************************************************************
==  EXPORTED FUNCTIONS
*****************************************************************************/
int                 main();                 /* Normal entry point to it all */

/*****************************************************************************
==  LOCAL ( HIDDEN ) FUNCTIONS
*****************************************************************************/

/*****************************************************************************
**  NAME:  cleanup()
**
**  PURPOSE:
**      Cleanup any mess that was created.
**
**  DESCRIPTION:
**      This function will attempt to close all open files and
**      free allocated memory.
**
**  INPUT:
**      - The file pointers and paths are used.
**
**  OUTPUT:
**      The function returns nothing.
**
*/

static void         cleanup( void )
{
    if( wav_file )
    {
        fclose( wav_file );
    }
    if( fsk_file )
    {
        fclose( fsk_file );
    }
    if( hex_file )
    {
        fclose( hex_file );
    }
    if( cas_file )
    {
        fclose( cas_file );
    }

    if( sample_cnt_tbl )
        free( (void *)sample_cnt_tbl );
    return;
}

/*****************************************************************************
**  NAME:  period_sample_cnt()
**
**  PURPOSE:
**      Count the number of samples within one period.
**
**  DESCRIPTION:
**      This function will count the samples between two period tops.
**
**  INPUT:
**      Nothing.
**      Data is taken from the pcm buffer.
**
**  OUTPUT:
**      Sets the sample count to the number of samples within one period.
**      If this is zero, there are no more samples.
**      Prints results.
**      Returns SUCCESS if data converted successfully.
**      Returns FAILURE if some error occurred.
**
*/

static uint32       period_sample_cnt( void )
{

/*
**  What was the current sample count, will now be the previous value.
**  What was the next sample count, will now be the current value, which
**  will later be rounded by comparing it with the new previous and next
**  sample counts.
**  What was the actual next sample count will be recorded as the value of
**  the sample counter for purposes of timing.
**  Determine the count for the next sample count.
*/
    cnt_prev = cnt_val;
    cnt_cur = cnt_next;
    cnt_val = cnt_next;
    cnt_next = 0;

    while( pcm_bytes )
    {

/*
**  Replenish buffer if beyond the critical mark.
*/
        if( pcm_ndx >= PCM_BUF_HALF )
            replenish_buffer();

/*
**  For better difference between mark and space, we count one complete
**  period, starting at one top, until the top one period away.
**  We start out with a falling level, back to rising, and when the level
**  stops rising, we have counted one complete period.  In order to eliminate
**  noise, the number of samples must be beyond a treshold.  This by itself
**  does not eliminate noise, but it eliminates low period counts.
*/
        if( pcm_level_rising )
        {                                               /* Level rising   */
            if( pcm[pcm_ndx] <= pcm_level_previous )    /* At the top?    */
            {
                pcm_level_rising = FALSE;
                pcm_level_previous = 255;
                if( cnt_next > sample_cnt_minimum )     /* Is it a period?*/
                    break;
            }
            else
                pcm_level_previous = pcm[pcm_ndx];
        } /* end if level rising */
        else
        {                                               /* Level falling  */
            if( pcm[pcm_ndx] >= pcm_level_previous )    /* At the bottom? */
            {
                pcm_level_rising = TRUE;
                pcm_level_previous = 0;
            }
            else
                pcm_level_previous = pcm[pcm_ndx];
        } /* end else if level rising */

/*
**  Count the number of samples within this period.
*/
        cnt_next++;
        pcm_ndx++;
        pcm_bytes--;
    }

/*
**  Allow for sampling timing differences.  If previous and next value
**  agree, the current sample is made to be the same value.  Otherwise,
**  it remains the sample count value as counted.
*/
    if( ( cnt_val == sample_cnt_low ) && ( cnt_next >= sample_cnt_low ) )
        if( cnt_prev > sample_cnt_low )
            cnt_cur = sample_cnt_low + 1;
    if( ( cnt_val == sample_cnt_low ) && ( cnt_next <= sample_cnt_low ) )
        if( cnt_prev < sample_cnt_low )
            cnt_cur = sample_cnt_low - 1;

    return( SUCCESS );
}

/*****************************************************************************
**  NAME:  process_cnt()
**
**  PURPOSE:
**      The sample count is used to build a table of the duration of each
**      mark and space tone.  Since this is fsk data, when one stops, the
**      other starts, so they are counted in pairs.
**
**  DESCRIPTION:
**      The samples are added to the sample count table.  When there is a
**      complete record in the table, we can output it to the .cas file.
**
**  INPUT:
**      - The number of samples to be represented.
**      - The level represented by the samples.
**
**  OUTPUT:
**      Data is added to the table.
**      The function returns nothing.
**
*/

static void         process_cnt( cnt, level )
uint32  cnt;                        /* Number of samples of this level      */
uint16  level;                      /* Interpreted level of signal          */
{

/*
**  If the level is the same as the previous level, we add the count to the
**  running total.  If the level is not the same, we start a new running total.
**  If the new level is a space, start counting in the space counter.  If the
**  level is a mark, we must increment the buffer pointer, and then we can
**  start counting in the mark counter.
*/

    if( level == sample_tbl_level )
    {
        if( level == FSK_SPACE )
            sample_cnt_tbl[sample_tbl_ndx].sample_cnt_space += cnt;
        else
            sample_cnt_tbl[sample_tbl_ndx].sample_cnt_mark += cnt;
    }
    else
    {
        sample_tbl_level = level;
        if( level == FSK_SPACE )
        {
            sample_cnt_tbl[sample_tbl_ndx].sample_cnt_space = cnt;

/*
**  If this mark was long enough to be recognized as an IRG or PRWT,
**  we can start interpreting the count values as bits and bytes.
*/
            if( sample_cnt_tbl[sample_tbl_ndx].sample_cnt_mark > sample_cnt_irg )
            {
                process_record();
            }
        }
        else
        {
            if( sample_tbl_ndx == SAMPLE_CNT_TBL_LEN )  /* Table full? */
                process_record();
            sample_tbl_ndx++;
            sample_cnt_tbl[sample_tbl_ndx].sample_cnt_mark = cnt;
            sample_cnt_tbl[sample_tbl_ndx].sample_pos = cnt_total;
        }
    }

    cnt_total += cnt;

    return;
}

/*****************************************************************************
**  NAME:  process_header()
**
**  PURPOSE:
**      Process the data from the header of the .wav file and store
**      relevant information.
**
**  DESCRIPTION:
**      This function will read the relevant data about the .wav file
**      and store it.
**
**  INPUT:
**      Nothing.
**      Data is read from the wave file.
**
**  OUTPUT:
**      Prints results.
**      Stores data related to the format and contents of the wave file.
**      Returns SUCCESS if header was processed successfully.
**      Returns FAILURE if some error occurred.
**
*/

static uint32       process_header( void )
{
    uint16          bits_sample;            /* Sample size                  */
    uint32          bytes;                  /* Number of bytes read         */
    uint16          channels;               /* Number of channels           */
    uint32          ck_len;                 /* Chunk length                 */
    uint32          fi_len;                 /* File length                  */
    uint16          fmt_tag;                /* Format tag                   */
    ubyte           wav_rec[512];           /* Buffer for wave data         */

/*
**  Wave files usually look like this
**
**  char[4] = "RIFF", long file size not including these 8 bytes,
**  char[4] = "WAVE",
**  char[4] = "fmt ", long wave format header size not including these 8 bytes,
**  bunch of longs containing info about sample rate and such,
**  char[4] = "data", long wave data size not including these 8 bytes,
**  gobs of PCM data bytes.
**  See below for more details.
*/

/*
**  .WAV files should begin with RIFF, followed by the size.
*/
    bytes = fread( (char *)wav_rec, (int)1, (int)4, wav_file );
    if( ( bytes < 4 ) || memcmp( wav_rec, "RIFF", 4 ) )
    {
        fprintf(stderr, "\nThis is not a valid .wav file, it does not begin with \"RIFF\".\n");
        return( FAILURE );
    }

/*
**  Get the next four bytes and convert them to an uint32 value.
*/
    bytes = fread( (char *)wav_rec, (int)1, (int)4, wav_file );
    if( bytes < 4 )
    {
        fprintf(stderr, "\nThis is not a valid .wav file, invalid file length.\n");
        return( FAILURE );
    }
    fi_len = (((((((uint32)wav_rec[3]) << 8 ) + wav_rec[2] ) << 8 )
              + wav_rec[1]) << 8 ) + wav_rec[0];

/*
**  Next we should find the block type "WAVE".
*/
    bytes = fread( (char *)wav_rec, (int)1, (int)4, wav_file );
    if( ( bytes < 4 ) || memcmp( wav_rec, "WAVE", 4 ) )
    {
        fprintf(stderr, "\nThis is not a valid .wav file, it does not contain \"WAVE\".\n");
        return( FAILURE );
    }

/*
**  Next we should find the block type "fmt ".
*/
    bytes = fread( (char *)wav_rec, (int)1, (int)4, wav_file );
    if( ( bytes < 4 ) || memcmp( wav_rec, "fmt ", 4 ) )
    {
        fprintf(stderr, "\nThis is not a valid .wav file, it does not contain \"fmt \".\n");
        return( FAILURE );
    }

/*
**  Get the chunk length.
*/
    bytes = fread( (char *)wav_rec, (int)1, (int)4, wav_file );
    if( bytes < 4 )
    {
        fprintf(stderr, "\nThis is not a valid .wav file, invalid chunk length.\n");
        return( FAILURE );
    }
    ck_len = (((((((uint32)wav_rec[3]) << 8 ) + wav_rec[2] ) << 8 )
              + wav_rec[1]) << 8 ) + wav_rec[0];

/*
**  Get the format information.
*/
    bytes = fread( (char *)wav_rec, (int)1, (int)14, wav_file );
    if( bytes < 14 )
    {
        fprintf(stderr, "\nThis is not a valid .wav file, invalid format data.\n");
        return( FAILURE );
    }

    fmt_tag = (((uint16)wav_rec[1]) << 8 ) + wav_rec[0];
    channels = (((uint16)wav_rec[3]) << 8 ) + wav_rec[2];
    sample_rate = (((((((uint32)wav_rec[7]) << 8 ) + wav_rec[6] ) << 8 )
              + wav_rec[5]) << 8 ) + wav_rec[4];

/*
**  Set sample rate related variables.
*/
    if( sample_rate == 44100L )
    {
        sample_cnt_low = 10;
        sample_cnt_high = 14;
        sample_cnt_irg = 1000;
        sample_cnt_bit = 74;
        sample_cnt_bit_limit = 40;
        sample_cnt_minimum = 6;
    }
    else
    if( sample_rate == 22050 )
    {
        sample_cnt_low = 6;
        sample_cnt_high = 7;
        sample_cnt_irg = 500;
        sample_cnt_bit = 37;
        sample_cnt_bit_limit = 20;
        sample_cnt_minimum = 3;
    }
    else
    {
        fprintf(stderr, "\nSample rates other than 22.050 and 44.100 not supported.\n");
        return( FAILURE );
    }

    fmt_tag = (((uint16)wav_rec[1]) << 8 ) + wav_rec[0];
    if( fmt_tag != 0x0001 )
    {
        fprintf(stderr, "\nNot a PCM wave file, format 0x%.4x not supported.\n", fmt_tag);
        return( FAILURE );
    }
    if( channels != 1 )
    {
        fprintf(stderr, "\nNot a MONO wave file, stereo and multi-channel files not supported.\n");
        return( FAILURE );
    }

/*
**  Get the bits per sample information.
*/
    bytes = fread( (char *)wav_rec, (int)1, (int)2, wav_file );
    if( bytes < 2 )
    {
        fprintf(stderr, "\nThis is not a valid .wav file, invalid format data.\n");
        return( FAILURE );
    }

    bits_sample = (((uint16)wav_rec[1]) << 8 ) + wav_rec[0];

    if( bits_sample != 8 )
    {
        fprintf(stderr, "\nNot a sound file sampled at 8 bit, format not supported.\n");
        return( FAILURE );
    }

/*
**  Next we should find the block type "data".
*/
    bytes = fread( (char *)wav_rec, (int)1, (int)4, wav_file );
    if( ( bytes < 4 ) || memcmp( wav_rec, "data", 4 ) )
    {
        fprintf(stderr, "\nThis is not a valid .wav file, it does not contain \"data\".\n");
        return( FAILURE );
    }

/*
**  Get the chunk length.
*/
    bytes = fread( (char *)wav_rec, (int)1, (int)4, wav_file );
    if( bytes < 4 )
    {
        fprintf(stderr, "\nThis is not a valid .wav file, invalid chunk length.\n");
        return( FAILURE );
    }
    pcm_ck_len = (((((((uint32)wav_rec[3]) << 8 ) + wav_rec[2] ) << 8 )
              + wav_rec[1]) << 8 ) + wav_rec[0];

    fprintf(stderr, "\nMONO 8 bit PCM format.  Sampling rate is %lu Hz.\n", sample_rate);

/*
**  Well, we finally hit the PCM sample data.
**  This looks like what we wanted, so we will call this success.
*/

    return( SUCCESS );
}

/*****************************************************************************
**  NAME:  process_record()
**
**  PURPOSE:
**      There is data in the count table.  This must now be interpreted.
**      The count values are converted to the data they are representing.
**
**  DESCRIPTION:
**      This function will take data from the count table and interpret it.
**      Then this data is removed from the buffer to make room for new data.
**      Data that is not processed when we leave this function is moved towards
**      the start of the buffer in order to make room for the new data.
**      We should get here every time a complete record has been stuffed into
**      the table.
**
**  INPUT:
**      Nothing.
**      Data is taken from the sample count table.
**
**  OUTPUT:
**      Data is output to the file.
**      The table is reset to the beginning.
**      The function returns nothing.
**
*/

static void         process_record( void )
{
    uint32          bit_cnt;                /* Bit counter                  */
    uint32          bit_len;                /* Length of one bit            */
    uint32          bit_limit;              /* Length for recognizing bit   */
    ubyte           byte;                   /* Byte decoded from fsk data   */
    uint32          bytes;                  /* Number of bytes read         */
    uint32          checksum;               /* Checksum of the record       */
    uint32          cnt;                    /* Count of samples             */
    uint32          cnt_sum;                /* Total sample counts for byte */
    uint32          ctl_1_cnt;              /* Samples in control byte 1    */
    uint32          ctl_2_cnt;              /* Samples in control byte 2    */
    uint32          data_ndx;               /* Index for record data        */
    uint32          ndx;                    /* Index into buffer            */
    uint32          ndx_prwt;               /* end of record                */

/*
**  If we only have a mark tone, this must be the leader of the tape.
**  We should write out the leader to the file, but for now, we will simply
**  ignore it.  It will be written with the first record.
*/
    if( sample_tbl_ndx == 0 )
        return;

    bytes = 0;

    PRINT( ("\nIn process_record() sample_tbl_ndx = %lu\n", sample_tbl_ndx) );
    PRINT( ("Now cleaning up glitches.\n") );

/*
**  Output the table to the FSK file and the diagnostics data.
*/
    for( ndx = 0; ndx <= sample_tbl_ndx; ndx++ )
    {
        PRINT( ("ndx %lu Position %lu Mark %lu, Space %lu.\n", ndx,
                sample_cnt_tbl[ndx].sample_pos,
                sample_cnt_tbl[ndx].sample_cnt_mark,
                sample_cnt_tbl[ndx].sample_cnt_space ) );

/*
**  Output the table to the FSK file.
**  The last space count was not finished yet, so we will have to print that
**  the next time we print the table.  The last thing we print is the
**  last mark count, so do not print that the next time.  Of course all
**  this is not applied for the very first and very last time we print
**  the contents of the table.
*/
        if( fsk )
        {
            if( ndx < sample_tbl_ndx )
            {
                if( ( ndx == 0 ) && ( fsk_print_first == FALSE ) )
                {                   /* First pair of table  */
                    fprintf( fsk_file, "%.5lu\n",
                        sample_cnt_tbl[ndx].sample_cnt_space );
                }
                else
                {                   /* First time around or regular pair */
                    fprintf( fsk_file, "%.8lu %.5lu %.5lu\n",
                        sample_cnt_tbl[ndx].sample_pos,
                        sample_cnt_tbl[ndx].sample_cnt_mark,
                        sample_cnt_tbl[ndx].sample_cnt_space );
                    fsk_print_first = FALSE;
                }
            } /* end if not at end of table */
            else
            {                       /* Last pair in table   */
                if( cnt_cur )       /* Not yet end of file? */
                {
                    fprintf( fsk_file, "%.8lu %.5lu ",
                        sample_cnt_tbl[ndx].sample_pos,
                        sample_cnt_tbl[ndx].sample_cnt_mark );
                }
                else
                {                   /* end of file, flush all */
                    fprintf( fsk_file, "%.8lu %.5lu %.5lu\n",
                        sample_cnt_tbl[ndx].sample_pos,
                        sample_cnt_tbl[ndx].sample_cnt_mark,
                        sample_cnt_tbl[ndx].sample_cnt_space );
                }
            } /* end else if not at end of table */
        } /* end if fsk data requested */
    } /* end for all table entries */
 
/*
**  If things worked the way they were designed, we should have one complete
**  record in the table now.  It starts out with the Pre Record Write Tone.
**  Before we start processing, first clean up glitches.
**  Mark and space tones that are very short should be ignored.  We add their
**  count to the preceeding count.
**  Maybe we should first see how many glitches there are, possibly disposing
**  of this entire record as noise in the leader or PRWT.
**  Since our record should start out with a very long mark tone, we will
**  assume that this first mark tone is fine.
*/
    ndx_prwt = sample_tbl_ndx;

    for( ndx = 0; ndx < sample_tbl_ndx; )
    {
        if( ndx && ( sample_cnt_tbl[ndx].sample_cnt_mark > sample_cnt_irg ) )
        {
            ndx_prwt = ndx;
            break;
        }

/*
**  If the current space count is too low, add it, and the next mark count to
**  the current mark count, move the next space count to the current space
**  count, and move up the rest of the table.
**  However, if the next mark count is also too low, add this space count
**  and the next mark count to the next space count.  We can do this by
**  adding the next mark count and the next space count to the current space
**  count, and then move up the rest of the table.
*/
        if( sample_cnt_tbl[ndx].sample_cnt_space < sample_cnt_bit_limit )
        {
            if( sample_cnt_tbl[ndx + 1].sample_cnt_mark < sample_cnt_bit_limit )
            {
                sample_cnt_tbl[ndx].sample_cnt_space +=
                sample_cnt_tbl[ndx + 1].sample_cnt_mark +
                sample_cnt_tbl[ndx + 1].sample_cnt_space;
            }
            else
            {
                sample_cnt_tbl[ndx].sample_cnt_mark +=
                sample_cnt_tbl[ndx].sample_cnt_space +
                sample_cnt_tbl[ndx + 1].sample_cnt_mark;
                sample_cnt_tbl[ndx].sample_cnt_space =
                sample_cnt_tbl[ndx + 1].sample_cnt_space;
            }

/*
**  Move the sample counts after the next one up by one.
*/
            if( ndx + 1 < sample_tbl_ndx )
            {
                memcpy( &(sample_cnt_tbl[ndx + 1]),
                        &(sample_cnt_tbl[ndx + 2]),
                        (sample_tbl_ndx - ndx - 1) * sizeof(sample_cnt_blk) );
            }
            sample_tbl_ndx--;
            ndx_prwt = sample_tbl_ndx;
            continue; /* Make sure it is fine now */
        }

/*
**  If the next mark count is too low, add it, and the next space count to
**  the current space count, and move up the rest of the table.
*/
        if( sample_cnt_tbl[ndx + 1].sample_cnt_mark < sample_cnt_bit_limit )
        {
            sample_cnt_tbl[ndx].sample_cnt_space +=
            sample_cnt_tbl[ndx + 1].sample_cnt_mark +
            sample_cnt_tbl[ndx + 1].sample_cnt_space;

/*
**  Move the sample counts after the next one up by one.
*/
            if( ndx + 1 < sample_tbl_ndx )
            {
                memcpy( &(sample_cnt_tbl[ndx + 1]),
                        &(sample_cnt_tbl[ndx + 2]),
                        (sample_tbl_ndx - ndx - 1) * sizeof(sample_cnt_blk) );
            }
            sample_tbl_ndx--;
            ndx_prwt = sample_tbl_ndx;
            continue; /* Make sure it is fine now */
        }

/*
**  This sample count is fine, move to the next one.
*/
        ndx++;
    }

    if( diagnostics )
    {
        printf("Done cleaning up glitches. %lu elements to process.\n", ndx_prwt);
        for( ndx = 0; ndx <= ndx_prwt; ndx++ )
        {
            printf("ndx %lu Position %lu Mark %lu, Space %lu.\n", ndx,
                sample_cnt_tbl[ndx].sample_pos,
                sample_cnt_tbl[ndx].sample_cnt_mark,
                sample_cnt_tbl[ndx].sample_cnt_space );
        }
    }

/*
**  The table starts with a Pre-Record Write Tone.
**  A bit is about 73.5 samples if the sample rate is 44.100.
**  We need 73.5 samples to form a single bit.  However, the tape
**  may not be coded at 600 baud.
**  We have to allow for some tolerance in the baudrate.
**  We need to detect the baudrate now.
**  Each record starts out with two marker bytes with a value of 0x55.
**  We will use these bytes to detect the baudrate, since that is what they
**  were intended to be used for.
**  Note that some tapes have a weird format, in which these two marker bytes
**  might not be present, so if we do not find them, we cannot be sure that
**  this stuff is noise in that case.
*/
    if( ndx_prwt < 5 )
    {

/*
**  Glitch in the PRWT, ignore this stuff.
*/
        bit_len = 1;
        bit_limit = bit_len * 55 / 100;
    }
    else
    if( ndx_prwt < 10 )
    {

/*
**  Might be one of these weird formats, probably a couple of checksum bytes.
*/
        bit_len = sample_cnt_bit;
        bit_limit = bit_len * 55 / 100;
    }
    else
    {

/*
**  We should find the two marker bytes at the beginning of the record.
**  They consist of 10 alternating bits each, so we compute the sum of the
**  first 20 mark and space counts, which should be 20 bits.  If we divide
**  this sum by 20, we know the length of one bit.
*/
        ctl_1_cnt = sample_cnt_tbl[0].sample_cnt_space +
                    sample_cnt_tbl[1].sample_cnt_mark  +
                    sample_cnt_tbl[1].sample_cnt_space +
                    sample_cnt_tbl[2].sample_cnt_mark  +
                    sample_cnt_tbl[2].sample_cnt_space +
                    sample_cnt_tbl[3].sample_cnt_mark  +
                    sample_cnt_tbl[3].sample_cnt_space +
                    sample_cnt_tbl[4].sample_cnt_mark  +
                    sample_cnt_tbl[4].sample_cnt_space +
                    sample_cnt_tbl[5].sample_cnt_mark;
        ctl_2_cnt = sample_cnt_tbl[5].sample_cnt_space +
                    sample_cnt_tbl[6].sample_cnt_mark  +
                    sample_cnt_tbl[6].sample_cnt_space +
                    sample_cnt_tbl[7].sample_cnt_mark  +
                    sample_cnt_tbl[7].sample_cnt_space +
                    sample_cnt_tbl[8].sample_cnt_mark  +
                    sample_cnt_tbl[8].sample_cnt_space +
                    sample_cnt_tbl[9].sample_cnt_mark  +
                    sample_cnt_tbl[9].sample_cnt_space +
                    sample_cnt_tbl[10].sample_cnt_mark;

        bit_len = ( ctl_1_cnt + ctl_2_cnt ) / 20;

/*
**  If the bit length gets a strange value, the record probably did not
**  start with two marker bytes, we will assume the default baudrate.
*/
        if( ( bit_len <= sample_cnt_bit * 70 / 100 ) ||
            ( bit_len >= sample_cnt_bit * 120 / 100 ) )
            bit_len = sample_cnt_bit;
        bit_limit = bit_len * 55 / 100;
    }

    PRINT( ("At %.8ld bit length = %lu, baudrate = %lu.\n",
                cnt_total, bit_len, sample_rate / bit_len ) );

    if( ( bit_len > sample_cnt_bit * 70 / 100 ) &&
        ( bit_len < sample_cnt_bit * 120 / 100 ) )
    {
        fprintf( stderr,
                 "At %.8ld bit length = %lu samples, baudrate = %lu baud.\n",
                 cnt_total, bit_len, sample_rate / bit_len );

/*
**  Write out the data to the cassette file.
**  Put the length of the PRWT into the aux-bytes.
*/
        memcpy( cas_rec.cas_record_id, "data", 4 );
        cas_rec.cas_len_lo = 0;
        cas_rec.cas_len_hi = 0;
        cas_rec.cas_aux1 =  (( sample_cnt_tbl[0].sample_cnt_mark * 1000 ) / sample_rate ) % 256;
        cas_rec.cas_aux2 =  (( sample_cnt_tbl[0].sample_cnt_mark * 1000 ) / sample_rate ) / 256;

        data_ndx = 0;

        for( ndx = 0; ndx < ndx_prwt; )
        {

/*
**  A byte consists of 8 data bits, a startbit and a stopbit.
**  See how many bits are represented by the sample count.
*/
            byte = 0x00;
            cnt_sum = 0;
            PRINT( ("Offset %lu ndx %lu ", sample_cnt_tbl[ndx].sample_pos,
                    ndx ) );
            for( bit_cnt = 0; bit_cnt < 10; )
            {
                cnt = sample_cnt_tbl[ndx].sample_cnt_space;
                cnt_sum += cnt;
                PRINT( (" %lu ", cnt ) );
                while( cnt > bit_limit )
                {
                    if( cnt > bit_len )
                        cnt -= bit_len;
                    else
                        cnt = 0;
                    if( bit_cnt )           /* Ignore startbit */
                    {
                        byte = byte >> 1;   /* Make room for next bit */
                        byte &= 0x7F;       /* Add a zero bit         */
                        PRINT( ("0") );
                    }
                    bit_cnt++;
                    if( bit_cnt == 10 )     /* Quit when we hit the stopbit */
                        break;
                } /* End while processing space count */
                ndx++;
                cnt = sample_cnt_tbl[ndx].sample_cnt_mark;
                cnt_sum += cnt;
                PRINT( (" %lu ", cnt ) );
                while( cnt > bit_limit )
                {
                    if( cnt > bit_len )
                        cnt -= bit_len;
                    else
                        cnt = 0;
                    if( bit_cnt < 9 )       /* Ignore stopbit         */
                    {
                        byte = byte >> 1;   /* Make room for next bit */
                        byte |= 0x080;      /* Add a one bit          */
                        PRINT( ("1") );
                    }
                    bit_cnt++;
                    if( bit_cnt == 10 )     /* Quit when we hit the stopbit */
                        break;
                } /* End while processing mark count */
            } /* End for 10 bits */
            PRINT( ("\nByte %.2x sum %lu.\n", byte, cnt_sum ) );
            cas_rec.cas_data[data_ndx++] = byte;
            bytes++;
        } /* end for all pairs up to next PRWT */

        cas_rec.cas_len_hi = data_ndx / 256;
        cas_rec.cas_len_lo = data_ndx % 256;
        fwrite( &cas_rec, 1,
                cas_rec.cas_len_hi * 256 + cas_rec.cas_len_lo + 8, cas_file );

/*
**  Print the hex values.
**  Start out with the PRWT length and the record length.
*/
        fprintf( hex_file, "%.5lu %lu",
        ( sample_cnt_tbl[0].sample_cnt_mark * 1000 ) / sample_rate, bytes );
        checksum = 0;
        for( ndx = 0; ndx < data_ndx; ndx++ )
        {
            fprintf( hex_file, " %.2x", cas_rec.cas_data[ndx] );
            if( ndx < data_ndx - 1 )
            {
                checksum += cas_rec.cas_data[ndx];
                if( checksum > 0x0ff )  /* If carry */
                {
                    checksum &= 0x0ff;  /* Wrap around */
                    checksum++;         /* Add carry back in */
                }
             }
        } /* end for all bytes in the block */
        fprintf( hex_file, " %.2lx %s\n", checksum,
                 ( checksum == cas_rec.cas_data[data_ndx - 1] ) ? "ok" : "bad" );

        if( diagnostics )
        {
            if( checksum != cas_rec.cas_data[data_ndx - 1] )
                printf("\nBad checksum!\n");
            printf( "PRWT is %lu milliseconds.\n", sample_cnt_tbl[0].sample_cnt_mark / 44 );
        }

/*
**  Move the remaining portion of the buffer to the beginning of the buffer.
**  Cleaning up the table might have caused the PRWT to be not the last
**  element in the table, so take note of this when moving the table.
*/
        if( sample_tbl_ndx )
        {
            if( sample_tbl_ndx > ndx_prwt )
            {
                ndx = sample_tbl_ndx - ndx_prwt + 1;
                memcpy( &(sample_cnt_tbl[0]),
                        &(sample_cnt_tbl[ndx_prwt]),
                        sizeof(sample_cnt_blk) * ndx);
                sample_tbl_ndx = ndx - 1;
            }
            else
            {
                memcpy( &(sample_cnt_tbl[0]),
                        &(sample_cnt_tbl[sample_tbl_ndx]),
                        sizeof(sample_cnt_blk) );
                sample_tbl_ndx = 0;
            }
        }
    } /* end if baudrate acceptable */
    else
    {
        fprintf( stderr,
                 "At %.8ld skipping noise or bad data.\n",
                 cnt_total );

/*
**  Must have been noise, add it all and consider it prwt.
*/
        if( sample_tbl_ndx )
        {
            for( ndx = 0; ndx < ndx_prwt; ndx++ )
            {
                sample_cnt_tbl[ndx_prwt].sample_cnt_mark +=
                sample_cnt_tbl[ndx].sample_cnt_mark +
                sample_cnt_tbl[ndx].sample_cnt_space;
            }
            ndx = sample_tbl_ndx - ndx_prwt + 1;
            memcpy( &(sample_cnt_tbl[0]),
                    &(sample_cnt_tbl[ndx_prwt]),
                    sizeof(sample_cnt_blk) * ndx);
            sample_tbl_ndx = 0;
        }
    }

    PRINT( ("Leaving process_record() with %lu bytes processed.\n", bytes ) );

}

/*****************************************************************************
**  NAME:  replenish_buffer()
**
**  PURPOSE:
**      Keep the buffer filled with PCM data.
**
**  DESCRIPTION:
**      This function will read data from the wave file and store it in the
**      PCM buffer.  Data that was still left in the buffer is moved towards
**      the start of the buffer in order to make room for the new data.
**
**  INPUT:
**      Nothing.
**      Data is read from the wave file.
**
**  OUTPUT:
**      Data is stored in the buffer.
**      Buffer status is updated.
**      The function returns nothing.
**
*/

static void         replenish_buffer( void )
{
    uint32          bytes;                  /* Number of bytes read         */
    uint32          rest_len;               /* Remaining bytes in buffer    */
    ubyte           wav_rec[PCM_BUF_HALF];  /* Buffer for reading wave data */

/*
**  Replenish buffer.
**  Move the remaining portion of the buffer to the beginning of the buffer.
**  The buffer end points to just beyond the last byte of the buffer.
*/
    if( pcm_ndx && pcm_ndx_end )
    {
        if( pcm_ndx > pcm_ndx_end )
        {
            fprintf( stderr, "\nPanic: Beyond end of buffer.\n");
            getchar();
        }
        else
        {
            rest_len = pcm_ndx_end - pcm_ndx;
            memcpy( pcm, &pcm[pcm_ndx], rest_len );
            pcm_ndx = 0;
            pcm_ndx_end = rest_len;
        }
    }

/*
**  Get some more bytes and stuff them into the PCM buffer.
*/
    bytes = PCM_BUF_HALF;
    bytes = fread( (char *)wav_rec, (int)1, (int)bytes, wav_file );
    if( bytes )
    {
        memcpy( &pcm[pcm_ndx_end], wav_rec, bytes );
        pcm_ndx_end += bytes;
        pcm_bytes += bytes;
    }
}

/*****************************************************************************
**  NAME:  usage()
**
**  PURPOSE:
**      Display the command line format for the program.
**
**  DESCRIPTION:
**      This function will explain the usage of the program to the user.
**      The program name is taken from the first command line argument.
**
**  INPUT:
**      - The address of the command line, containing the program name.
**
**  OUTPUT:
**      The usage is displayed on the terminal.
**      The function returns nothing.
**
*/

static void         usage( cmd )
char * cmd;                         /* Program name                     */
{
    char * whoami;      /* For searching program name in command line   */
    char * name;        /* Pointer to actual program name in command    */
    int    len;         /* Length of program name                       */
    int    found_dot;   /* Nonzero if we found a dot in the name        */

/*
**  Get program name and print usage message.
**  The complete pathname including extension is part of the first
**  argument as passed by the operating system.
*/
    for( whoami = cmd, len = 0, found_dot = 0; *whoami; whoami++ )
    {
        if( *whoami == '.' )
        {
            found_dot = 1;
            continue;
        }
        if( *whoami == '\\' )   /* if this was part of the path, */
        {
            name = whoami + 1;  /* record position */
            len = 0;            /* then restart counting length */
            found_dot = 0;
            continue;
        }
        if( *whoami == ' ' )    /* end of name found            */
            break;
        if( found_dot )         /* skip .exe or .com stuff      */
            continue;
        len++;                  /* Increment program name length */
    }

/*
**  Let me explain...
*/
    fprintf(stderr, "\nUsage: %.*s [wavefile] [\"description\"] [/d] [/f]\n", len, name);
    fprintf(stderr, "to convert a .wav file to a .cas cassette image.\n\n");
    fprintf(stderr, "wavefile      an Atari classic tape sampled at 44.100.\n");
    fprintf(stderr, "description   descriptive text entered as a quoted string.\n\n");
    fprintf(stderr, "/d            to print diagnostic information.\n");
    fprintf(stderr, "/f            to write the FSK table file.\n");
    fprintf(stderr, "Refer to the documentation for more information.\n");

    return;
}

/*****************************************************************************
**  NAME:  wav2fsk()
**
**  PURPOSE:
**      Read data from the .wav file and convert it to levels recognized
**      as fsk mark and space values.
**      The number of samples within one half period is counted.  Based on
**      this, we can determine the frequency of the sound, and thus we can
**      distinguish between mark and space levels.  The output routine is
**      then called to print this number of samples.
**      The number of samples of this level are recorded for interpretation.
**      If we have a complete record in the buffer, we can try to interpret
**      the bits in the buffer.
**
**  DESCRIPTION:
**      This function will read the data and process it.
**
**  INPUT:
**      Nothing.
**
**  OUTPUT:
**      Prints results.
**      Returns SUCCESS if data converted successfully.
**      Returns FAILURE if some error occurred.
**
*/

static uint32       wav2fsk( void )
{
    uint16          level;                  /* Interpreted level of signal  */

    cnt_total = 0;

/*
**  Set pointers to point to the end of the buffer.
*/
    pcm_cnt = 0;
    pcm_ndx = 0;
    pcm_ndx_end = 0;

/*
**  Initialize sample count table.
*/
    sample_tbl_ndx = 0;
    sample_tbl_level = FSK_MARK;
    sample_cnt_tbl[0].sample_pos = 0;
    sample_cnt_tbl[0].sample_cnt_mark = 0;

/*
**  Load the buffer.
*/
    replenish_buffer();
    replenish_buffer();

    if( pcm[1] > pcm[0] )
    {
        pcm_level_previous = 0;
        pcm_level_rising = TRUE;
    }
    else
    {
        pcm_level_previous = 255;
        pcm_level_rising = FALSE;
    }

    period_sample_cnt();
    period_sample_cnt();
    while( cnt_cur )
    {
        if( ( cnt_cur >= sample_cnt_low ) && ( cnt_cur <= sample_cnt_high ) )
            level = FSK_SPACE;
        else
            level = FSK_MARK;

        process_cnt( cnt_val, level );

        period_sample_cnt();
    } /* end while */
    process_record();
    printf("\n");

    return( SUCCESS );
}

/*****************************************************************************
==  EXPORTED FUNCTIONS
*****************************************************************************/

/*****************************************************************************
**  NAME:  MAIN()
**
**  PURPOSE:
**      An entry point for testing or running this utility.
**
**  DESCRIPTION:
**      Prompt for the file to open and then go process it.
**
**  INPUT:
**      argc and argv.
**
**  OUTPUT:
**      Returns an int as it should.
**
*/
int                 main( argc, argv )
int                 argc;               /* Command line argument count  */
char              * argv[];             /* Command line argument ptrs   */
{
    ubyte           answer;                 /* Response to yes/no question  */
    uint32          arg_ndx;                /* Argument number index        */
    uint32          arg_no;                 /* Argument number              */
    uint32          wrk_ndx;                /* Work index                   */
    ubyte           desc[80];               /* Description of cassette tape */
    uint16          desc_len;               /* Length of the description    */
    bool            end_of_str;             /* Null terminator seen?        */
    ubyte           input_path[PATH_LEN];   /* Input wave file spec         */
    ubyte           fsk_path[PATH_LEN];     /* Output fsk file spec         */
    ubyte           hex_path[PATH_LEN];     /* Output hex file spec         */
    ubyte           cas_path[PATH_LEN];     /* Output cas file spec         */
    ubyte           buf[BUF_LEN];           /* Buffer string                */
    uint32          stat;                   /* Status from function         */
    ubyte           proceed;                /* Proceed with conversion      */

/*
**  Allocate buffer space for the sample count table.
*/
    sample_cnt_tbl = (sample_cnt_blk *)
                     malloc((unsigned long)SAMPLE_CNT_TBL_LEN *
                            sizeof( sample_cnt_blk ) );


    if( !sample_cnt_tbl )
    {
        fprintf( stderr, "\nCannot allocate buffer, insufficient memory.\n" );
        exit( 255 );
    }

/*
**  Process command line arguments.
**  We do not treat the options switch as an argument.  It may be placed
**  anywhere on the command line.  So we have to count the arguments ourselves
**  so that we know what argument we are processing.
*/
    arg_no = 0;
    diagnostics = FALSE;
    fsk = FALSE;
    desc_len = 0;

    for( arg_ndx = 1; arg_ndx < argc; arg_ndx++ )
    {

/*
**  If we encounter the options switch, process the options.
**  The options must start with a slash.
*/
        if( argv[arg_ndx][0] == '/' )
        {

            for( wrk_ndx = 0; argv[arg_ndx][wrk_ndx]; wrk_ndx++ )
            {

/*
**  If the user is confused, seeking help, she/he should read the * manual.
**  We can give them a hint though.
*/
                if( argv[arg_ndx][wrk_ndx] == '?' )
                {
                    usage( argv[0] );
                    exit( 0 );
                }

/*
**  The /d option selects the diagnostics output.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'D' )
                {
                    diagnostics = TRUE;
                    continue;
                }

/*
**  The /f option selects the fsk table output.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'F' )
                {
                    fsk = TRUE;
                    continue;
                }

/*
**  Ignore other options.
*/
                continue;
            } /* end for all characters after options switch */

/*
**  No further processing for the options switches.
*/
            continue;
        } /* end if options switch */
        arg_no++;

/*
**  First argument is the file spec.
*/
        if( arg_no == 1 )
        {
            for ( wrk_ndx = 0, end_of_str = FALSE;
                wrk_ndx < PATH_LEN; wrk_ndx++ )
            {
                if ( argv[arg_ndx][wrk_ndx] == '\0' ) /* End of argument string?            */
                    end_of_str = TRUE;
                if ( end_of_str )
                    input_path[wrk_ndx] = '\0';
                else
                    input_path[wrk_ndx] = toupper( argv[arg_ndx][wrk_ndx] );
            }

            wav_file = fopen( (char *)input_path, "rb" );
            if( wav_file == NULL )
            {
                fprintf(stderr, "Cannot open wave file %s\n", input_path);
                exit( 255 );
            }
        }

/*
**  Optionally, the description can be entered as the second command line
**  argument.  This should be done as a quoted string, since that is the
**  way command line arguments work.
*/
        if( arg_no ==  2 )
        {
            desc_len = STRLEN( argv[arg_ndx] );
            if( desc_len > 80 )
                desc_len = 80;
            memcpy( desc, argv[arg_ndx], desc_len );
        } /* end if description argument */
    } /* end for all command line arguments */

/*
**  If there is no filename on the command line, ask for it.
*/
    if( arg_no == 0 )
    {

/*
**  Open hailing frequencies.
**  No command line arguments, so ask what it is we have to do.
*/
        printf( "\n\nClassic Atari cassette tape recovery version April 20, 1997\n" );
        printf( "\n\nCopyright 1996, 1997 by Ernest R. Schreurs\n" );
        printf( "\n\nAll rights reserved\n" );


        while( TRUE )                       /* until terminated by control Z*/
        {
            printf( "\nEnter ^Z or hit Control Z to terminate\n" );

            do                              /* until .wav file entered    */
            {
                printf("\nEnter .wav file to be converted : ");
                GET_BUF();

                for ( wrk_ndx = 0, end_of_str = FALSE;
                    wrk_ndx < PATH_LEN; wrk_ndx++ )
                {
                    if ( wrk_ndx < BUF_LEN )
                    {
                        if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                            end_of_str = TRUE;
                        if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                            end_of_str = TRUE;
                        if ( end_of_str )
                            input_path[wrk_ndx] = '\0';
                        else
                            input_path[wrk_ndx] = toupper( buf[wrk_ndx] );
                    }
                }
            } while ( input_path[0] == ' ' );

            do                              /* until answer is Y or N       */
            {
                printf("\nLoading file %s\n", input_path);
                printf("\nIs this correct Y)es or N)o : ");
                GET_BUF();
                proceed = toupper( buf[0] );

/*
**  If blank, default is correct
*/
                if ( proceed == '\n' )
                    proceed = 'Y';

            } while ( proceed != 'Y' && proceed != 'N' );

            if ( proceed == 'N' )
                continue;

            do                              /* until answer is Y or N       */
            {
                printf("\nPrint diagnostic data Y)es or N)o : ");
                GET_BUF();
                answer = toupper( buf[0] );

/*
**  If blank, default is correct
*/
                if ( answer == '\n' )
                    answer = 'Y';

            } while ( answer != 'Y' && answer != 'N' );

            diagnostics = ( answer == 'Y' ) ? TRUE : FALSE;

            printf( "\nLoading data from wave file.\n" );

            wav_file = fopen( (char *)input_path, "rb" );
            if( wav_file == NULL )
            {
                fprintf(stderr, "Cannot open wave file\n");
                continue;
            }
            break;
        } /* end while need a valid filename */

        printf("\nEnter description : ");
        GET_BUF();

        for ( wrk_ndx = 0, desc_len = 0, end_of_str = FALSE;
              wrk_ndx < 80; wrk_ndx++ )
        {
            if ( wrk_ndx < BUF_LEN )
            {
                if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                    end_of_str = TRUE;
                if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                    end_of_str = TRUE;
            }
            if ( end_of_str )
                desc[wrk_ndx] = '\0';
            else
            {
                desc[wrk_ndx] = buf[wrk_ndx];
                desc_len++;
            }
        }
    } /* end else if command line arguments */

/*
**  Process the header of the .wav file.
*/
    stat = process_header();
    if( stat == FAILURE )
        exit( 255 );

    PRINT( ("File : %s\n", input_path ) );

    memcpy( fsk_path, input_path, PATH_LEN );
    memcpy( &(fsk_path[STRLEN(fsk_path) - 3]), "fsk", 3 );
    memcpy( hex_path, input_path, PATH_LEN );
    memcpy( &(hex_path[STRLEN(hex_path) - 3]), "hex", 3 );
    memcpy( cas_path, input_path, PATH_LEN );
    memcpy( &(cas_path[STRLEN(cas_path) - 3]), "cas", 3 );

    if( fsk )
        fsk_file = fopen( (char *)fsk_path, "w" );
    hex_file = fopen( (char *)hex_path, "w" );
    cas_file = fopen( (char *)cas_path, "wb" );

/*
**  Write the header to the cassette file.  The header makes it possible
**  to identify the file as a .cas file.  Also, the description is stored
**  in this header record.
*/
    memcpy( cas_rec.cas_record_id, "FUJI", 4 );
    cas_rec.cas_len_lo = desc_len;
    cas_rec.cas_len_hi = desc_len / 256;
    cas_rec.cas_aux1 = 0x00;
    cas_rec.cas_aux2 = 0x00;
    if( desc_len )
        memcpy( cas_rec.cas_data, desc, desc_len );
    fwrite( &cas_rec, 1, cas_rec.cas_len_hi * 256 + cas_rec.cas_len_lo + 8, cas_file );

/*
**  Write out default baud rate to the cassette file.
*/
    memcpy( cas_rec.cas_record_id, "baud", 4 );
    cas_rec.cas_len_lo = 0;
    cas_rec.cas_len_hi = 0;
    cas_rec.cas_aux1 = 600 % 256;
    cas_rec.cas_aux2 = 600 / 256;
    fwrite( &cas_rec, 1, 8, cas_file );


    if( fsk )
        fprintf( fsk_file, "%.*s\n", desc_len, desc );
    fprintf( hex_file, "%.*s\n", desc_len, desc );
    fsk_print_first = TRUE;

/*
**      Process the data portion of the .wav file.
*/
    stat = wav2fsk();

    cleanup();

    fprintf(stderr, "\nDone processing.\n");

    return 0;
}

/*****************************************************************************
**  MODIFICATION HISTORY
**
**  DATE        BY   Description
**  ----------  ---  ---------------------------------------------------------
**  1996/11/30  ERS  Project start
**  1997/05/01  ERS  Release 01.00
**
*****************************************************************************/
