//******************************************************************************
// FILE
//      ezpulse.cpp
//
//
// REVISION HISTORY
//  dd-mmm-yyyy  AUTHOR      DESCRIPTION
//  29-Jul-2000  dan       - [1.5] Adapt 22KHz, 8bit sampling.
//  28-Jul-2000  dan       - [1.4] Fixed pulltime converting.
//  25-Jul-2000  dan       - [1.3] Modified for tentative solution: output
//                                 odometer data whenever the pulse detected.
//  23-Jul-2000  dan       - [1.2] Bug fixed, processing stopped after approx.
//                                 7 minutes due to the overflow in playtime
//                                 calculation.
//
//******************************************************************************

/*** INCLUDE FILES ***/

#include "stdafx.h"
#include "stdio.h"
#include "conio.h"
#include "malloc.h"
#include "math.h"
#include "limits.h"
#include "string.h"


/*** LOCAL DEFINITIONS ***/

   // Generic macros

#define SUCCESS     0
#define FAILURE    -1

#define TRUE        1
#define FALSE       0

#define PUBLIC
#define PRIVATE     static

#define EOS         0x00

typedef int         Bool;

    // Private macros

#define EZP_ODOPULSE_RESET                 0
#define EZP_ODOPULSE_DETECT                1

#define EZP_CRANKPULSE_RESET               0
#define EZP_CRANKPULSE_DETECT              1

#define EZP_WAV_BUFFERING_TIME            60  // 60 seconds of wave data to be processed at once

   // Audio preprocess settings

#define EZP_GAIN_L           100
#define EZP_GAIN_R           100
#define EZP_GAIN_BASE        100


   // Pulse detection thresholds

#define EZP_ODOPULSE_LEVEL_ON_THRESHOLD         5000   // ORG=1000
#define EZP_ODOPULSE_LEVEL_OFF_THRESHOLD        4500   // ORG=500
#define EZP_ODOPULSE_TIME_ON_THRESHOLD            11  // ORG=22
#define EZP_ODOPULSE_TIME_OFF_THRESHOLD            5  // ORG=10

#define EZP_CRANK_POSITIVE_THRESHOLD                0   // ORG=10   ver5=-128
#define EZP_CRANK_NEGATIVE_THRESHOLD                0   // ORG= -10 ver5= 128
#define EZP_CRANK_TIME_THRESHOLD1                   8   // ORG=12
#define EZP_CRANK_TIME_THRESHOLD2                   2   // ORG=4

#define EZP_CRANK_POSITIVE_DELTASUM_THRESHOLD1   256    // ORG=120
#define EZP_CRANK_NEGATIVE_DELTASUM_THRESHOLD1  -256    // ORG=-120
#define EZP_CRANK_POSITIVE_DELTASUM_THRESHOLD2  4000
#define EZP_CRANK_NEGATIVE_DELTASUM_THRESHOLD2 -4000

#define MSEC                              10
#define PULSETIME_TO_TENTH_SECONDS(pulsetime)   (pulsetime / 1000)

#define EZP_COMBINED_OUTFILE             TRUE          // Use combined output file.

#define EZP_ODOPULSE_REPORTPERIOD        (200 * MSEC)  // Odometer is checked every 0.2 Seconds.
#define EZP_CRANK_REPORTPERIOD           (100 * MSEC)  // Crank speed is checked every 0.1 Seconds.
#define EZP_REPORTPERIOD                 (200 * MSEC)  // Odometer and Crank speed is checked every 0.2 Seconds.

#define EZP_FILENAME_BUFFER_LENGTH       (512)
#define EZP_FILENAME_MAX_LENGTH          (EZP_FILENAME_BUFFER_LENGTH -16 )

#define WAVFORMAT_8BIT                  0             // "FormatSpecific" in WAV format chunk.
#define WAVFORMAT_16BIT                 2             // "FormatSpecific" in WAV format chunk.

typedef struct
{
    unsigned long rID;   /* "RIFF" signeture */
    unsigned long rLen;  /* Length of the data in the next chunk */
} RIFFHeaderObj;

typedef struct
{
    unsigned long  fId;             /*  Contains the characters "fmt"       */
    unsigned long  fLen;            /* Length of data in the format chunk   */
    unsigned short wFormatTag;      /*                                      */
    unsigned short nChannels;       /* Number of channels, 1=mono, 2=stereo */
    unsigned short nSamplesPerSec;  /* Playback frequency                   */
    unsigned short nAvgBytesPerSec; /*                                      */
    unsigned short nBlockAlign;     /*                                      */
    unsigned short FormatSpecific;  /*  Format specific data area           */
} WAVFormatChunkObj;

