/*****************************************************************************
**
**  Copyright 1998, 1999 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  "CAS2WAV.C"
/*****************************************************************************
**  NAME: CAS2WAV.C
**
**  Author            : Ernest R. Schreurs
**  Date              : January 3, 1999
**  Release           : 01.00
**
**  Description       : This program will convert a .cas cassette image file
**                      to a .wav file.
**
**  Amiga port by Wojciech Pasiecznik (voydial@wp.pl), Feb. 14, 2002.
**  Few additional changes:
**
** - Control Z check is replaced by the Control C (including comments),
** - title is a little changed ;),
** - "fltenv.h" include file is disabled now.
**
*****************************************************************************/

/*****************************************************************************
==  INCLUDE FILES
*****************************************************************************/
#include <stdio.h>              /* For printf() and gets()              */
#include <ctype.h>              /* For isalnum() and toupper()          */
/* #include <fltenv.h>          /* For sin() exceptions                 */
#include <math.h>               /* For sin()                            */
#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              */
#define ZERO_LEVEL          128             /* Zero level for wav sample    */

/*
**  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      */
#define MARK_TABLE_LEN      44100L          /* Length of mark tone table    */
#define SPACE_TABLE_LEN     44100L          /* Length of space tone table   */

/*****************************************************************************
==  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 C or
**  inputting the string ^C to indicate intention of
**  terminating the program.
*/
#define GET_BUF()                                                           \
{                                                                           \
    if ( FGETS( buf, BUF_LEN, stdin )  ==  NULL )                           \
    {                                                                       \
        printf( "Terminated by ^C\n" );                                     \
        exit(0);                                                            \
    }                                                                       \
    if ( memcmp( buf, "^C", 2 ) == 0 || memcmp( buf, "^c", 2 ) == 0  )      \
    {                                                                       \
        printf( "Terminated by ^C\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;

/*****************************************************************************
==  IMPORTED VARIABLES
*****************************************************************************/
/*****************************************************************************
==  LOCAL ( HIDDEN ) VARIABLES
*****************************************************************************/
static FILE *   cas_file;               /* Cassette image file              */
static FILE *   wav_file;               /* Wave file                        */

static cas_blk  cas_rec;                /* The cassette record buffer       */
static uint32   cas_len;                /* Length of cassette data          */

static uint32   baudrate;               /* Baudrate                         */
static bool     baudrate_fixed;         /* Fixed baudrate entered           */
static uint32   bitlen;                 /* Number of samples in one bit     */
static uint32   bit_stretch;            /* Number of samples for stretch    */
static uint32   bytelen;                /* Number of samples in one byte    */
static bool     diagnostics;            /* Print diagnostic data            */
static bool     header_written;         /* Did we write out a header yet    */
static bool     format_pure;            /* Format is pure sine waves        */
static bool     format_sine;            /* Format is sine waves             */
static bool     format_square;          /* Format is square waves           */
static bool     zero_transition;        /* Do a transition at zero level    */
static uint32   test_tape;              /* Generate test tape only          */
static uint32   mark_tone;              /* Frequency of mark tone           */
static uint32   space_tone;             /* Frequency of space tone          */
static uint32   leader;                 /* Fixed length of leader           */
static uint32   irg;                    /* Fixed length of Inter Record Gap */
static uint32   pos;                    /* Number of bytes in wav file      */
static uint32   pos_chunk_size;         /* Position in wav file for length  */
static uint32   pos_file_size;          /* Position in wav file for length  */
static ubyte    number[4];              /* Buffer for numbers               */
static ubyte *  mark;                   /* Buffer with mark tone generator  */
static ubyte *  space;                  /* Buffer with space tone generator */
static ubyte *  ptr;                    /* Pointer to tone generator        */
static uint32   table_len;              /* Length of generator table        */
static uint32   prev_bitvalue;          /* Last bit value written           */
static uint32   recno;                  /* Record number                    */

/*****************************************************************************
==  EXPORTED VARIABLES
*****************************************************************************/
/*****************************************************************************
==  IMPORTED FUNCTIONS
*****************************************************************************/
/*****************************************************************************
==  LOCAL ( HIDDEN ) FUNCTIONS
*****************************************************************************/
static void     cleanup( void );
static uint32   process_header( void );
static void     process_record( void );
static uint32   read_record( void );
static void     usage( char * cmd );
static void     write_wav( char * buffer, uint32 buflen );
static void     write_wav_bit( uint32 value, uint32 samples );
static void     write_wav_number( uint32 value, uint32 buflen );

/*****************************************************************************
==  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( cas_file )
    {
        fclose( cas_file );
    }

    if( mark )
        free( (void *)mark );

    if( space )
        free( (void *)space );
    return;
}

/*****************************************************************************
**  NAME:  process_header()
**
**  PURPOSE:
**      Process the data from the header of the .cas file and store
**      relevant information.  This will verify that the file truly is
**      a cassette image file.
**
**  DESCRIPTION:
**      This function will read the relevant data about the .cas file
**      and verify it.
**
**  INPUT:
**      Nothing.
**      Data is read from the cassette image file.
**
**  OUTPUT:
**      Prints results.
**      Stores data related to the contents of the cassette image file.
**      Returns SUCCESS if header was processed successfully.
**      Returns FAILURE if some error occurred.
**
*/

static uint32       process_header( void )
{
    uint32          bytes;                  /* Number of bytes read         */

/*
**  Cassette image files usually look like this
**
**  char[4] = "FUJI", two chars record size lo/hi, two chars null
**  char[4] = "baud", two chars null, two chars baudrate lo/hi
**  char[4] = "data", two chars record size lo/hi, two chars PRWT lo/hi
**  See below for more details.
*/

/*
**  .cas files should begin with FUJI, followed by the size of the description.
*/
    bytes = fread( (char *)&cas_rec, (int)1, (int)4, cas_file );
    if( ( bytes < 4 ) || memcmp( cas_rec.cas_record_id, "FUJI", 4 ) )
    {
        fprintf(stderr, "\nThis is not a valid .cas file, it does not begin with \"FUJI\".\n");
        return( FAILURE );
    }

/*
**  Get the remaining four bytes of the record header.
*/
    bytes = fread( ((char *)&cas_rec)+4, (int)1, (int)4, cas_file );
    if( bytes < 4 )
    {
        fprintf(stderr, "\nThis is not a valid .cas file, description length missing.\n");
        return( FAILURE );
    }

/*
**  This looks like what we wanted, so we will call this success.
**  Position the file at the beginning again.
*/
    fseek( cas_file, 0L, SEEK_SET );    /* Go back to beginning of file */
    return( SUCCESS );
}

/*****************************************************************************
**  NAME:  process_record()
**
**  PURPOSE:
**      There is data in the cassette record buffer.  This must now be
**      converted to the selected format.
**
**  DESCRIPTION:
**      This function will take data from the record buffer and convert it
**      to FSK bits.  The bits are coded as mark and space tones.
**      These tones are output as wave data.
**
**  INPUT:
**      Nothing.
**      Data is taken from the cassette record buffer.
**
**  OUTPUT:
**      Data is output to the file.
**      The function returns nothing.
**
*/

static void         process_record( void )
{
    uint32          bit;                    /* Number of bits processed     */
    uint32          bitvalue;               /* Current bit level counting   */
    ubyte           byte;                   /* Byte decoded from fsk data   */
    uint32          bytes;                  /* Number of bytes read         */
    uint32          change;                 /* Bit change index             */
    uint32          changes;                /* Bit changes index            */
/*  uint32          msecs;                  /* Number of milli-seconds      */
    uint32          prwt;                   /* Length of the PRWT           */
    uint32          bits[10];               /* Number of bits               */
    uint32          remainder;              /* Left over samples            */
    uint32          samples[10];            /* Number of samples per bit    */
    uint32          total;                  /* Total sample count           */
    uint32          total_bits;             /* Total number of bits         */

/*
**  Currently, we only have three types of record:
**  FUJI  A header/description record.
**  baud  A record telling us the baudrate.
**  data  A record with cassette data.
*/

/*
**  If we find a header, and it is the first one, write the header
**  for the wave file.
*/
    if( memcmp( cas_rec.cas_record_id, "FUJI", 4 ) == 0 )
    {
        PRINT( ("\nDescription \"%.*s\".\n", (int)cas_len, cas_rec.cas_data) );
        if( !header_written )
        {
            write_wav( (char *)"RIFF", (uint32)4L );
            pos_file_size = pos;
            write_wav_number( (uint32)0L, (uint32)4L );
            header_written = TRUE;
        }
        write_wav( (char *)"WAVE", (uint32)4L );

        write_wav( (char *)"fmt ", (uint32)4L );
        write_wav_number( (uint32)16L, (uint32)4L ); /* Header size */

        write_wav_number( (uint32)1L, (uint32)2L );  /* fmt tag 1 */
        write_wav_number( (uint32)1L, (uint32)2L );  /* channels 1 */
        write_wav_number( (uint32)44100L, (uint32)4L );  /* sample rate 44100 */
        write_wav_number( (uint32)44100L, (uint32)4L );  /* Bytes per second */
        write_wav_number( (uint32)1L, (uint32)2L );  /* Buffer alignment */

        write_wav_number( (uint32)8L, (uint32)2L );  /* bits per sample */

        write_wav( (char *)"data", (uint32)4L );
        pos_chunk_size = pos;
        write_wav_number( (uint32)0L, (uint32)4L );

        return;
    }

/*
**  If this is a baudrate record, change the baud rate, unless
**  the user selected a fixed baudrate.
*/
    if( memcmp( cas_rec.cas_record_id, "baud", 4 ) == 0 )
    {
        if( !baudrate_fixed )
        {
            baudrate = (((uint32)cas_rec.cas_aux2) << 8 ) + cas_rec.cas_aux1;
            bytelen = ( 44100L * 10 ) / baudrate;
            bitlen = 44100L / baudrate;
        }
        PRINT( ("\nBaudrate set to %lu.\n", baudrate) );
        return;
    }

/*
**  If this is a plain old data record, encode the bits.
*/
    if( memcmp( cas_rec.cas_record_id, "data", 4 ) == 0 )
    {
        prwt = (((uint32)cas_rec.cas_aux2) << 8 ) + cas_rec.cas_aux1;

/*
**  If there is a fixed leader, and we did not write a leader yet,
**  set the length of the leader.
**  If this is a normal irg or other gap, any gap less than 3 seconds
**  is considered an irg and if specified, the fixed irg is used.
*/
        if( leader )
        {
            prwt = leader;
            leader = 0;
        }
        else
        {
            if( irg )
            {
                if( prwt < 3000 )
                    prwt = irg;
            }
        }

/*
**  Each record starts out with the PRWT.
**  Then we write out all the bytes in the cassette record.
**  The PRWT is measured in milli-seconds.  At a sample rate of
**  44,100, this means the number of samples is 44.1 times the prwt value.
*/
        write_wav_bit( FSK_PRWT, prwt * 44 + prwt / 10 );
        recno++;
        PRINT( ("\nRecord %lu at offset %lu PRWT = %lu with %lu data bytes.",
                 recno, pos - pos_chunk_size - 4, prwt, cas_len ) );

/*
**  Now process the data record byte by byte.
*/
        for( bytes = 0; bytes < cas_len; bytes++ )
        {

/*
**  Since this is FSK, we accumulate the sample counts of the bits that
**  are the same value.  This way, we can get a more precise baudrate.
**  Encode and sum up like bits.  Begin with a startbit, then the databits,
**  and finally the stopbit.
**  We need ten bits to encode one byte (startbit and stopbit included)
**  but since we are using integers here, we will divide the bytelength
**  by ten after summing up the bits, for improved accuracy.
*/
            bitvalue = FSK_STARTBIT;
            samples[0] = bytelen;
            bits[0] = 1;
            changes = 0;
            byte = cas_rec.cas_data[bytes];

/*
**  Convert each byte to a string of 8 bits.
**  Least significant bit is encoded first.
**  If the bitvalue changes, start the new count,
**  otherwise add the length of a bit to the sum.
*/
            for( bit = 0; bit < 8; bit++ )
            {
                if( ( byte & 0x01 ) != bitvalue )
                {
                    bitvalue = byte & 0x01;
                    changes++;
                    samples[changes] = bytelen;
                    bits[changes] = 1;
                }
                else
                {
                    samples[changes] += bytelen;
                    bits[changes]++;
                }
                byte = byte >> 1;
            }

/*
**  The last bit is the stop bit.  Add it to the length of the last bit(s)
**  if they were mark, otherwise, make a final new count.
*/
            if( bitvalue == FSK_STOPBIT )
            {
                samples[changes] += bytelen;
                bits[changes]++;
            }
            else
            {
                changes++;
                samples[changes] = bytelen;
                bits[changes] = 1;
            }

/*
**  Now that we have the bits encoded, we must still divide the bytelength
**  by ten, thus we must divide our sums by ten.  We must make sure that
**  the baudrate is correct, so the total sum of the samples for all bits
**  must be equal to the bytelength.  Distribute the remaining samples,
**  caused by inaccuracy in the division, by checking the sum at every change.
**  Keep a running total, and compute where we should be at, in order
**  to compensate for truncation that occurs during the division.
*/
            total = 0;
            total_bits = 0;
            for( change = 0; change <= changes; change++ )
            {
                samples[change] /= 10;
                total += samples[change];
                total_bits += bits[change];
                remainder = (( total_bits * bytelen ) / 10 ) - total;
                samples[change] += remainder;
                total += remainder;
            }

/*
**  Now that we have the bits encoded, stretch the space bits.
**  The table starts with the startbit, which is a space.  Steal the
**  samples from the following mark bit(s).
**  Even table entries are space, odd entries are mark, since they alternate.
*/
            for( change = 0; change < changes; change++ )
            {
                samples[change++] += bit_stretch;
                samples[change] -= bit_stretch;
            }

/*
**  Now output this stuff.
**  There are at least two changes, since we have a start bit and
**  a stop bit.  The bits may alternate several times, but we are
**  certain that the last one will be a stop bit, which is a mark.
**  Thus, process bits in pairs, alternating between space and mark.
*/
            for( change = 0; change < changes; change++ )
            {
                write_wav_bit( FSK_SPACE, samples[change++] );
                write_wav_bit( FSK_MARK, samples[change] );
            }
        }
        return;
    }

    PRINT( ("\nIn process_record() unknown record type %.4s %lu bytes data\n", &cas_rec, cas_len) );
    return;
}

/*****************************************************************************
**  NAME:  read_record()
**
**  PURPOSE:
**      Read a record into the cassette buffer.
**
**  DESCRIPTION:
**      This function will read data from the cas file and store it in the
**      cassette buffer.  One complete record is read into the buffer.
**
**  INPUT:
**      Nothing.
**      Data is read from the cas file.
**
**  OUTPUT:
**      Data is stored in the buffer.
**      Buffer status is updated.
**      Returns SUCCESS if a record was read successfully.
**      Returns FAILURE if some error occurred.
**
*/

static uint32       read_record( void )
{
    uint32          bytes;                  /* Number of bytes read         */

/*
**  First read the header bytes, because we need to know the length of the
**  record.  If we cannot read any bytes, this must be the end of file.
*/
    bytes = fread( (char *)&cas_rec, (int)1, (int)8, cas_file );
    if( bytes == 0 )
        return( FAILURE );

/*
**  Found header bytes, compute the record length from the header info.
**  Then read the data portion of the record, if any.
*/
    if( bytes != 8 )
    {
        fprintf(stderr, "\nThis is not a valid .cas file, record header damaged.\n");
        return( FAILURE );
    }
    cas_len = (((uint32)cas_rec.cas_len_hi) << 8 ) + cas_rec.cas_len_lo;
    if( cas_len )
    {
        bytes = fread( (char *)cas_rec.cas_data, (int)1, (int)cas_len, cas_file );
        if( bytes != cas_len )
        {
            fprintf(stderr, "\nThis is not a valid .cas file, record damaged.\n");
            return( FAILURE );
        }
    }
    return( SUCCESS );
}

/*****************************************************************************
**  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 [cassette file] [/d] [/w=x] [/t=nnnn] [/m=nnnn] [/s=nnnn]\n", len, name );
    fprintf(stderr, "                               [/b=nnnn] [/l=nnnn] [/i=nnnn]\n");
    fprintf(stderr, "to convert a .cas cassette image file to a .wav file.\n\n");
    fprintf(stderr, "cassette file an Atari classic tape image file.\n");
    fprintf(stderr, "/d            to print diagnostic information.\n");
    fprintf(stderr, "/w=x          to select the waveform of the tone used,\n");
    fprintf(stderr, "              where x is s for sine waves, b for block waves, p for pure tones.\n");
    fprintf(stderr, "/z            to select transition at zero level.\n");
    fprintf(stderr, "/t=nnnn       to generate a test tape only,\n");
    fprintf(stderr, "              where nnnn is the duration in milli-seconds.\n");
    fprintf(stderr, "/m=nnnn       frequency of mark tone in Hertz,\n");
    fprintf(stderr, "              where nnnn is a number around 5327.\n");
    fprintf(stderr, "/s=nnnn       frequency of space tone in Hertz,\n");
    fprintf(stderr, "              where nnnn is a number around 3995.\n");
    fprintf(stderr, "/b=nnnn       fixed baudrate to use,\n");
    fprintf(stderr, "              where nnnn is a number around 600, from 425 to 875.\n");
    fprintf(stderr, "/l=nnnn       fixed length of leader in milli-seconds,\n");
    fprintf(stderr, "              where nnnn is a number around 20000.\n");
    fprintf(stderr, "/i=nnnn       fixed length of Inter Record Gap in milli-seconds,\n");
    fprintf(stderr, "              where nnnn is a number around 250.\n");
    fprintf(stderr, "Refer to the documentation for more information.\n");

    return;
}

/*****************************************************************************
**  NAME:  write_test_tape()
**
**  PURPOSE:
**      Write data for a test tape to the wav file.
**
**  DESCRIPTION:
**      This function will write test data to the wav file.
**      A test tape consists of only mark and space bits in some test pattern.
**      Use an oscilloscope to view the output of the cassette unit.
**
**  INPUT:
**      - The file pointers and paths are used.
**
**  OUTPUT:
**      The data is written to the wav file.
**      The function returns nothing.
**
*/

static void         write_test_tape( void )
{
    uint32          sample_count;           /* Count of samples test tape   */
    uint32          samples;                /* Number of samples test tape  */

/*
**  We use a fixed filename for test tapes.
*/
    fprintf(stderr, "\nProcessing test tape, please wait!\n");
    wav_file = fopen( (char *)"testtape.wav", "wb" );
    if( wav_file == NULL )
    {
        fprintf(stderr, "\nCannot open testtape.wav file!\n");
        cleanup();
        exit( 255 );
    }

    if( !header_written )
    {
        write_wav( (char *)"RIFF", (uint32)4L );
        pos_file_size = pos;
        write_wav_number( (uint32)0L, (uint32)4L );
        header_written = TRUE;
    }
    write_wav( (char *)"WAVE", (uint32)4L );

    write_wav( (char *)"fmt ", (uint32)4L );
    write_wav_number( (uint32)16L, (uint32)4L ); /* Header size */

    write_wav_number( (uint32)1L, (uint32)2L );  /* fmt tag 1 */
    write_wav_number( (uint32)1L, (uint32)2L );  /* channels 1 */
    write_wav_number( (uint32)44100L, (uint32)4L );  /* sample rate 44100 */
    write_wav_number( (uint32)44100L, (uint32)4L );  /* Bytes per second */
    write_wav_number( (uint32)1L, (uint32)2L );  /* Buffer alignment */

    write_wav_number( (uint32)8L, (uint32)2L );  /* bits per sample */

    write_wav( (char *)"data", (uint32)4L );
    pos_chunk_size = pos;
    write_wav_number( (uint32)0L, (uint32)4L );

/*
**  Compute the number of samples to generate.
**  Multiply the number of milli-seconds by the sample rate.
**  The sample rate is per second, so divide by 1000.
*/
    samples = ( test_tape * 441 ) / 10;
    sample_count = 0;

/*
**  Generate the test pattern.
*/
    while( sample_count < samples )
    {

/*
**  Alternate mark and space bits.
*/
#if 0
        write_wav_bit( FSK_MARK, bitlen );
        sample_count += bitlen;
        write_wav_bit( FSK_SPACE, bitlen );
        sample_count += bitlen;
#endif

/*
**  A small piece of silence.
*/
#if 0
        for( byte = 0; byte < 881; byte++ )
        {
            write_wav( "\200", 1L );
            sample_count++;
        }
#endif

/*
**  Large piece of mark, and alternating space and mark,
**  with elongated space bits.
*/
#if 0
        write_wav_bit( FSK_MARK, bitlen * 3 );
        sample_count += bitlen * 3;
        ptr = mark;
        write_wav_bit( FSK_MARK, bitlen * 3 );
        sample_count += bitlen * 3;
        ptr = mark;
        write_wav_bit( FSK_SPACE, bitlen );
        sample_count += bitlen;
        ptr = mark;
        write_wav_bit( FSK_MARK, bitlen - 8);
        sample_count += bitlen;
        ptr = mark;
        write_wav_bit( FSK_SPACE, bitlen + 8);
        sample_count += bitlen;
        ptr = mark;
        write_wav_bit( FSK_MARK, bitlen - 8);
        sample_count += bitlen;
        ptr = mark;
        write_wav_bit( FSK_SPACE, bitlen + 8);
        sample_count += bitlen;
        ptr = mark;
        write_wav_bit( FSK_MARK, bitlen - 8);
        sample_count += bitlen;
#endif

/*
**  Large piece of mark, and alternating space and mark,
**  with abrupt change over.
*/
#if 0
        ptr = mark;
        write_wav( (char *)&(mark[0]), 414 );
        sample_count += 414;
        write_wav( (char *)&(space[414]), 73 );
        sample_count += 73;
        write_wav( (char *)&(mark[487]), 73 );
        sample_count += 73;
        write_wav( (char *)&(space[560]), 73 );
        sample_count += 73;
        write_wav( (char *)&(mark[633]), 73 );
        sample_count += 73;
        write_wav( (char *)&(space[706]), 73 );
        sample_count += 73;
        write_wav( (char *)&(mark[779]), 73 );
        sample_count += 73;
#endif

/*
**  Large piece of mark, and alternating space and mark,
**  with normal change over.
*/
#if 1
        ptr = mark;
        table_len = MARK_TABLE_LEN;
        write_wav_bit( FSK_MARK, bitlen * 3 );
        sample_count += bitlen * 3;
        write_wav_bit( FSK_MARK, bitlen * 3 );
        sample_count += bitlen * 3;
        write_wav_bit( FSK_SPACE, bitlen );
        sample_count += bitlen;
        write_wav_bit( FSK_MARK, bitlen );
        sample_count += bitlen;
        write_wav_bit( FSK_SPACE, bitlen );
        sample_count += bitlen;
        write_wav_bit( FSK_MARK, bitlen );
        sample_count += bitlen;
        write_wav_bit( FSK_SPACE, bitlen );
        sample_count += bitlen;
        write_wav_bit( FSK_MARK, bitlen );
        sample_count += bitlen;
#endif

/*
**  Large piece of mark, and alternating space and mark,
**  with normal change over and elongated space bits.
*/
#if 0
        ptr = mark;
        table_len = MARK_TABLE_LEN;
        write_wav_bit( FSK_MARK, bitlen * 3 );
        sample_count += bitlen * 3;
        write_wav_bit( FSK_MARK, bitlen * 3 );
        sample_count += bitlen * 3;
        write_wav_bit( FSK_SPACE, bitlen + 9 );
        sample_count += bitlen + 9;
        write_wav_bit( FSK_MARK, bitlen - 9);
        sample_count += bitlen - 9;
        write_wav_bit( FSK_SPACE, bitlen + 9);
        sample_count += bitlen + 9;
        write_wav_bit( FSK_MARK, bitlen - 9);
        sample_count += bitlen - 9;
        write_wav_bit( FSK_SPACE, bitlen + 9);
        sample_count += bitlen + 9;
        write_wav_bit( FSK_MARK, bitlen );
        sample_count += bitlen;
#endif
    }

    return;
}

/*****************************************************************************
**  NAME:  write_wav()
**
**  PURPOSE:
**      Write data to the wav file.
**
**  DESCRIPTION:
**      This function will write the specified buffer to the wav file.
**
**  INPUT:
**      - The address of the data to be written.
**      - The amount of data to be written.
**
**  OUTPUT:
**      The data is written to the wav file.
**      The function returns nothing.
**
*/

static void         write_wav( buffer, buflen )
char * buffer;                      /* Address of buffer to be written  */
uint32 buflen;                      /* Number of bytes to be written    */
{
    uint32 bytes;       /* Number of bytes actually written             */

    bytes = fwrite( buffer, (int)1, (int)buflen, wav_file );
    if( bytes != buflen )
    {
        fprintf(stderr, "\nWrite error on wav file.\n");
        cleanup();
        exit( 255 );
    }
    pos += bytes;
    return;
}

/*****************************************************************************
**  NAME:  write_wav_bit()
**
**  PURPOSE:
**      Write a bit or a prwt to the wav file.
**
**  DESCRIPTION:
**      This function will write the specified number of samples to the wav
**      file, representing the selected bit value.
**      The way we make the transition from mark to space or from space to
**      mark depends on the selected format of the waves.
**
**  INPUT:
**      - The bit value to be represented.
**      - The amount of data to be written.
**
**  OUTPUT:
**      The data is written to the wav file.
**      The function returns nothing.
**
*/

static void         write_wav_bit( bitvalue, samples )
uint32 bitvalue;                    /* The bit value to be represented  */
uint32 samples;                     /* Number of bytes to be written    */
{
    uint32 bytes;                   /* Number of bytes actually written */
    bool   falling;                 /* Level of signal is falling       */
    ubyte  last_value;              /* Last byte value output           */

/*
**  Here is where we have to do special things in order to make the
**  selected type of transition from mark to space or from space to mark,
**  if we have a transition at all.  If there is no transition, things
**  are really simple.  If there is a transition, the transition from
**  mark to space is the most important.  Unless we are writing pure
**  tones, the mark tone is always written from the end of the mark table,
**  and the space tone is written from the start of the space table.
**  Unfortunately, sometimes we write a very long mark tone, which is longer
**  than the table.  In this case we will be wrapping around to the start of
**  the table one or more times, so compute where to start at, such that we
**  we arrive at the end of the mark table.
**  With pure tones, we always make a smooth transition.
*/

/*
**  No bytes written yet.
*/
    bytes = 0;

/*
**  If only the result counts, we do not care whether or not the wave format
**  looks nice, we just want it to load reliable.  For mark tones, write the
**  tone from the end of the table.  For space tones, write it from the start
**  of the table.  This makes the mark to space transition occur at the zero
**  level.
*/
    if( zero_transition )
    {
        if( bitvalue == FSK_MARK )
        {
            if( samples < MARK_TABLE_LEN )
            {
                ptr = &(mark[MARK_TABLE_LEN - samples]);
                table_len = samples;
            }
            else
            {
                ptr = &(mark[MARK_TABLE_LEN - (samples % MARK_TABLE_LEN)]);
                table_len = samples % MARK_TABLE_LEN;
            }
        }
        else
        {
            ptr = space;
            table_len = SPACE_TABLE_LEN;
        }
    } /* end if zero transition */
    else

/*
**  If we are writing pure tones, we must orderly end the current tone
**  before changing over to the next.
**  Complete the half period in progress before changing the tone.
*/
    if( format_pure )
    {

/*
**  When we pass the zero level, change the tone.
**  Try to make a clean transition from mark to space.
**  Finish up the last half period of the previous bit.
**  If the bit is the same, continue with the current tone where we
**  left off.
*/
        if( bitvalue != prev_bitvalue )
        {

/*
**  If we are at the start of our generator buffer, no need to do
**  complicated things, we can simply change to the start of the other
**  generator tone buffer.
**  Besides, we would not have a previous value in this case to determine
**  rising or falling, although we know it is rising.
*/
            if( ( prev_bitvalue == FSK_MARK ) && ( ptr == mark ) )
            {
                ptr = space;
                table_len = SPACE_TABLE_LEN;
            }
            else
            if( ( prev_bitvalue == FSK_SPACE ) && ( ptr == space ) )
            {
                ptr = mark;
                table_len = MARK_TABLE_LEN;
            }
            else

/*
**  Otherwise, figure out whether the signal was rising or falling.
**  If we just crossed the zero level, we can see whether we are rising
**  or falling.  If the current value is below the zero level, we are now
**  falling.  If it is above the zero level, we are now rising.
*/
            {
                if( ( *(ptr - 1) >= ZERO_LEVEL ) && ( *ptr <= ZERO_LEVEL ) )
                {
                    falling = TRUE;
                }
                else
                if( ( *(ptr - 1) <= ZERO_LEVEL ) && ( *ptr >= ZERO_LEVEL ) )
                {
                    falling = FALSE;
                }
                else

/*
**  Since we did not cross the zero level just now, look at the last value.
**  If it is above the zero level, continue to write samples until we pass
**  the zero level falling down, otherwise continue to write samples until
**  we pass the zero level going up.
*/
                if( *(ptr - 1) > ZERO_LEVEL )
                {
                    falling = TRUE;
                    while( *ptr > ZERO_LEVEL )
                    {
                        write_wav( (char *)ptr, 1 );
                        bytes++;
                        ptr++;
                        if( --table_len == 0 )
                            break;
                    }
                }
                else
                {
                    falling = FALSE;
                    while( *ptr < ZERO_LEVEL )
                    {
                        write_wav( (char *)ptr, 1 );
                        bytes++;
                        ptr++;
                        if( --table_len == 0 )
                            break;
                    }
                }

/*
**  At this point, we finished the last half period of the previous
**  tone.  Now switch tone.  Make sure we are going in the same direction
**  as the previous tone, either falling or rising.
**  This means that if we need to be falling, we must move ahead until
**  we are.  The tables start out rising.
*/
                if( bitvalue == FSK_MARK )
                {
                    ptr = mark;
                    table_len = MARK_TABLE_LEN;
                }
                else
                {
                    ptr = space;
                    table_len = SPACE_TABLE_LEN;
                }

/*
**  If falling, search for the zero crossing in the other table.
*/
                if( falling )
                {
                    ptr++;
                    table_len--;
                    for( ; table_len; ptr++, table_len-- )
                    {
                        if( *ptr <= ZERO_LEVEL )
                            break;
                    }
                }
            } /* end else if at start of buffer */
        } /* end if bit value changed */
    } /* end if pure tones */

    else

/*
**  If we are writing normal sine waves, change from one sine wave to
**  the other.  Do so by finding the corresponding value in the other table,
**  and continue writing data from the other table.
*/
    if( format_sine )
    {
        if( bitvalue != prev_bitvalue )
        {

/*
**  Check for start of buffer.
*/
            if( ( prev_bitvalue == FSK_MARK ) && ( ptr == mark ) )
            {
                ptr = space;
                table_len = SPACE_TABLE_LEN;
            }
            else
            if( ( prev_bitvalue == FSK_SPACE ) && ( ptr == space ) )
            {
                ptr = mark;
                table_len = MARK_TABLE_LEN;
            }
            else

/*
**  Compare the current value to the last value.  If it is smaller, we
**  are falling.  If it is larger, we are rising.  If it is the same, we
**  must be at the top or bottom of the sine wave, and we are then moving
**  away from that.
*/
            {
                last_value = *(ptr - 1);

#if 0

/*
**  If the space value was larger than any value in the mark table,
**  we will never find the correct value, so limit it to the top value.
*/
                if( last_value > 192 )
                {
                    falling = TRUE;
                    last_value = 192;
                }
                else
                if( last_value < 64 )
                {
                    falling = FALSE;
                    last_value = 64;
                }
                else

#endif

                if( last_value > *ptr )
                {
                    falling = TRUE;
                }
                else
                if( last_value < *ptr )
                {
                    falling = FALSE;
                }
                else

/*
**  Values are the same, moving away from top, determine which top.
*/
                {
                    if( *ptr > ZERO_LEVEL )
                        falling = TRUE;
                    else
                        falling = FALSE;
                }

/*
**  Now that we know whether we are rising or falling, find the value we
**  wrote last, but this time in the buffer for the other frequency, and
**  find it either falling or rising, whatever it was we were doing.
*/
                if( bitvalue == FSK_MARK )
                {
                    ptr = mark + 1;
                    table_len = MARK_TABLE_LEN - 1;
                }
                else
                {
                    ptr = space + 1;
                    table_len = SPACE_TABLE_LEN - 1;
                }

/*
**  Search for the value in the other table.
*/
                for( ; table_len; ptr++, table_len-- )
                {
                    if( *ptr == last_value )
                    {
                        if( falling )
                        {
                            ptr++;
                            table_len--;
                            if( table_len == 0 )
                                break;
                            if( *ptr < last_value )
                                break;
                        }
                        else
                        {
                            ptr++;
                            table_len--;
                            if( table_len == 0 )
                                break;
                            if( *ptr > last_value )
                                break;
                        }
                    } /* end if matching value found */
                } /* end search for matching value and direction */
            } /* end else if at start of buffer */
        } /* end if bit value changed */
    } /* end if sine wave */

    else

/*
**  Nothing left but square waves.  We cannot really make them smooth.
**  We do make sure that at least the level changes to the opposite,
**  so that the length of the first period is correct.
*/
    if( format_square )
    {
        if( bitvalue == FSK_MARK )
        {
            if( *ptr > ZERO_LEVEL )
            {
                ptr = mark;
                table_len = MARK_TABLE_LEN;
                while( *ptr > ZERO_LEVEL )
                {
                    ptr++;
                    table_len--;
                }
            }
            else
            {
                ptr = mark;
                table_len = MARK_TABLE_LEN;
            }
        } /* end if mark */
        else
        {
            if( *ptr > ZERO_LEVEL )
            {
                ptr = space;
                table_len = SPACE_TABLE_LEN;
                while( *ptr > ZERO_LEVEL )
                {
                    ptr++;
                    table_len--;
                }
            }
            else
            {
                ptr = space;
                table_len = SPACE_TABLE_LEN;
            }
        } /* end else if mark */
    } /* end if square waves */

/*
**  We are now pointing to the correct spot in the buffer.
**  Write the requested number of samples.  Deduct the number of samples
**  we used to complete the previous tone.
*/
    if( samples > bytes )
    {
        while( samples - bytes > table_len )
        {
            write_wav( (char *)ptr, table_len );
            bytes += table_len;

            if( bitvalue == FSK_MARK )
            {
                ptr = mark;
                table_len = MARK_TABLE_LEN;
            }
            else
            {
                ptr = space;
                table_len = SPACE_TABLE_LEN;
            }
        }
        write_wav( (char *)ptr, samples - bytes );
        ptr += ( samples - bytes );
        table_len -= ( samples - bytes );
    }

    prev_bitvalue = bitvalue;

    if( table_len == 0 )
    {
        if( bitvalue == FSK_MARK )
        {
            ptr = mark;
            table_len = MARK_TABLE_LEN;
        }
        else
        {
            ptr = space;
            table_len = SPACE_TABLE_LEN;
        }
    }
}

/*****************************************************************************
**  NAME:  write_wav_number()
**
**  PURPOSE:
**      Write a number to the wav file.
**
**  DESCRIPTION:
**      This function will write the specified number to the wav file.
**
**  INPUT:
**      - The number to be written.
**      - The amount of data to be written.
**
**  OUTPUT:
**      The data is written to the wav file.
**      The function returns nothing.
**
*/

static void         write_wav_number( value, buflen )
uint32 value;                       /* The number to be written         */
uint32 buflen;                      /* Number of bytes to be written    */
{
    number[0] = value;
    number[1] = value >> 8;
    number[2] = value >> 16;
    number[3] = value >> 24;
    write_wav( (char *)number, buflen );
}

/*****************************************************************************
==  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          byte;                   /* Byte index                   */
    uint32          wrk_ndx;                /* Work index                   */
    bool            end_of_str;             /* Null terminator seen?        */
    ubyte           input_path[PATH_LEN];   /* Input cas file spec          */
    uint32          len_chunk;              /* Chunck length                */
    uint32          len_file;               /* File length                  */
    ubyte           wav_path[PATH_LEN];     /* Output wave file spec        */
    ubyte           buf[BUF_LEN];           /* Buffer string                */
    double          rad;                    /* Radians intermediate value   */
/*  double          rad_mark;               /* Radians intermediate value   */
/*  double          rad_space;              /* Radians intermediate value   */
    uint32          stat;                   /* Status from function         */
    ubyte           proceed;                /* Proceed with conversion      */

/*
**  Allocate buffer space for the tone generators.
*/
    mark = (ubyte *)malloc((unsigned long)MARK_TABLE_LEN * sizeof( ubyte ) );
    space = (ubyte *)malloc((unsigned long)SPACE_TABLE_LEN * sizeof( ubyte ) );

    if( !mark || !space )
    {
        fprintf( stderr, "\nCannot allocate buffer, insufficient memory.\n" );
        cleanup();
        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;
    test_tape = 0;
    baudrate = 600;
    baudrate_fixed = FALSE;
    leader = 0;
    irg = 0;
    mark_tone = FSK_TONE_MARK;
    space_tone = FSK_TONE_SPACE;
    table_len = MARK_TABLE_LEN;
    recno = 0;
    format_pure = FALSE;
    format_sine = TRUE;
    format_square = FALSE;
    zero_transition = FALSE;

    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 = 1; 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;
                    break;
                }

/*
**  The /z option selects the transition at the zero level.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'Z' )
                {
                    zero_transition = TRUE;
                    break;
                }

/*
**  The /w option selects the wave format.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'W' )
                {
                    while( argv[arg_ndx][++wrk_ndx] )
                    {
                        if( toupper( argv[arg_ndx][wrk_ndx] ) == 'S' )
                        {
                            format_pure = FALSE;
                            format_sine = TRUE;
                            format_square = FALSE;
                        }
                        if( toupper( argv[arg_ndx][wrk_ndx] ) == 'B' )
                        {
                            format_pure = FALSE;
                            format_sine = FALSE;
                            format_square = TRUE;
                        }
                        if( toupper( argv[arg_ndx][wrk_ndx] ) == 'P' )
                        {
                            format_pure = TRUE;
                            format_sine = TRUE;
                            format_square = FALSE;
                        }
                    }
                    break;
                }

/*
**  The /t option selects a test tape output.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'T' )
                {
                    test_tape = 0;
                    while( argv[arg_ndx][++wrk_ndx] )
                    {
                        if( ( argv[arg_ndx][wrk_ndx] >= '0' ) &&
                            ( argv[arg_ndx][wrk_ndx] <= '9' ) )
                        {
                            test_tape *= 10;
                            test_tape += argv[arg_ndx][wrk_ndx] - '0';
                        }
                    }
                    break;
                }

/*
**  The /m option selects the mark tone frequency.
**  The format of this switch is /m=nnnn where nnnn is a numeric value
**  that should be around 5327 Hertz.  We accept any number the user enters
**  though.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'M' )
                {
                    mark_tone = 0;
                    while( argv[arg_ndx][++wrk_ndx] )
                    {
                        if( ( argv[arg_ndx][wrk_ndx] >= '0' ) &&
                            ( argv[arg_ndx][wrk_ndx] <= '9' ) )
                        {
                            mark_tone *= 10;
                            mark_tone += argv[arg_ndx][wrk_ndx] - '0';
                        }
                    }
                    break;
                }

/*
**  The /s option selects the space tone frequency.
**  The format of this switch is /s=nnnn where nnnn is a numeric value
**  that should be around 3995 Hertz.  We accept any number the user enters
**  though.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'S' )
                {
                    space_tone = 0;
                    while( argv[arg_ndx][++wrk_ndx] )
                    {
                        if( ( argv[arg_ndx][wrk_ndx] >= '0' ) &&
                            ( argv[arg_ndx][wrk_ndx] <= '9' ) )
                        {
                            space_tone *= 10;
                            space_tone += argv[arg_ndx][wrk_ndx] - '0';
                        }
                    }
                    break;
                }

/*
**  The /b option selects the baudrate.
**  The format of this switch is /b=nnnn where nnnn is a numeric value
**  that should be around 600 baud.  We accept any number the user enters
**  though.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'B' )
                {
                    baudrate = 0;
                    baudrate_fixed = TRUE;
                    while( argv[arg_ndx][++wrk_ndx] )
                    {
                        if( ( argv[arg_ndx][wrk_ndx] >= '0' ) &&
                            ( argv[arg_ndx][wrk_ndx] <= '9' ) )
                        {
                            baudrate *= 10;
                            baudrate += argv[arg_ndx][wrk_ndx] - '0';
                        }
                    }
                    break;
                }

/*
**  The /l option selects the length of the leader.
**  The format of this switch is /l=nnnn where nnnn is a numeric value
**  that should be around 20000 milli-seconds.  We accept any number the user
**  enters though.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'L' )
                {
                    leader = 0;
                    while( argv[arg_ndx][++wrk_ndx] )
                    {
                        if( ( argv[arg_ndx][wrk_ndx] >= '0' ) &&
                            ( argv[arg_ndx][wrk_ndx] <= '9' ) )
                        {
                            leader *= 10;
                            leader += argv[arg_ndx][wrk_ndx] - '0';
                        }
                    }
                    break;
                }

/*
**  The /i option selects the length of inter record gaps.
**  The format of this switch is /i=nnnn where nnnn is a numeric value
**  that should be around 250 milli-seconds.  We accept any number the user
**  enters though.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'I' )
                {
                    irg = 0;
                    while( argv[arg_ndx][++wrk_ndx] )
                    {
                        if( ( argv[arg_ndx][wrk_ndx] >= '0' ) &&
                            ( argv[arg_ndx][wrk_ndx] <= '9' ) )
                        {
                            irg *= 10;
                            irg += argv[arg_ndx][wrk_ndx] - '0';
                        }
                    }
                    break;
                }

/*
**  Ignore other options.
*/
                fprintf(stderr,"\nOption %c invalid.\n", argv[arg_ndx][wrk_ndx] );
                break;
            } /* 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] );
            }

            cas_file = fopen( (char *)input_path, "rb" );
            if( cas_file == NULL )
            {
                fprintf(stderr, "Cannot open cassette image file %s\n", input_path);
                exit( 255 );
            }
        }
    } /* end for all command line arguments */

/*
**  If there is no filename on the command line, ask for it.
*/
    if( arg_no == 0 && !test_tape )
    {

/*
**  Open hailing frequencies.
**  No command line arguments, so ask what it is we have to do.
*/
        printf( "Classic Atari cassette tape recovery version April 26, 1999\n" );
        printf( "Copyright 1998, 1999 by Ernest R. Schreurs. All rights reserved.\n" );
        printf( "Amiga port by Wojciech Pasiecznik (voydial@wp.pl).\n" );


        while( TRUE )                       /* until terminated by control C*/
        {
            printf( "\nEnter ^C or hit Control C to terminate\n" );

            do                              /* until .cas file entered    */
            {
                printf("\nEnter .cas 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("\nConvert 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;

            cas_file = fopen( (char *)input_path, "rb" );
            if( cas_file == NULL )
            {
                fprintf(stderr, "Cannot open cassette image file\n");
                continue;
            }
            break;
        } /* end while need a valid filename */

        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;

/*
**  Ask for the wave format.
*/
        format_pure = FALSE;
        format_sine = FALSE;
        format_square = FALSE;
        do                              /* until answer is S or B or P */
        {
            printf("\nDo you want s)ine waves, b)lock waves or p)ure waves? [s]: ");
            GET_BUF();
            answer = toupper( buf[0] );

/*
**  If blank, default is block waves
*/
            if ( answer == '\n' )
                answer = 'S';

        } while ( (answer != 'S') && (answer != 'B') && (answer != 'P') );

        if( answer == 'S' )
        {
            format_pure = FALSE;
            format_sine = TRUE;
            format_square = FALSE;
        }
        if( answer == 'B' )
        {
            format_pure = FALSE;
            format_sine = FALSE;
            format_square = TRUE;
        }
        if( answer == 'P' )
        {
            format_pure = TRUE;
            format_sine = TRUE;
            format_square = FALSE;
        }

        do                              /* until answer is Y or N       */
        {
            printf("\nUse space/mark transition at zero level Y)es or [N]o : ");
            GET_BUF();
            answer = toupper( buf[0] );

/*
**  If blank, default is no
*/
            if ( answer == '\n' )
                answer = 'N';

        } while ( answer != 'Y' && answer != 'N' );

        zero_transition = ( answer == 'Y' ) ? TRUE : FALSE;

/*
**  Ask for the mark tone frequency.
*/
        printf("\nEnter mark frequency [5327]: ");
        GET_BUF();
        mark_tone = 0;

        for( wrk_ndx = 0; wrk_ndx < BUF_LEN; wrk_ndx++ )
        {
            if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                break;
            if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                break;
            if( ( buf[wrk_ndx] >= '0' ) &&
                ( buf[wrk_ndx] <= '9' ) )
            {
                mark_tone *= 10;
                mark_tone += buf[wrk_ndx] - '0';
            }
        }
        if( mark_tone == 0 )
            mark_tone = FSK_TONE_MARK;

/*
**  Ask for the space tone frequency.
*/
        printf("\nEnter space frequency [3995]: ");
        GET_BUF();
        space_tone = 0;

        for( wrk_ndx = 0; wrk_ndx < BUF_LEN; wrk_ndx++ )
        {
            if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                break;
            if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                break;
            if( ( buf[wrk_ndx] >= '0' ) &&
                ( buf[wrk_ndx] <= '9' ) )
            {
                space_tone *= 10;
                space_tone += buf[wrk_ndx] - '0';
            }
        }
        if( space_tone == 0 )
            space_tone = FSK_TONE_SPACE;

/*
**  Ask for a fixed baudrate.
*/
        printf("\nEnter fixed baudrate if desired 425 - 875 : ");
        GET_BUF();
        baudrate = 0;

        for( wrk_ndx = 0; wrk_ndx < BUF_LEN; wrk_ndx++ )
        {
            if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                break;
            if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                break;
            if( ( buf[wrk_ndx] >= '0' ) &&
                ( buf[wrk_ndx] <= '9' ) )
            {
                baudrate *= 10;
                baudrate += buf[wrk_ndx] - '0';
            }
        }
        if( baudrate == 0 )
            baudrate = 600;
        else
            baudrate_fixed = TRUE;

/*
**  Ask for the fixed length of the leader.
*/
        printf("\nLength of leader if fixed length in milli-seconds : ");
        GET_BUF();
        leader = 0;

        for( wrk_ndx = 0; wrk_ndx < BUF_LEN; wrk_ndx++ )
        {
            if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                break;
            if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                break;
            if( ( buf[wrk_ndx] >= '0' ) &&
                ( buf[wrk_ndx] <= '9' ) )
            {
                leader *= 10;
                leader += buf[wrk_ndx] - '0';
            }
        }
 
/*
**  Ask for the fixed length of the irg.
*/
        printf("\nLength of Inter Record Gap if fixed length in milli-seconds : ");
        GET_BUF();
        irg = 0;

        for( wrk_ndx = 0; wrk_ndx < BUF_LEN; wrk_ndx++ )
        {
            if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                break;
            if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                break;
            if( ( buf[wrk_ndx] >= '0' ) &&
                ( buf[wrk_ndx] <= '9' ) )
            {
                irg *= 10;
                irg += buf[wrk_ndx] - '0';
            }
        }
 
    } /* end else if command line arguments */

/*
**  Compute the sine value for all sample positions.
**  The mark tone is 5327 Hertz.  One period is 2 PI radians,
**  One second contains 5327 periods.  We have 44,100 samples in one second.
**  To evenly divide these 5327 * 2 PI radians over 44,100 samples, we
**  have to divide by 44,100.  To get the radians value, multiply by the
**  sample position number.  Similar computations are done for the space tone.
**  Convert the sine value to a PCM value with proper audio volume by
**  multiplying it by 64.
**  The center of the PCM values is at 128, thus we add 128.
*/
    rad = (double)mark_tone * 2.0 * PI / MARK_TABLE_LEN;
    for( byte = 0; byte < MARK_TABLE_LEN; byte++ )
    {
        mark[byte] = sin( rad * byte ) * 64 + ZERO_LEVEL;

        if( format_square )
        {
            if( mark[byte] >= ZERO_LEVEL )
                mark[byte] = 192;
            else
                mark[byte] = 64;
        }

    }
    rad = (double)space_tone * 2.0 * PI / SPACE_TABLE_LEN;
    for( byte = 0; byte < SPACE_TABLE_LEN; byte++ )
    {
        space[byte] = sin( rad * byte ) * 64 + ZERO_LEVEL;

        if( format_square )
        {
            if( space[byte] >= ZERO_LEVEL )
                space[byte] = 128;
            else
                space[byte] = 64;
        }

    }

/*
**  Initialize the pointer to the table.
*/
    bytelen = ( 44100L * 10 ) / baudrate;
    bitlen = 44100L / baudrate;

#if 0

    bit_stretch = 9;
    if( zero_transition && format_square )
        bit_stretch = 0;

#endif

    header_written = FALSE;
    prev_bitvalue = FSK_MARK;
    ptr = mark;
    table_len = MARK_TABLE_LEN;

/*
**  If we must write a test-tape, do so, and then quit.
*/
    if( test_tape )
    {
        write_test_tape();
    } /* end if test tape */
    else

/*
**  Generate a wave file based on the .cas file.
*/
    {

/*
**  Process the header of the .cas file.
*/
        stat = process_header();
        if( stat == FAILURE )
        {
            cleanup();
            exit( 255 );
        }

        PRINT( ("File : %s\n", input_path ) );

        memcpy( wav_path, input_path, PATH_LEN );

/*
**  Find the dot and replace by or add .wav extension.
*/
        wrk_ndx = STRLEN(wav_path);
        for( ;; )
        {
            if( wav_path[wrk_ndx] == '.' )
            {
                memcpy( &(wav_path[wrk_ndx]), ".wav", 5 );
                break;
            }
            if( ( wrk_ndx == 0 ) ||
                ( wav_path[wrk_ndx] == '/' ) ||
                ( wav_path[wrk_ndx] == '\\' ) )
            {
                memcpy( &(wav_path[STRLEN(wav_path)]), ".wav", 5 );
                break;
            }
            wrk_ndx--;
        }

        wav_file = fopen( (char *)wav_path, "wb" );
        if( wav_file == NULL )
        {
            fprintf(stderr, "\nCannot open .wav file!\n");
            cleanup();
            exit( 255 );
        }

        fprintf(stderr, "\nProcessing, please wait!\n");

/*
**  Read records, and process them.
*/

        while( read_record() == SUCCESS )
            process_record();

    } /* end else if a test tape */

/*
**  Fix up the file size and the chunk size in the wave file.
**  Compute the lengths.  Write an alignment byte if needed.
**  Position the file at the chunk size and update it and do the
**  same for the file size.
*/
    len_chunk = pos - pos_chunk_size - 4;
    len_file  = pos - pos_file_size - 4;
    if( len_chunk & 0x01L )
        write_wav( (char *)"\0", (uint32)1L );     /* Write alignment byte */
    fseek( wav_file, pos_chunk_size, SEEK_SET );   /* Go back to chunk size */
    write_wav_number( len_chunk, (uint32)4L );
    fseek( wav_file, pos_file_size, SEEK_SET );    /* Go back to file size */
    write_wav_number( len_file, (uint32)4L );

    cleanup();
    return 0;
}

/*****************************************************************************
**  MODIFICATION HISTORY
**
**  DATE        BY   Description
**  ----------  ---  ---------------------------------------------------------
**  1998/12/30  ERS  Project start
**  1999/04/26  ERS  Release 01.00
**
*****************************************************************************/