typedef struct
{
    unsigned long  wID;
    WAVFormatChunkObj wavFormatChunk;
} WAVHeaderObj;

typedef struct
{
    short L_Ch;
    short R_Ch;
} WaveBufferEntryObj_16bitStereo;

typedef struct
{
    unsigned char L_Ch;
    unsigned char R_Ch;
} WaveBufferEntryObj_8bitStereo;

#define WAVBuffer void

typedef struct
{
    char           filename[ EZP_FILENAME_BUFFER_LENGTH ];
    FILE          *infile;
    FILE          *odometerfile;
    FILE          *crankfile;
    RIFFHeaderObj  riff_header;
    WAVHeaderObj   wav_header;
    WAVBuffer     *p_buffer;
    int            total_samples_in_buffer;
    int            valid_samples_in_buffer;
    long           sampleindex_in_buffer;
    long           sampleindex_in_file;
    int            latest_odometer;
    int            latest_crankspeed;
} EZPDataObject;

typedef struct
{
    int   bargraph_cycle;
    Bool  display_header;
    Bool  display_odoinfo;
    Bool  display_crankinfo;
} EZPOptionObject;



/*** GLOBALS ***/

EZPOptionObject ezp_option;


/*** PROTOTYPES ***/

PRIVATE int    ezp_OpenFile( EZPDataObject * );
PRIVATE void   ezp_CloseFile( EZPDataObject * );
PRIVATE int    ezp_GetRiffHeader( EZPDataObject * );
PRIVATE int    ezp_GetWavHeader( EZPDataObject * );
PRIVATE int    ezp_DumpWavHeader( EZPDataObject * );
PRIVATE int    ezp_AllocateWaveBuffer( EZPDataObject *, int );
PRIVATE int    ezp_DeallocateWaveBuffer( void * );
PRIVATE int    ezp_ProcessWaveData( EZPDataObject * );
PRIVATE void   ezp_DisplayBargraph( long *, long *, long, int, int, int );
PRIVATE int    ezp_DetectOdopulse( EZPDataObject *, int, long, long );
PRIVATE int    ezp_DetectCrankpulse( EZPDataObject *, int, long, long );
PRIVATE long   ezp_GetPulseTime( EZPDataObject * );

PRIVATE EZPOptionObject * ezp_GetOptionObject( void );

    // Function style macros

#define EZP_BARGRAPHCYCLE()             ( ezp_option.bargraph_cycle )
#define EZP_IS_DISPLAY_HEADERINFO_ON()  ( ezp_option.display_header )
#define EZP_IS_DISPLAY_ODOINFO_ON()     ( ezp_option.display_odoinfo )
#define EZP_IS_DISPLAY_CRANKINFO_ON()   ( ezp_option.display_crankinfo )

/******************************************************************************
** FILE
**      main()
**
******************************************************************************/
PUBLIC int main(int argc, char* argv[])
{
    EZPDataObject ezp_data;
    EZPOptionObject *p_option;
    int           seconds = 0;

    p_option = ezp_GetOptionObject();

    p_option->bargraph_cycle       = 0;
    p_option->display_header       = FALSE;
    p_option->display_odoinfo      = TRUE;
    p_option->display_crankinfo    = TRUE;

    ezp_data.odometerfile = (FILE *)NULL;
    ezp_data.crankfile    = (FILE *)NULL;

    // Set source filename and open it.

    if ( argc < 2 )
    {
        printf( "EZP: ERROR: No filename given.\n");
        return( FAILURE );
    }

    if ( strlen( argv[1] ) < 1 )
    {
        printf( "EZP: ERROR: No filename given.\n");
        return( FAILURE );
    }

    if ( strlen( argv[1] ) > EZP_FILENAME_MAX_LENGTH )
    {
        printf( "EZP: ERROR: Filename too long.\n");
        return( FAILURE );
    }

    strncpy( ezp_data.filename, argv[1], EZP_FILENAME_BUFFER_LENGTH -1  );
  
    if ( FAILURE == ezp_OpenFile( &ezp_data ) )
    {
        return( FAILURE );
    }

    // Allocate WAVE data buffer.
  
    if ( SUCCESS != ezp_AllocateWaveBuffer( &ezp_data, EZP_WAV_BUFFERING_TIME ) )
    {
        printf( "EZP: ERROR: Wave buffer allocation failed.\n" );
        ezp_CloseFile( &ezp_data );      
        return( FAILURE );
    }

    // Now we have wave buffer ready. Process the wave file.

    if( SUCCESS != ezp_ProcessWaveData( &ezp_data ) )
    {
        printf( "EZP: ERROR: Error occured while processing the file.\n" );
    } 

    // Deallocate the wave buffer and terminate the program.
  
    ezp_DeallocateWaveBuffer( ezp_data.p_buffer );

    ezp_CloseFile( &ezp_data );

    printf("EZP: Process completed.\n");  
  
    return( SUCCESS );
}


/******************************************************************************
** FILE
**      ezp_OpenFile()
**
******************************************************************************/
PRIVATE int ezp_OpenFile(  EZPDataObject *p_ezp_data )
{
    if ( NULL == ( p_ezp_data->infile = fopen ( p_ezp_data->filename, "rb" ) ) )
    {
        printf("EPZ: ERROR: File %s not found.\n", p_ezp_data->filename );
        return( FAILURE );
    }

    if (SUCCESS != ezp_GetRiffHeader( p_ezp_data ) )
    {
        printf( "EZP: ERROR: Invalid RIFF header.\n" );
        return( FAILURE );
    }

    if (SUCCESS != ezp_GetWavHeader( p_ezp_data ) )
    {
        printf( "EZP: ERROR: Invalid WAV header.\n" );
        return( FAILURE );
    }

    if ( EZP_IS_DISPLAY_HEADERINFO_ON() ) ezp_DumpWavHeader( p_ezp_data );

    return( SUCCESS );
}


/******************************************************************************
** FILE
**      ezp_CloseFile()
**
** DESCRIPTION
**      Close output files, if opened.
**
******************************************************************************/
PRIVATE void ezp_CloseFile( EZPDataObject *p_ezp_data )
{
    if ( NULL != p_ezp_data->odometerfile )
    {
        fclose ( p_ezp_data->odometerfile );
        p_ezp_data->odometerfile = NULL;
    }
    if ( NULL != p_ezp_data->crankfile )
    {
        fclose ( p_ezp_data->crankfile );
        p_ezp_data->crankfile = NULL;
    }
}

/******************************************************************************
** FILE
**      ezp_GetRiffHeader()
**
******************************************************************************/
PRIVATE int ezp_GetRiffHeader( EZPDataObject *p_ezp_data )
{
    char *p;
    FILE *infile;
    RIFFHeaderObj  *p_riffheader;
    

    // Setup pointers for readability.
    p_riffheader = &(p_ezp_data->riff_header);
    infile       =   p_ezp_data->infile;
  
    // Get RIFF signeture.
    if ( 1 > fread( &(p_riffheader->rID),  sizeof(p_riffheader->rID),  1, infile) )return ( FAILURE );

    // Get the length of the next chunk.
    if ( 1 > fread( &(p_riffheader->rLen), sizeof(p_riffheader->rLen), 1, infile) )return ( FAILURE );

    // Validate the RIFF signeture.
    p = (char *)&(p_riffheader->rID);
    if ( 'R' != *p++ ) return( FAILURE );
    if ( 'I' != *p++ ) return( FAILURE );
    if ( 'F' != *p++ ) return( FAILURE );
    if ( 'F' != *p++ ) return( FAILURE );
    return( SUCCESS );
}


/******************************************************************************
** FILE
**      ezp_GetWavHeader()
**
******************************************************************************/
PRIVATE int ezp_GetWavHeader( EZPDataObject *p_ezp_data )
{
    char           *p;
    WAVHeaderObj   *p_wavheader;
    FILE          *infile;
  
    // Setup pointers for readability.
    p_wavheader  = &(p_ezp_data->wav_header);
    infile       =   p_ezp_data->infile;
  
    // Get WAV header.
    if ( 1 > fread( p_wavheader,  sizeof(WAVHeaderObj),  1, infile) )return ( FAILURE );

    // Validate the WAV Chunk signeture.
    p = (char *)&(p_wavheader->wID);
    if ( 'W' != *p++ ) return( FAILURE );
    if ( 'A' != *p++ ) return( FAILURE );
    if ( 'V' != *p++ ) return( FAILURE );
    if ( 'E' != *p++ ) return( FAILURE );

    // Validate the Format Chunk signeture.
    p = (char *)&(p_wavheader->wavFormatChunk.fId);
    if ( 'f' != *p++ ) return( FAILURE );
    if ( 'm' != *p++ ) return( FAILURE );
    if ( 't' != *p++ ) return( FAILURE );
         
    return( SUCCESS );
}



/******************************************************************************
** FILE
**      ezp_DumpWavHeader()
**
******************************************************************************/
PRIVATE int ezp_DumpWavHeader( EZPDataObject *p_ezp_data )
{
    WAVHeaderObj *p_wavheader;

    p_wavheader = &(p_ezp_data->wav_header);

    printf( "EZP: fLen            = %d\n", p_wavheader->wavFormatChunk.fLen );
    printf( "EZP: wFormatTag      = %d\n", p_wavheader->wavFormatChunk.wFormatTag );
    printf( "EZP: nChannels       = %d\n", p_wavheader->wavFormatChunk.nChannels );
    printf( "EZP: nSamplesPerSec  = %d\n", p_wavheader->wavFormatChunk.nSamplesPerSec );
    printf( "EZP: nAvgBytesPerSec = %d\n", p_wavheader->wavFormatChunk.nAvgBytesPerSec );
    printf( "EZP: nBlockAlign     = %d\n", p_wavheader->wavFormatChunk.nBlockAlign );
    printf( "EZP: FormatSpecific  = %d\n", p_wavheader->wavFormatChunk.FormatSpecific );

    getch();
   
    return( SUCCESS );
}


/******************************************************************************
** FUNCTION
**      ezp_AllocateWaveBuffer()
**
** DESCRIPTION
**      Allocate memory for the wave data.
**
** PARAMETERS
**      WAVHeaderObj *p_wavheader : Specify the pointer to the WAV header.
**      void        **p_buffer    : A pointer to the allocated buffer is
**                                  returned here. Returns 0 if allocation
**                                  failed.
**      int           seconds     : Specify the amount of time to store the
**                                  wave data (in seconds).
**
******************************************************************************/
PRIVATE int ezp_AllocateWaveBuffer( EZPDataObject *p_ezp_data, int seconds )
{
    size_t buffersize;

    // Calculate number of sample points.
    p_ezp_data->total_samples_in_buffer  = p_ezp_data->wav_header.wavFormatChunk.nSamplesPerSec;
    p_ezp_data->total_samples_in_buffer *= seconds;
  
    // Calculate how much memory do we need for the buffer.

    switch( p_ezp_data->wav_header.wavFormatChunk.FormatSpecific )
    {
    case WAVFORMAT_16BIT:
        buffersize =  p_ezp_data->total_samples_in_buffer * sizeof( WaveBufferEntryObj_16bitStereo );
        break;
      
    case WAVFORMAT_8BIT:
        buffersize =  p_ezp_data->total_samples_in_buffer * sizeof( WaveBufferEntryObj_8bitStereo );
        break;

    default:
        fprintf( stderr, "EZP: Unsupported format, Formatspecific=%02d.\n",
                 p_ezp_data->wav_header.wavFormatChunk.FormatSpecific );
        return( FAILURE );
        break;
    }

    if ( NULL != ( p_ezp_data->p_buffer = malloc( buffersize)))
    {
        // Initialize the buffer index.
        p_ezp_data->valid_samples_in_buffer = 0;
        p_ezp_data->sampleindex_in_buffer   = 0;
        p_ezp_data->sampleindex_in_file     = 0;
        p_ezp_data->latest_odometer         = 0;
        p_ezp_data->latest_crankspeed       = 0;
        return( SUCCESS );
    }
    else
    {
        return( FAILURE );
    }
}

/******************************************************************************
** FUNCTION
**      ezp_DealocateWaveBuffer()
**
******************************************************************************/
PRIVATE int ezp_DeallocateWaveBuffer( void *p_buffer )
{
    free( p_buffer );
    return( SUCCESS );
}


/******************************************************************************
** FUNCTION
**      ezp_ProcessWaveData()
**
******************************************************************************/
PRIVATE int ezp_ProcessWaveData( EZPDataObject *p_ezp_data )
{
    long   L_Ch;
    long   R_Ch;
    size_t buffersize;
    unsigned short FormatSpecific;
    
    WaveBufferEntryObj_16bitStereo *p_entry16;
    WaveBufferEntryObj_8bitStereo  *p_entry8;

    long    pulsetime;
    static  crankpulsecount = 0;

    int     odopulse_detected;
    int     crankpulse_detected;

    FormatSpecific = p_ezp_data->wav_header.wavFormatChunk.FormatSpecific;

    switch( FormatSpecific )
    {
    case WAVFORMAT_16BIT:
        buffersize =  sizeof( WaveBufferEntryObj_16bitStereo );
        break;
      
    case WAVFORMAT_8BIT:
        buffersize =  sizeof( WaveBufferEntryObj_8bitStereo );
        break;

    default:
        fprintf( stderr, "EZP: ezp_ProcessWavData(): Unsupported format.\n" );
        return( FAILURE );
        break;
    }

    while ( TRUE )
    {
        // Do we need to fill the buffer before processing?
        if ( p_ezp_data->sampleindex_in_buffer >= p_ezp_data->valid_samples_in_buffer )
        {
            // Have we read all the data from the file ?
            if ( feof( p_ezp_data->infile ) )
            {
                // Yep, we've finished everything.
                return( SUCCESS );
            }

            // Still on our way. Fill the buffer.
            p_ezp_data->valid_samples_in_buffer
                = fread( p_ezp_data->p_buffer,
                         buffersize,
                         p_ezp_data->total_samples_in_buffer,
                         p_ezp_data->infile );
          
            // Do we have entire buffer filled?
            if ( p_ezp_data->valid_samples_in_buffer < p_ezp_data->total_samples_in_buffer )
            {
                // No. Seems like this is the last reading.
            }

            // Reset the index to the top of the buffer.
            p_ezp_data->sampleindex_in_buffer  = 0;
            p_entry8  = (WaveBufferEntryObj_8bitStereo  *)p_ezp_data->p_buffer;
            p_entry16 = (WaveBufferEntryObj_16bitStereo *)p_ezp_data->p_buffer;

        }

        // Get sound sample and adjust the volume level.

        switch( FormatSpecific )
        {
        case WAVFORMAT_16BIT:
            L_Ch = (long) p_entry16->L_Ch * EZP_GAIN_L / EZP_GAIN_BASE;
            R_Ch = (long) p_entry16->R_Ch * EZP_GAIN_R / EZP_GAIN_BASE;
            break;
      
        case WAVFORMAT_8BIT:
            L_Ch = ((long) p_entry8->L_Ch - 127 ) * 256 * EZP_GAIN_L / EZP_GAIN_BASE;
            R_Ch = ((long) p_entry8->R_Ch - 127 ) * 256 * EZP_GAIN_R / EZP_GAIN_BASE;
            // printf( "L_Ch=%08d R_Ch=%08d\n", p_entry8->L_Ch, p_entry8->R_Ch ); 
            break;

        default:
            // Should not reach here.
            return( FAILURE );
            break;
        }
      
        // Get elapsed time expressed in 0.1 mSec.
        pulsetime  = ezp_GetPulseTime( p_ezp_data );

        odopulse_detected   = ezp_DetectOdopulse  ( p_ezp_data, EZP_ODOPULSE_DETECT,   L_Ch, pulsetime );
        crankpulse_detected = ezp_DetectCrankpulse( p_ezp_data, EZP_CRANKPULSE_DETECT, R_Ch, pulsetime );
 
        ezp_DisplayBargraph( &L_Ch, &R_Ch, pulsetime, EZP_BARGRAPHCYCLE(), odopulse_detected, crankpulse_detected );

        // Go to next sample.
        p_entry16++;
        p_entry8++;
        p_ezp_data->sampleindex_in_buffer++;
        p_ezp_data->sampleindex_in_file++;
    }

    return( SUCCESS ); 
}  


/******************************************************************************
** FUNCTION
**      ezp_DisplayBargraph()
**  
******************************************************************************/
PRIVATE void ezp_DisplayBargraph( long *p_L_Ch, long *p_R_Ch, long pulsetime,int bargraph, int odopulse_detected, int crankpulse_detected )
{
    static int bargraph_cycle = 0;
    static int odo_on         = FALSE;
    static int crank_on       = FALSE;

    int        dots;
    int        pdots = 0;
    int        level_L;
    int        level_R;

    char       bargraphstring[80];

    if ( bargraph == 0 ) return;

    // Keep pulse-detected flag in case of display skipped.

    if ( odopulse_detected )   odo_on   = TRUE;
    if ( crankpulse_detected ) crank_on = TRUE;  

    if ( ++bargraph_cycle >= bargraph )
    {
        // It's time to display the bargraph.

        bargraph_cycle= 0;

        level_L = *p_L_Ch * 20 / SHRT_MAX + 10;
        level_R = *p_R_Ch * 20 / SHRT_MAX + 10;
          
        bargraphstring[ pdots++ ] = '|';
        bargraphstring[ pdots++ ] = '|';
        for ( dots =  0; dots < 20; dots ++)
        {
            if ( dots == level_L )
            {
                bargraphstring[ pdots++ ] = '*';
            }
            else
            {
                bargraphstring[ pdots++ ] = ' ';
            }
        } 
        bargraphstring[ pdots++ ] = '|';
        bargraphstring[ pdots++ ] = '|';
 
        for ( dots =  0; dots < 20; dots ++)
        {
            if ( dots == level_R )
            {
                bargraphstring[ pdots++ ] = '*';
            }
            else
            {
                bargraphstring[ pdots++ ] = ' ';
            }
        } 
        bargraphstring[ pdots++ ] = '|';
        bargraphstring[ pdots++ ] = '|';

        if ( odo_on )
        {              
            bargraphstring[ pdots++ ] = 'O';
            bargraphstring[ pdots++ ] = 'D';
            bargraphstring[ pdots++ ] = 'O';
        }
        else
        {
            bargraphstring[ pdots++ ] = ' ';
            bargraphstring[ pdots++ ] = ' ';
            bargraphstring[ pdots++ ] = ' ';
        }

        bargraphstring[ pdots++ ] = ':';

        if ( crank_on )
        {              
            bargraphstring[ pdots++ ] = 'E';
            bargraphstring[ pdots++ ] = 'N';
            bargraphstring[ pdots++ ] = 'G';
        }
        else
        {
            bargraphstring[ pdots++ ] = ' ';
            bargraphstring[ pdots++ ] = ' ';
            bargraphstring[ pdots++ ] = ' ';
        }
         
        bargraphstring[ pdots++ ] = EOS;
        printf( "%07d %06d %06d : %s\n", pulsetime, *p_L_Ch, *p_R_Ch, bargraphstring );

        odo_on   = FALSE;
        crank_on = FALSE;
    }
}


/******************************************************************************
** FUNCTION
**    ezp_DetectOdopulse()
**
******************************************************************************/
#define ODOPULSE_ON   1
#define ODOPULSE_OFF  0

PRIVATE int ezp_DetectOdopulse( EZPDataObject *p_ezp_data, int operation, long data, long pulsetime)
{
    static int odopulse_state = ODOPULSE_OFF;
    static int odopulse_count = 0;
    static int samplecount    = 0;
    static long prev_pulsetime = 0;
    static int duration=0;
    int        result         = FALSE;
    static int odopulse_period = 0;
    static int filecount = 0;
    
    switch( operation )
    {
    case EZP_ODOPULSE_RESET:
        odopulse_state = ODOPULSE_OFF;
        odopulse_count = 0;
        samplecount    = 0;
        duration       = 0;
        break;

    case EZP_ODOPULSE_DETECT:

        samplecount++;

        switch( odopulse_state )
        {
        case ODOPULSE_OFF:
            if ( abs(data) > EZP_ODOPULSE_LEVEL_ON_THRESHOLD )
            {
                if ( ++duration > EZP_ODOPULSE_TIME_ON_THRESHOLD )
                {
                    /* Now we have odopulse. */
                    odopulse_state = ODOPULSE_ON;
                    duration=0;
                }
            }
            else
            {
                if ( duration > 0 ) duration--;
            }
            break;

        case ODOPULSE_ON:
            if ( abs(data) < EZP_ODOPULSE_LEVEL_OFF_THRESHOLD )
            {
                if( ++duration > EZP_ODOPULSE_TIME_OFF_THRESHOLD )
                {
                    /* Now odopulse ceased. */
                    // printf( "EZP: pulsetime=%08d samplecount=%08d\n", pulsetime, samplecount );

                    if ( samplecount < 1000 )
                    {
                        // This may be a double click.
                        odopulse_state  = ODOPULSE_OFF;
                        duration        = 0;
                    }
                    else
                    {
                        // Normal click.
                        odopulse_period = samplecount;
                        samplecount     = 0;
                        odopulse_count++;                  
                        odopulse_state  = ODOPULSE_OFF;
                        duration        = 0;
                        result          = TRUE;
                    }          
                }
            }
            else
            {
                duration = 0;
            }
            break;
       default:
            odopulse_state=ODOPULSE_OFF;
            duration = 0;
            break;
       }
       break;
      
    default:
       break;
    }
  
    if ( EZP_IS_DISPLAY_ODOINFO_ON() )
    {
        static int last_valid_report_pulsetime = 0;
        if ( result == TRUE )
        {
            // We have detected the odo pulse.

            // Do we have output file opened?
            if ( p_ezp_data->odometerfile == NULL )
            {
                // No odometer output file is opened. Open it now.
                // Here the data is not output, since it has no time difference available.

                char odometerfilename[512+6];

                sprintf( odometerfilename, "%s.odo%02d", p_ezp_data->filename, ++filecount );
 
                if ( NULL == ( p_ezp_data->odometerfile = fopen( odometerfilename, "wt" ) ) )
                {
                    fprintf( stderr, "EZP: cannot open %s for writing.\n", odometerfilename );
                }
                else
                {
                    fprintf( stderr, "EZP: File %s opened.\n", odometerfilename );
                }
            }
            else
            {
                fprintf( p_ezp_data->odometerfile, "%08d %06d\n", pulsetime, odopulse_period );
                // printf( "%08d %06d\n", pulsetime, odopulse_period );
            }

            last_valid_report_pulsetime  = pulsetime;

        }
        else
        {
            // No odopulse detected.
          
            if ( ( pulsetime - last_valid_report_pulsetime ) >= ( 5000 * MSEC ) )
            {
                // No odopulse detected for more than 5 seconds.
                if ( p_ezp_data->odometerfile )
                {
                    fclose ( p_ezp_data->odometerfile );
                    p_ezp_data->odometerfile = NULL;
                }
            }
        }
    }
  
#ifdef ODOMETER_PERIODIC_REPORT
    if ( EZP_IS_DISPLAY_ODOINFO_ON() )
    {
        // Check elapsed time.
        if ( ( pulsetime - prev_pulsetime ) >= EZP_ODOPULSE_REPORTPERIOD )
        {
            static int last_valid_report_pulsetime = 0;
            int report_period;

            // Now it's time to report the odometer reading.
            if ( odopulse_count != 0 )
            {
                report_period = odopulse_period;
                last_valid_report_pulsetime = pulsetime;

                if ( p_ezp_data->odometerfile == NULL )
                {
                    // No odometer output file is opened. Open it now.

                    char odometerfilename[512+6];

                    sprintf( odometerfilename, "%s.odo%02d", p_ezp_data->filename, ++filecount );
 
                    if ( NULL == ( p_ezp_data->odometerfile = fopen( odometerfilename, "wt" ) ) )
                    {
                        fprintf( stderr, "EZP: cannot open %s for writing.\n", odometerfilename );
                    }
                    else
                    {
                        fprintf( stderr, "EZP: File %s opened.\n", odometerfilename );
                    } 
                }
            }
            else
            {
                // No odopulse detected.
                if ( ( pulsetime - last_valid_report_pulsetime ) >= ( 3000 * MSEC ) )
                {
                    // No odopulse detected for more than 3 seconds.
                    report_period = 0;
                }
                else
                {
                    // No odopulse detected for less than 3 seconds. Report prev value.
                    report_period = odopulse_period;
                }
            }

            if ( p_ezp_data->odometerfile != NULL )
            {
                fprintf( p_ezp_data->odometerfile, "%06d %06d\n", PULSETIME_TO_TENTH_SECONDS(pulsetime), report_period );
                if ( ( pulsetime - last_valid_report_pulsetime ) >= ( 5000 * MSEC ) )
                {
                    // No odopulse detected for more than 5 seconds.
                    fclose ( p_ezp_data->odometerfile );
                    p_ezp_data->odometerfile = NULL;
                }
            }

            prev_pulsetime = pulsetime;
            odopulse_count = 0;
        }
    }
#endif
    return( result ); 
}

/******************************************************************************
** FUNCTION
**      ezp_DetectCrankPulse()
**
******************************************************************************/
#define CRANK_POSITIVE   1
#define CRANK_NEGATIVE   0

PRIVATE int ezp_DetectCrankpulse( EZPDataObject *p_ezp_data, int operation, long data, long pulsetime )
{
    static int pulse_state = CRANK_NEGATIVE;
    static int duration=0;
    static int prev_data;
    static int delta_sum;
    static int crankpulse_count = 0;
    static long prev_pulsetime = 0;

    int delta;

    delta = data - prev_data;
    prev_data = data;
    
    switch( operation )
    {
    case EZP_CRANKPULSE_RESET:
        pulse_state=CRANK_NEGATIVE;
        duration = 0;
        break;

    case EZP_CRANKPULSE_DETECT:
        switch( pulse_state )
        {
        case CRANK_NEGATIVE:
            delta_sum += delta;
            if ( delta > EZP_CRANK_POSITIVE_THRESHOLD )
            {
                if ( delta_sum > EZP_CRANK_POSITIVE_DELTASUM_THRESHOLD2 )
                {
                    if ( ++duration > EZP_CRANK_TIME_THRESHOLD2 )
                    {
                        /* Now we have positive pulse. */
                        pulse_state = CRANK_POSITIVE;
                        delta_sum = 0;
                        duration=0;
                        crankpulse_count++;
                        return( TRUE );
                    }
                }
                else if ( delta_sum > EZP_CRANK_POSITIVE_DELTASUM_THRESHOLD1 )
                {
                    if ( ++duration > EZP_CRANK_TIME_THRESHOLD1 )
                    {
                        /* Now we have positive pulse. */
                        pulse_state = CRANK_POSITIVE;
                        delta_sum = 0;
                        duration=0;
                        crankpulse_count++;
                        return( TRUE );
                    }
                }
            }
            else
            {
                delta_sum = 0;
                duration = 0;
            }
            break;

        case CRANK_POSITIVE:
            delta_sum += delta;
            if ( delta < EZP_CRANK_NEGATIVE_THRESHOLD )
            {
                if ( delta_sum < EZP_CRANK_NEGATIVE_DELTASUM_THRESHOLD2 )
                { 
                    if( ++duration > EZP_CRANK_TIME_THRESHOLD2 )
                    {
                        /* Now we have negative pulse. */
                        pulse_state=CRANK_NEGATIVE;
                        delta_sum  =0;
                        duration=0;
                    
                    }
                }
                else if ( delta_sum < EZP_CRANK_NEGATIVE_DELTASUM_THRESHOLD1 )
                { 
                    if( ++duration > EZP_CRANK_TIME_THRESHOLD1 )
                    {
                        /* Now we have negative pulse. */
                        pulse_state=CRANK_NEGATIVE;
                        delta_sum  =0;
                        duration=0;
                    
                    }
                }
            }
            else
            {
                delta_sum = 0;
                duration = 0;
            }
            break;
       default:
            pulse_state=CRANK_NEGATIVE;
            duration = 0;
            break;
       }
       break;
    default:
       break;
    }

    if ( EZP_IS_DISPLAY_CRANKINFO_ON() )
    {
        // Check elapsed time.
        if ( ( pulsetime - prev_pulsetime ) >= EZP_CRANK_REPORTPERIOD )
        {
            static int last_valid_report_pulsetime = 0;
            static int filecount = 0;

            // Now it's time to report the crank speed.
            if ( crankpulse_count != 0 )
            {
                last_valid_report_pulsetime = pulsetime;

                if ( p_ezp_data->crankfile == NULL )
                {
                    // No crank output file is opened. Open it now.

                    char crankfilename[512+6];

                    sprintf( crankfilename, "%s.eng%02d", p_ezp_data->filename, ++filecount );
 
                    if ( NULL == ( p_ezp_data->crankfile = fopen( crankfilename, "wt" ) ) )
                    {
                        fprintf( stderr, "EZP: cannot open %s for writing.\n", crankfilename );
                    }
                    else
                    {
                        fprintf( stderr, "EZP: File %s opened.\n", crankfilename );
                    } 
                }
            }

            if ( p_ezp_data->crankfile != NULL )
            {
                fprintf( p_ezp_data->crankfile, "%06d %06d\n", PULSETIME_TO_TENTH_SECONDS(pulsetime), crankpulse_count );
                if ( ( pulsetime - last_valid_report_pulsetime ) >= ( 3000 * MSEC ) )
                {
                    // No odopulse detected for more than 3 seconds.
                    fclose ( p_ezp_data->crankfile );
                    p_ezp_data->crankfile = NULL;
                }
            }

            prev_pulsetime = pulsetime;
            crankpulse_count = 0;
        }
    }

    return( FALSE );
}


/******************************************************************************
** FUNCTION
**      ezp_GetPulseTime()
**
** DESCRIPTION
**      Get the sample index from the EZP data object, this function calculates
**    the elapsed time measured from the beginning of the WAV file. The unit
**    is 0.1 mSec, i.e. 10000 is equal to 1 second.
**
** REVISION HISTORY
**  dd-mmm-yyyy  AUTHOR      DESCRIPTION
**  28-Jul-2000  dan       - Re-written. Now the function is practicaly
**                           acculate without overflow.
**
******************************************************************************/
PRIVATE long ezp_GetPulseTime( EZPDataObject *p_ezp_data )
{
    long pulsetime;
    long sampleindex    =        p_ezp_data->sampleindex_in_file;
    long nSamplesPerSec = (long)(p_ezp_data->wav_header.wavFormatChunk.nSamplesPerSec );

    pulsetime =   ( sampleindex / nSamplesPerSec ) * 10000;
    pulsetime += (( sampleindex % nSamplesPerSec ) * 10000 / nSamplesPerSec);

    return( pulsetime );

}


/******************************************************************************
** FUNCTION
**      ezp_ProcessWaveData()
**
******************************************************************************/
PRIVATE EZPOptionObject * ezp_GetOptionObject( void )
{
    return( &ezp_option );
}

