unit Fileio;
{ ======================================================================
  WinWCP - File Input/Output routines
  (c) J.Dempster, University of Strathclyde 1996-97. All Rights Reserved
  ======================================================================
  4/6/97 Error in retaining Seal test amplitude fixed
  7/6/97 ADCAmplifierGain now correctly included in scale factor
  16/16/97 ... WriteToLogFileNoDate added
  25/6/97 V1.6 Extra sealtest settings added to support 3 pulses
  27/6/97 V1.6c SealTest.AutoScale & SealTest.DisplayScale added
                to INI file
  8/7/97 V1.6e ADCAmplifierGain now handled correctly when scaling signals
  8/9/97 V1.7b EventDetector.PreTrigger added to INI file
  6/1/98 V1.8a LogFIleAvailable initialised to FALSE
  20/1/98 V1.8b Seal Test Free run status included in INI file
  26/2/98 V1.8c ... Zero levels from WCP for DOS files now retained
  5/5/98 V2.0b Size of array increased in SaveInitializationFile and
         LoadInitializationFile to avoids Array Full error when
         6 channels are used.
  25/6/99 V2.3a ADCVoltageRange = 0.0 trapped and corrected
          Problem arises when WinWCP files are analysed by WCP for DOS
  14/7/99 V3.0 Converted to 32 bit
  4/8/99 ... 2048 offset removed from WCP data files
             GetRecord/PutRecord now use GetRecordHeaderOnly/PutRecordHeaderOnly
             to retrieve/store record header information
  14/8/99 ... Normalisation error in light gaussian filtering fixed
              record sampling interval (RH.dt) now stored in (s) rather than (ms)
  17/8/99 ... BMAPW and BMAPH replaced by PLMFW and PLMFH
  12/3/00 ... Settings.DifferentiationMode added to INI file
  19/7/00 ... scDisplay.ADCScale now updated every time record is changed.
  14/9/00 ... GetHeader updated (error reading single record files fixed)
  13/2/01 ... ADC data range now adjusted automatically when 12/16 bit WCP data files
              are opened. Lab. interface may be disabled as a consequence.
  3/9/01 .... Fixed/From record zero level settings now saved in initialisation file
  12/2/3 .... Settings.TimeCalBar & Settings.BarValues added to INI file
              FileCloseSafe() procedure added
  24/06/03 .. NumHorizontalGridLines & NumVerticalGridLines added to INI file
  03.02.04 .. DACInvertTriggerLevel settings to INI file (DACINVTRIG)
  17.03.04 .. GetHeader & SaveHeader now load/save MaxADCValue (not SESLabIO.ADCMaxValue)
  27.07.04 ... Settings.ExternalTriggerActiveHigh added to initialisation file
  11.12.04 ... GetHeader modified to correctly detect file header length of V2.3 WCP files
  04.07.05 ... Display zoom min/max ranges now only changed when A/D resolution,
               no. of samples or channels changed
  29/7/5 ..... Settings.ADCInputMode (LABADIP=) added to ini file
  03/08/06 ... Settings.NumChannels Settings.NumSamples, Settings.ADCVoltageRangeIndex
               Settings.RecordDuration added to INI file
  07/12/06 ... GetRecord32 function added
  05/9/07 ... No. longer reports that the log file cannot be opened
              File header array full warning only given once
  16/12/8 ... Memory violation error prevented check for 0 character made
              on zero length channel name or unit strings. Blank channel names and units now possible
  10/02/9 ... winwcp.ini file size increased to 10000 bytes to avoid "Header Full" error
              when long data file paths used.
  11/02/9 ... Settings.VProtDirectory added (Settings.SectorWriteTime removed)
  07/12/9 ... Non-stationary variance settings added to WCP file header
  }


interface

uses shared,global,sysUtils,dialogs, maths, math, seslabio ;

procedure SaveHeader( fHDR : TFileHeader ) ;
procedure GetHeader( var fHDR : TFileHeader ) ;
procedure LoadInitializationFile( const IniFileName : string ) ;
procedure SaveInitializationFile( const IniFileName : string ) ;
procedure PutRecord ( var fHDR : TFileHeader;
                     var rH : TRecHeader ;
                     RecordNum : Integer ;
                     var dBuf : Array of SmallInt ) ;
procedure PutRecordHeaderOnly( var fHDR : TFileHeader;
                               Const rH : TRecHeader ;
                               RecordNum : Integer ) ;
procedure GetRecord( var fHDR : TFileHeader;
                     var rH : TRecHeader ;
                     RecordNum : Integer ;
                     var dBuf : Array of SmallInt ) ;

procedure GetRecord32( var fHDR : TFileHeader;
                       var rH : TRecHeader ;
                       RecordNum : Integer ;
                       var dBuf : Array of Integer ) ;

procedure GetRecordHeaderOnly( var fHDR : TFileHeader;
                               var rH : TRecHeader ;
                               RecordNum : Integer ) ;
function CalibFactorToADCScale( var RH : TRecHeader ; Ch : Integer ) : single ;

procedure OpenLogFile ;
procedure WriteToLogFile( Line : string ) ;
procedure WriteToLogFileNoDate( Line : string ) ;
procedure CloseLogFile ;
procedure FileCloseSafe( var FileHandle : Integer ) ;

procedure GaussianFilter( const FHdr : TFileHeader ;
                           Var Buf : Array of SmallInt ;
                           CutOffFrequency : single ) ;

function FileOverwriteCheck( var FileName : string ) : boolean ;

implementation

uses mdiform, AmpModule ;

const
    INIFileSize = 10000 ;

var
   LogFile : TextFile ;
   LogFileAvailable : boolean ;


procedure SaveHeader(
          fHDR : TFileHeader
          ) ;
{ ---------------------------------------
  Save file header data to WCP data file
  ---------------------------------------}
var
   Header : array[1..NumBytesInFileHeader] of char ;
   i : Integer ;
   ch : Integer ;
begin
     { Initialise empty header buffer with zero bytes }
     for i := 1 to sizeof(Header) do Header[i] := chr(0) ;
     HeaderArrayFull := False ;

     fHDR.Version := FileVersion ;
     AppendFloat( Header, 'VER=',fHDR.Version );
     AppendString( Header, 'CTIME=', fHDR.CreationTime ) ;
     AppendInt( Header, 'NBH=', NumBytesInFileHeader ) ;

     // 13/2/02 Added to distinguish between 12 and 16 bit data files
     AppendInt( Header, 'ADCMAX=', MaxADCValue ) ;

     AppendInt( Header, 'NC=', fHDR.NumChannels ) ;
     fHDR.NumDataBytesPerRecord := fHDR.NumSamples*fHDR.NumChannels*2 ;
     fHDR.NumAnalysisBytesPerRecord := NumAnalysisBytesPerRecord ;
     fHDR.NumBytesPerRecord := fHDR.NumAnalysisBytesPerRecord +
                               fHDR.NumDataBytesPerRecord ;
     AppendInt( Header, 'NBA=', fHDR.NumAnalysisBytesPerRecord div 512 ) ;
     AppendInt( Header, 'NBD=', fHDR.NumDataBytesPerRecord div 512 ) ;

     AppendFloat( Header, 'AD=', fHDR.ADCVoltageRange ) ;

     AppendInt( Header, 'NR=', fHDR.NumRecords ) ;
     AppendFloat( Header, 'DT=',fHDR.dt );

     AppendInt( Header, 'NZ=', fHDR.NumZeroAvg ) ;

     for ch := 0 to fHDR.NumChannels-1 do begin
         AppendInt( Header, format('YO%d=',[ch]), Channel[ch].ChannelOffset) ;
         AppendString( Header, format('YU%d=',[ch]), Channel[ch].ADCUnits ) ;
         AppendString( Header, format('YN%d=',[ch]), Channel[ch].ADCName ) ;
         AppendFloat( Header, format('YG%d=',[ch]), Channel[ch].ADCCalibrationFactor ) ;
         AppendInt( Header, format('YZ%d=',[ch]), Channel[ch].ADCZero) ;
         AppendInt( Header, format('YR%d=',[ch]), Channel[ch].ADCZeroAt) ;
         end ;

     // T.(x%) waveform measurement percentage decay time
     AppendFloat( Header, 'TXPERC=', fHDR.DecayTimePercentage ) ;

     // Non-stationary variance analysis settings
     AppendInt( Header, 'NSVCHAN=', fHDR.NSVChannel ) ;
     AppendInt( Header, 'NSVALIGN=', fHDR.NSVAlignmentMode ) ;
     AppendInt( Header, 'NSVTYPR=', fHDR.NSVType ) ;
     AppendLogical( Header, 'NSVS2P=', fHDR.NSVScaleToPeak ) ;
     AppendInt( Header, 'NSVCUR0=', fHDR.NSVAnalysisCursor0 ) ;
     AppendInt( Header, 'NSVCUR1=', fHDR.NSVAnalysisCursor1 ) ;

     { Experiment identification line }
     AppendString( Header, 'ID=', fHDR.IdentLine ) ;

     FileSeek( fHDR.FileHandle, 0, 0 ) ;
     if FileWrite( fHDR.FileHandle, Header, Sizeof(Header) )
        <> Sizeof(Header) then
        ShowMessage( ' File Header Write - Failed ' ) ;

     { Add Names of channels to list }
     ChannelNames.Clear ;
     for ch := 0 to fHDR.NumChannels-1 do
         ChannelNames.Add( format('Ch.%d %s',[ch,Channel[ch].ADCName]) ) ;

     if HeaderArrayFull then
        ShowMessage( fHDR.FileName + ' File header parameter array full!' ) ;

     end ;


procedure GetHeader(
          var fHDR : TFileHeader
          ) ;
{ -----------------------------------------------------------
  Read file header block from data file,
  decode parameter list, and put into FileHeader record
  26/2/98 ... Zero levels from WCP for DOS files now retained
  -----------------------------------------------------------}

const
     NumBytesPerSector = 512 ;
var
   Header : array[1..1024] of char ;
   RecordStatusArray : array[0..3] of char ;
   RecordStatus : string ;
   i,NumBytesInFile,NumRecords,FilePointer,ch,RecordCounter : Integer ;
   iValue : Integer ;
   Done, OK : Boolean ;
   NumSectors : Integer ;
   ResetDisplayMagnification : Boolean ;
   OldValue : Integer ;
begin
     OK := True ;

     // Default file header/analysis block sizes
     fHDR.NumBytesInHeader := 1024 ;
     fHDR.NumAnalysisBytesPerRecord := 1024 ;
     // Set bytes/record to 0 to force calculation later
     fHDR.NumBytesPerRecord := 0 ;

     { Determine size of file header }
     FilePointer := 0 ;
     RecordCounter := 0 ;
     Done := False ;
     while not Done do begin

         { Get WCP 8 byte data record status field }
         FileSeek( fHDR.FileHandle, FilePointer, 0 ) ;
         FileRead( fHDR.FileHandle, RecordStatusArray, 4 ) ;
         CopyArrayToString(RecordStatus,RecordStatusArray) ;

         { Is it a record status field }
         if (RecordStatus = 'ACCE') or
            (RecordStatus = 'REJE') or
            (RecordStatus = 'acce') or
            (RecordStatus = 'reje') then begin
            Inc(RecordCounter) ;
            if RecordCounter = 1 then begin
               fHDR.NumBytesInHeader := FilePointer ;
               fHDR.NumBytesPerRecord := FileSeek(fHDR.FileHandle,0,2) - fHDR.NumBytesInHeader ;
               end
            else if  RecordCounter = 2 then begin
               fHDR.NumBytesPerRecord := FilePointer - fHDR.NumBytesInHeader  ;
               Done := True ;
               end ;
            end ;
         { Exit if end of file reached }
         FilePointer := FilePointer + NumBytesPerSector ;
         if FilePointer > FileSeek(fHDR.FileHandle,0,2) then Done := True ;
         end ;

     { Read file header }
     FileSeek( fHDR.FileHandle, 0, 0 ) ;
     for i := 0 to High(Header) do Header[i] := #0 ;
     if FileRead( fHDR.FileHandle, Header, fHDR.NumBytesInHeader )
        = fHDR.NumBytesInHeader then begin

          ReadFloat( Header, 'VER=',fHDR.Version );
          ReadString( Header, 'CTIME=', fHDR.CreationTime ) ;

          OldValue := fHDR.NumChannels ;
          ReadInt( Header, 'NC=', fHDR.NumChannels ) ;
          if fHDR.NumChannels <> OldValue then ResetDisplayMagnification := True ;

          OldValue := MaxADCValue ;
          MaxADCValue := 0 ;
          ReadInt( Header, 'ADCMAX=', MaxADCValue ) ;
          if MaxADCValue = 0 then MaxADCValue := 2047 ;
          MinADCValue := -MaxADCValue -1 ;
          if OldValue <> MaxADCValue then ResetDisplayMagnification := True ;

          // Analysis block size
          NumSectors := fHDR.NumAnalysisBytesPerRecord div 512 ;
          ReadInt( Header, 'NBA=', NumSectors ) ;
          fHDR.NumAnalysisBytesPerRecord := NumSectors*512 ;

          ReadInt( Header, 'NBD=', fHDR.NumDataBytesPerRecord ) ;
          fHDR.NumDataBytesPerRecord := fHDR.NumDataBytesPerRecord*NumBytesPerSector ;
          { Compute other record size parameters, handling situation where
            overall record size is not known }
          if fHDR.NumBytesPerRecord > 0 then begin
             fHDR.NumAnalysisBytesPerRecord := fHDR.NumBytesPerRecord
                                               - fHDR.NumDataBytesPerRecord ;
             end
          else begin
             fHDR.NumBytesPerRecord := fHDR.NumDataBytesPerRecord
                                       + fHDR.NumAnalysisBytesPerRecord ;
             end ;

          fHDR.NumSamplesPerRecord := fHDR.NumDataBytesPerRecord div 2 ;
          OldValue := fHDR.NumSamples ;
          fHDR.NumSamples := fHDR.NumSamplesPerRecord div fHDR.NumChannels ;
          if OldValue <> fHDR.NumSamples then ResetDisplayMagnification := True ;

          ReadFloat( Header, 'AD=',fHDR.ADCVoltageRange);

          ReadInt( Header, 'NR=', fHDR.NumRecords ) ;

          { Fix files which accidentally lost their record count }
          if fHDR.NumRecords = 0 then begin
             NumBytesInFile := FileSeek( FHDR.FileHandle, 0, 2 ) ;
             NumRecords := (NumBytesInFile - SizeOf(Header)) div fHDR.NumBytesPerRecord ;
             if NumRecords > 0 then begin
                ShowMessage( ' Number of records in file corrected.' ) ;
                fHDR.NumRecords := NumRecords ;
                end ;
             end ;

          ReadFloat( Header, 'DT=', fHDR.dt );

          ReadInt( Header, 'NZ=', fHDR.NumZeroAvg ) ;

          { Read channel scaling data }

          for ch := 0 to fHDR.NumChannels-1 do begin

              { Channels are mapped in ascending order by WCP for DOS (Version<6.0)
                and descending order by WinWCP. Data file Versions 6.1 and later
                have channel mapping explicitly saved in YO0= ... YO1 etc parameter}
              if (fHdr.Version >= 6.0) or (fHdr.Version = 0.0) then
                 Channel[ch].ChannelOffset := fHDR.NumChannels - 1 - ch
              else
                 Channel[ch].ChannelOffset := ch ;

              ReadInt( Header, format('YO%d=',[ch]), Channel[ch].ChannelOffset) ;

              Channel[ch].ADCUnits := '' ;
              ReadString( Header, format('YU%d=',[ch]) , Channel[ch].ADCUnits ) ;
              { Fix to avoid strings with #0 in them }
              if Length(Channel[ch].ADCUnits) >= 1 then
                 if Channel[ch].ADCUnits[1] = chr(0) then Channel[ch].ADCUnits := ' ' ;
              Channel[ch].ADCName := 'Ch.' + IntToStr(ch) ;

              ReadString( Header, format('YN%d=',[ch]), Channel[ch].ADCName ) ;
              { Fix to avoid strings with #0 in them }
              if Length(Channel[ch].ADCName) >= 1 then
                 if Channel[ch].ADCName[1] = chr(0) then Channel[ch].ADCName := ' ' ;

              ReadFloat( Header, format('YG%d=',[ch]), Channel[ch].ADCCalibrationFactor) ;

              { Zero level (in fixed mode) }
              ReadInt( Header, format('YZ%d=',[ch]), Channel[ch].ADCZero) ;
              { Start of zero level reference samples (-1 = fixed zero) }
              ReadInt( Header, format('YR%d=',[ch]), Channel[ch].ADCZeroAt) ;

              { Special treatment for old WCP for DOS data files}
              if fHDR.Version < 6.0 then begin

                 { Remove 2048 offset from zero level }
                 Channel[ch].ADCZero := Channel[ch].ADCZero {- 2048} ;
                 { Decrement reference position because WinWCP samples start at 0}
                 Dec(Channel[ch].ADCZeroAt) ;
                 end ;

              // Reset display magnification (if required)
              if ResetDisplayMagnification then begin
                 Channel[ch].yMax := MaxADCValue ;
                 Channel[ch].yMin := MinADCValue ;
                 Channel[0].xMin := 0 ;
                 Channel[0].xMax := fHDR.NumSamples-1 ;
                 end ;

              end ;

          // T.(x%) waveform measurement percentage decay time
          fHDR.DecayTimePercentage := 50.0 ; // Default value
          ReadFloat( Header, 'TXPERC=', fHDR.DecayTimePercentage ) ;

          // Non-stationary variance analysis settings
          ReadInt( Header, 'NSVCHAN=', fHDR.NSVChannel ) ;
          ReadInt( Header, 'NSVTYPR=', fHDR.NSVType ) ;
          ReadInt( Header, 'NSVALIGN=', fHDR.NSVAlignmentMode ) ;
          ReadLogical( Header, 'NSVS2P=', fHDR.NSVScaleToPeak ) ;
          ReadInt( Header, 'NSVCUR0=', fHDR.NSVAnalysisCursor0 ) ;
          ReadInt( Header, 'NSVCUR1=', fHDR.NSVAnalysisCursor1 ) ;

          { Experiment identification line }
          ReadString( Header, 'ID=', fHDR.IdentLine ) ;

           { Add names of channels to list }
          ChannelNames.Clear ;
          for ch := 0 to fHDR.NumChannels-1 do
              ChannelNames.Add( format('Ch.%d %s',[ch,Channel[ch].ADCName] )) ;

          end
     else
          ShowMessage( ' File Header Read - Failed ' ) ;

     // Clear this flag to ensure display magnification remains unchanged
     ResetDisplayMagnification := False ;

     end ;


procedure LoadInitializationFile( const IniFileName : string ) ;
{ ---------------------------------------------------------
  Read Initialization file to get initial program settings,
  e.g. the name of the last data file used
  ---------------------------------------------------------}
var
   Header : array[1..INIFileSize] of char ;
   i,ch,IniFileHandle : Integer ;
   AmpType, GainChannel, ModeChannel : Integer ;
begin
     if FileExists( IniFileName ) then begin
        IniFileHandle := FileOpen( IniFileName, fmOpenReadWrite ) ;

        if FileRead(IniFileHandle,Header,Sizeof(Header)) > 0 then begin
           { Last raw data file used }
           ReadString( Header, 'FILE=', RawFH.FileName ) ;
           { Last averages data file used }
           AvgFH.Filename := '' ;
           ReadString( Header, 'FILEAVG=', AvgFH.FileName ) ;
           { Last leak subtracted data file used }
           LeakFH.Filename := '' ;
           ReadString( Header, 'LEAKAVG=', LeakFH.FileName ) ;

           // Get default recording settings
           ReadInt( Header, 'NC=', Settings.NumChannels ) ;
           Settings.NumChannels := Max( 1,Settings.NumChannels ) ;
           ReadInt( Header, 'NSAMP=', Settings.NumSamples ) ;
           ReadInt( Header, 'ADCVRI=', Settings.ADCVoltageRangeIndex ) ;
           ReadFloat( Header, 'RECDUR=', Settings.RecordDuration ) ;

           { CED 1902 amplifier settings }
           ReadInt( Header, 'CEDI=', Amplifier.CED1902.Input ) ;
           ReadInt( Header, 'CEDG=', Amplifier.CED1902.Gain ) ;
           ReadFloat( Header, 'CEDGV=', Amplifier.CED1902.GainValue ) ;
           ReadInt( Header, 'CEDLP=', Amplifier.CED1902.LPFilter ) ;
           ReadInt( Header, 'CEDHP=', Amplifier.CED1902.HPFilter ) ;
           ReadInt( Header, 'CEDAC=', Amplifier.CED1902.ACCoupled ) ;
           ReadInt( Header, 'CEDDCO=', Amplifier.CED1902.DCOffset ) ;
           ReadInt( Header, 'CEDNF=', Amplifier.CED1902.NotchFilter ) ;
           ReadInt( Header, 'CEDPO=', Amplifier.CED1902.ComPort ) ;

           // Patch clamp amplifier data

           ModeChannel := -1 ;

           AmpType := amNone ;
           ReadInt( Header, 'AMP=',AmpType ) ;
           ReadInt( Header, 'AMPCH=',GainChannel ) ;
           Amplifier.AmplifierType[1] := AmpType ;
           Amplifier.GainTelegraphChannel[1] := GainChannel ;
           Amplifier.ModeTelegraphChannel[1] := ModeChannel ;

           ReadInt( Header, 'AMP1=',AmpType ) ;
           ReadInt( Header, 'AMP1CH=',GainChannel ) ;
           ReadInt( Header, 'AMP1GAINCH=',GainChannel ) ;
           ReadInt( Header, 'AMP1MODECH=',ModeChannel ) ;
           Amplifier.AmplifierType[1] := AmpType ;
           Amplifier.GainTelegraphChannel[1] := GainChannel ;
           Amplifier.ModeTelegraphChannel[1] := ModeChannel ;

           AmpType := amNone ;
           ReadInt( Header, 'AMP2=',AmpType ) ;
           ReadInt( Header, 'AMP2CH=',GainChannel ) ;
           ReadInt( Header, 'AMP2GAINCH=',GainChannel ) ;
           ReadInt( Header, 'AMP2MODECH=',ModeChannel ) ;
           Amplifier.AmplifierType[2] := AmpType ;
           Amplifier.GainTelegraphChannel[2] := GainChannel ;
           Amplifier.ModeTelegraphChannel[2] := ModeChannel ;

           { Send new values to CED 1902 }
          { if Amplifier.CED1902.InUse then SetCED1902(Amplifier.CED1902) ;}

           { Read global settings }

           { Get No. channels & No samples/channel }
           ReadInt( Header, 'NC=', RawFH.NumChannels ) ;
           RawFH.NumChannels := MaxInt( [1,RawFH.NumChannels] ) ;
           ReadInt( Header, 'NBD=', RawFH.NumDataBytesPerRecord ) ;
           RawFH.NumDataBytesPerRecord := RawFH.NumDataBytesPerRecord * 512 ;
           RawFH.NumSamplesPerRecord := RawFH.NumDataBytesPerRecord div 2 ;
           RawFH.NumSamples := RawFH.NumSamplesPerRecord div RawFH.NumChannels ;

           {ReadInt( Header, 'NBA=', i ) ;}
           RawFH.NumAnalysisBytesPerRecord := NumAnalysisBytesPerRecord ;

           RawFH.NumBytesPerRecord := RawFH.NumDataBytesPerRecord
                                     + RawFH.NumAnalysisBytesPerRecord ;


           { *** Recording settings *** }
           { Recording trigger mode }
           ReadString( Header, 'TRG=', Settings.TriggerMode ) ;
           { Event detector, channel and % threshold level }
           ReadInt( Header, 'DETCH=', Settings.EventDetector.Channel ) ;
           ReadFloat( Header, 'DETTH=', Settings.EventDetector.Threshold ) ;
           ReadFloat( Header, 'DETPT=', Settings.EventDetector.PreTrigger ) ;
           ReadLogical( Header, 'EXTTRIGAH=', Settings.ExternalTriggerActiveHigh ) ;

           { Display auto-erase mode }
           ReadLogical( Header, 'AER=', Settings.AutoErase ) ;
           { Number of records required (in free run/ext. trigger/detect modes}
           ReadInt( Header, 'NRQ=', Settings.NumRecordsRequired ) ;
           { Currently selected voltage program }
           ReadString( Header, 'FILEVPR=', Settings.VProgramFileName ) ;

           { Voltage clamp command voltage division factor }
           ReadFloat( Header, 'VCDIV=', Settings.DAC[0].DivideFactor ) ;
           ReadFloat( Header, 'VCDIV0=', Settings.DAC[0].DivideFactor ) ;
           ReadFloat( Header, 'VCDIV1=', Settings.DAC[1].DivideFactor ) ;

           { Default voltage clamp holding potential setting }
           ReadFloat( Header, 'VCHOLD=', Settings.DAC[0].HoldingVoltage ) ;
           ReadFloat( Header, 'VCHOLD0=', Settings.DAC[0].HoldingVoltage ) ;
           ReadFloat( Header, 'VCHOLD1=', Settings.DAC[1].HoldingVoltage ) ;

           { Default digital control port output byte setting }
           ReadInt( Header, 'DIGPORT=', Settings.DigitalPort.Value ) ;
           Settings.UpdateOutputs := True ;
           { Display digital low pass filter setting }
           ReadFloat( Header, 'CUTOFF=', Settings.CutOffFrequency ) ;

           // Invert recording sweep trigger pulse flag
           ReadLogical( Header, 'DACINVTRIG=', Settings.DACInvertTriggerLevel ) ;

           { Load time units (ms or s) }
           ReadString( Header, 'TUNITS=', Settings.TUnits ) ;
           if Settings.TUnits = 's' then begin
              Settings.TScale := 1. ;
              Settings.TUnScale := 1. ;
              end
           else begin
              Settings.TUnits := 'ms' ;
              Settings.TScale := SecsToms ;
              Settings.TUnScale := MsToSecs ;
              end ;

           { Seal test pulse settings }
           ReadFloat( Header, 'STPH=', Settings.SealTest.PulseHeight ) ;
           ReadFloat( Header, 'STPH1=', Settings.SealTest.PulseHeight1 ) ;
           ReadFloat( Header, 'STPH2=', Settings.SealTest.PulseHeight2 ) ;
           ReadFloat( Header, 'STPH3=', Settings.SealTest.PulseHeight3 ) ;
           ReadFloat( Header, 'STHV1=', Settings.SealTest.HoldingVoltage1 ) ;
           ReadFloat( Header, 'STHV2=', Settings.SealTest.HoldingVoltage2 ) ;
           ReadFloat( Header, 'STHV3=', Settings.SealTest.HoldingVoltage3 ) ;
           ReadFloat( Header, 'STPW=', Settings.SealTest.PulseWidth ) ;
           ReadInt( Header, 'STCCH=', Settings.SealTest.CurrentChannel ) ;
           ReadInt( Header, 'STVCH=', Settings.SealTest.VoltageChannel ) ;
           ReadInt( Header, 'STUSE=', Settings.SealTest.Use ) ;
           ReadInt( Header, 'STDSC=', Settings.SealTest.DisplayScale ) ;
           ReadLogical( Header, 'STASC=', Settings.SealTest.AutoScale ) ;
           ReadLogical( Header, 'STFRU=', Settings.SealTest.FreeRun ) ;

           { Note. For compatibility with older Strathclyde
             programs 'ADCVoltageRange' is written to file
             as VOLTS but is mV internally' }
           ReadFloat( Header, 'AD=',RawFH.ADCVoltageRange);

           ReadFloat( Header, 'DT=', RawFH.dt );

           { Plotting page settings }
           ReadFloat( Header, 'PLTPM=',Settings.Plot.TopMargin ) ;
           ReadFloat( Header, 'PLBTM=',Settings.Plot.BottomMargin ) ;
           ReadFloat( Header, 'PLLFM=',Settings.Plot.LeftMargin ) ;
           ReadFloat( Header, 'PLRTM=',Settings.Plot.RightMargin ) ;
           ReadString( Header, 'PLFNT=',Settings.Plot.FontName ) ;
           ReadInt( Header, 'PLFSI=',Settings.Plot.FontSize ) ;
           ReadInt( Header, 'PLLTH=',Settings.Plot.LineThickness ) ;
           ReadLogical( Header, 'PLSHL=',Settings.Plot.ShowLines ) ;
           ReadInt( Header, 'PLMKS=',Settings.Plot.MarkerSize ) ;
           ReadLogical( Header, 'PLSHM=',Settings.Plot.ShowMarkers ) ;
           ReadLogical( Header, 'PLCOL=',Settings.Plot.UseColor ) ;
           { Width/height of clipboard metafiles }
           ReadInt( Header, 'PLMFW=', Settings.Plot.MetafileWidth ) ;
           ReadInt( Header, 'PLMFH=', Settings.Plot.MetafileHeight ) ;

           // Calibration bar settings
           ReadFloat( Header, 'TBVAL=',Settings.TimeBarValue ) ;
           for ch := 0 to WCPChannelLimit do
               ReadFloat( Header, format('BVAL%d=',[ch]),
                          Settings.BarValue[ch] ) ;

           //ReadFloat( Header, 'SRT=',Settings.SectorWriteTime ) ;

           ReadString( Header, 'DDIR=', Settings.DataDirectory ) ;

           ReadString( Header, 'VPDIR=', Settings.VprotDirectory ) ;

           for i := 0 to High(Settings.RecentFiles) do Settings.RecentFiles[i] := '' ;
           ReadString( Header, 'RF0=', Settings.RecentFiles[0] ) ;
           ReadString( Header, 'RF1=', Settings.RecentFiles[1] ) ;
           ReadString( Header, 'RF2=', Settings.RecentFiles[2] ) ;
           ReadString( Header, 'RF3=', Settings.RecentFiles[3] ) ;


           { Laboratory interface }
           ReadInt( Header, 'LABINT=',Settings.LaboratoryInterface ) ;

           // Lab. Interface A/D input mode
           Settings.ADCInputMode := 0 ;
           ReadInt( Header, 'LABADIP=',Settings.ADCInputMode ) ;

           { Waveform generator No Display check box setting }
           ReadLogical( Header, 'WGNDSP=', Settings.WavGenNoDisplay ) ;

           { Time first record in current data file was recorded }
           ReadFloat( Header, 'TRS=',Settings.TimeRecordingStarted ) ;

           ReadInt( Header, 'DIFFM=', Settings.DifferentiationMode ) ;

           { Display autoerase flag }
           ReadLogical( Header, 'AUTOERASE=', Settings.AutoErase ) ;

           { Display grid }
           ReadLogical( Header, 'DISPGRID=', Settings.DisplayGrid ) ;
           ReadInt( Header, 'DISPNVG=', Settings.NumVerticalGridLines ) ;
           ReadInt( Header, 'DISPNHG=', Settings.NumHorizontalGridLines ) ;

           { Get channel names, units, scale factors }
           for ch := 0 to RawFH.NumChannels-1 do begin
              ReadString( Header, format('YU%d=',[ch]), RecChannel[ch].ADCUnits ) ;
              { Fix to avoid strings with #0 in them }
              if RecChannel[ch].ADCUnits[1] = #0 then RecChannel[ch].ADCUnits := '??' ;
              ReadString( Header, format('YN%d=',[ch]), RecChannel[ch].ADCName ) ;
              { Fix to avoid strings with #0 in them }
              if RecChannel[ch].ADCName[1] = #0 then RecChannel[ch].ADCName := '??' ;

              ReadFloat( Header, format('YG%d=',[ch]), RecChannel[ch].ADCCalibrationFactor) ;
              RecChannel[ch].ADCCalibrationFactor := RecChannel[ch].ADCCalibrationFactor ;

              { Zero level (in fixed mode) }
              ReadInt( Header, format('YZ%d=',[ch]), RecChannel[ch].ADCZero) ;
              { Start of zero level reference samples (-1 = fixed zero) }
              ReadInt( Header, format('YR%d=',[ch]), RecChannel[ch].ADCZeroAt) ;

              end ;

              // Triton 8 channel patch clamp settings
              for i := 0 to High(Settings.Triton.Source) do begin

                ReadInt( Header, format('TRITONS%d=',[i]), Settings.Triton.Source[i] ) ;
                ReadInt( Header, format('TRITONG%d=',[i]), Settings.Triton.Gain[i] ) ;
                ReadFloat( Header, format('TRITONIR%d=',[i]), Settings.Triton.InputRange[i] ) ;
                ReadFloat( Header, format('TRITCF%d=',[i]), Settings.Triton.CFAST[i] ) ;
                ReadFloat( Header, format('TRITCSA%d=',[i]), Settings.Triton.CSLOW_A[i] ) ;
                ReadFloat( Header, format('TRITCSB%d=',[i]), Settings.Triton.CSLOW_B[i] ) ;
                ReadFloat( Header, format('TRITCSC%d=',[i]), Settings.Triton.CSLOW_C[i] ) ;
                ReadFloat( Header, format('TRITCSD%d=',[i]), Settings.Triton.CSLOW_D[i] ) ;
                ReadFloat( Header, format('TRITRS%d=',[i]), Settings.Triton.RSERIES[i] ) ;
                ReadFloat( Header, format('TRITCOFF%d=',[i]), Settings.Triton.CHANOFF[i] ) ;
                ReadFloat( Header, format('TRITLEAK%d=',[i]), Settings.Triton.LEAK[i] ) ;
                ReadFloat( Header, format('TRITBESS%d=',[i]), Settings.Triton.BESSEL[i] ) ;

                ReadLogical( Header, format('TRITCSAEN%d=',[i]), Settings.Triton.CSLOW_AEnabled[i] ) ;
                ReadLogical( Header, format('TRITCSBEN%d=',[i]), Settings.Triton.CSLOW_BEnabled[i] ) ;
                ReadLogical( Header, format('TRITCSCEN%d=',[i]), Settings.Triton.CSLOW_CEnabled[i] ) ;
                ReadLogical( Header, format('TRITCSDEN%d=',[i]), Settings.Triton.CSLOW_DEnabled[i] ) ;
                ReadLogical( Header, format('TRITRSEN%d=',[i]), Settings.Triton.RSERIESEnabled[i] ) ;
                ReadLogical( Header, format('TRITCOFFEN%d=',[i]), Settings.Triton.CHANOFFEnabled[i] ) ;
                ReadLogical( Header, format('TRITLEAKEN%d=',[i]), Settings.Triton.LEAKEnabled[i] ) ;

                end ;

           end ;

        FileCloseSafe( IniFileHandle ) ;

        end ;


     end ;


procedure SaveInitializationFile( const IniFileName : string ) ;
{ --------------------------------------------
  Save program settings to Initialization file
  --------------------------------------------}
type
    TKeyword = string[6] ;
var
   Header : array[1..INIFileSize] of char ;
   i,ch : Integer ;
   IniFileHandle : Integer ;
begin

     IniFileHandle := FileCreate( IniFileName ) ;

     HeaderArrayFull := False ;
     { Initialise empty buffer with zero bytes }
     for i := 1 to sizeof(Header) do Header[i] := chr(0) ;

     { Last raw data file used }
     AppendString( Header, 'FILE=', RawFH.FileName ) ;
     { Last averages data file used }
     if AvgFH.Filename <> '' then
           AppendString( Header, 'FILEAVG=', AvgFH.FileName ) ;
     { Last leak subtracted data file used }
     if LeakFH.Filename <> '' then
           AppendString( Header, 'LEAKAVG=', LeakFH.FileName ) ;

     // SAve default recording settings
     AppendInt( Header, 'NC=', Settings.NumChannels ) ;
     AppendInt( Header, 'NSAMP=', Settings.NumSamples ) ;
     AppendInt( Header, 'ADCVRI=', Settings.ADCVoltageRangeIndex ) ;
     AppendFloat( Header, 'RECDUR=', Settings.RecordDuration ) ;

     AppendInt( Header, 'CEDI=', Amplifier.CED1902.Input ) ;
     AppendInt( Header, 'CEDG=', Amplifier.CED1902.Gain ) ;
     AppendFloat( Header, 'CEDGV=', Amplifier.CED1902.GainValue ) ;
     AppendInt( Header, 'CEDLP=', Amplifier.CED1902.LPFilter ) ;
     AppendInt( Header, 'CEDHP=', Amplifier.CED1902.HPFilter ) ;
     AppendInt( Header, 'CEDAC=', Amplifier.CED1902.ACCoupled ) ;
     AppendInt( Header, 'CEDDCO=', Amplifier.CED1902.DCOffset ) ;
     AppendInt( Header, 'CEDNF=', Amplifier.CED1902.NotchFilter ) ;
     AppendInt( Header, 'CEDPO=', Amplifier.CED1902.ComPort ) ;

     // Patch clamp amplifier data
     AppendInt( Header, 'AMP1=',Amplifier.AmplifierType[1] ) ;
     AppendInt( Header, 'AMP1GAINCH=',Amplifier.GainTelegraphChannel[1] ) ;
     AppendInt( Header, 'AMP1MODECH=',Amplifier.ModeTelegraphChannel[1] ) ;
     AppendInt( Header, 'AMP2=',Amplifier.AmplifierType[2] ) ;
     AppendInt( Header, 'AMP2GAINCH=',Amplifier.GainTelegraphChannel[2] ) ;
     AppendInt( Header, 'AMP2MODECH=',Amplifier.ModeTelegraphChannel[2] ) ;

     { Read record settings }
     AppendString( Header, 'TRG=', Settings.TriggerMode ) ;
     { Event detector, channel and % threshold level }
     AppendInt( Header, 'DETCH=', Settings.EventDetector.Channel ) ;
     AppendFloat( Header, 'DETTH=', Settings.EventDetector.Threshold ) ;
     AppendFloat( Header, 'DETPT=', Settings.EventDetector.PreTrigger ) ;
     AppendLogical( Header, 'EXTTRIGAH=', Settings.ExternalTriggerActiveHigh ) ;

     AppendLogical( Header, 'AER=', Settings.AutoErase ) ;
     AppendInt( Header, 'NRQ=', Settings.NumRecordsRequired ) ;
     AppendString( Header, 'FILEVPR=', Settings.VProgramFileName ) ;

     AppendFloat( Header, 'VCDIV=', Settings.DAC[0].DivideFactor ) ;
     AppendFloat( Header, 'VCDIV0=', Settings.DAC[0].DivideFactor ) ;
     AppendFloat( Header, 'VCDIV1=', Settings.DAC[1].DivideFactor ) ;

     AppendFloat( Header, 'VCHOLD=', Settings.DAC[0].HoldingVoltage ) ;
     AppendFloat( Header, 'VCHOLD0=', Settings.DAC[0].HoldingVoltage ) ;
     AppendFloat( Header, 'VCHOLD1=', Settings.DAC[1].HoldingVoltage ) ;

     AppendInt( Header, 'DIGPORT=', Settings.DigitalPort.Value ) ;

     // Invert recording sweep trigger pulse flag
     AppendLogical( Header, 'DACINVTRIG=', Settings.DACInvertTriggerLevel ) ;

     AppendFloat( Header, 'CUTOFF=', Settings.CutOffFrequency ) ;
     AppendString( Header, 'TUNITS=', Settings.TUnits ) ;

     { Pipette seal test settings }
     AppendFloat( Header, 'STPH=', Settings.SealTest.PulseHeight ) ;
     AppendFloat( Header, 'STPH1=', Settings.SealTest.PulseHeight1 ) ;
     AppendFloat( Header, 'STPH2=', Settings.SealTest.PulseHeight2 ) ;
     AppendFloat( Header, 'STPH3=', Settings.SealTest.PulseHeight3 ) ;
     AppendFloat( Header, 'STHV1=', Settings.SealTest.HoldingVoltage1 ) ;
     AppendFloat( Header, 'STHV2=', Settings.SealTest.HoldingVoltage2 ) ;
     AppendFloat( Header, 'STHV3=', Settings.SealTest.HoldingVoltage3 ) ;
     AppendFloat( Header, 'STPW=', Settings.SealTest.PulseWidth ) ;
     AppendInt( Header, 'STCCH=', Settings.SealTest.CurrentChannel ) ;
     AppendInt( Header, 'STVCH=', Settings.SealTest.VoltageChannel ) ;
     AppendInt( Header, 'STUSE=', Settings.SealTest.Use ) ;
     AppendInt( Header, 'STDSC=', Settings.SealTest.DisplayScale ) ;
     AppendLogical( Header, 'STASC=', Settings.SealTest.AutoScale ) ;
     AppendLogical( Header, 'STFRU=', Settings.SealTest.FreeRun ) ;

     AppendInt( Header, 'NC=', RawFH.NumChannels ) ;

     RawFH.NumAnalysisBytesPerRecord := NumAnalysisBytesPerRecord ;
     AppendInt( Header, 'NBA=', 1 ) ;
     AppendInt(Header,'NBD=',((RawFH.NumSamples*RawFH.NumChannels*2) div 512 )) ;

     AppendFloat( Header, 'AD=', RawFH.ADCVoltageRange ) ;

     AppendFloat( Header, 'DT=',RawFH.dt );

     { Plotting page settings }
     AppendFloat( Header, 'PLTPM=',Settings.Plot.TopMargin ) ;
     AppendFloat( Header, 'PLBTM=',Settings.Plot.BottomMargin ) ;
     AppendFloat( Header, 'PLLFM=',Settings.Plot.LeftMargin ) ;
     AppendFloat( Header, 'PLRTM=',Settings.Plot.RightMargin ) ;
     AppendString( Header, 'PLFNT=',Settings.Plot.FontName ) ;
     AppendInt( Header, 'PLFSI=',Settings.Plot.FontSize ) ;
     AppendInt( Header, 'PLLTH=',Settings.Plot.LineThickness ) ;
     AppendLogical( Header, 'PLSHL=',Settings.Plot.ShowLines ) ;
     AppendInt( Header, 'PLMKS=',Settings.Plot.MarkerSize ) ;
     AppendLogical( Header, 'PLSHM=',Settings.Plot.ShowMarkers ) ;
     AppendLogical( Header, 'PLCOL=',Settings.Plot.UseColor ) ;
     { Width/height of clipboard metafiles }
     AppendInt( Header, 'PLMFW=', Settings.Plot.MetafileWidth ) ;
     AppendInt( Header, 'PLMFH=', Settings.Plot.MetafileHeight ) ;

     // Calibration bar settings
     AppendFloat( Header, 'TBVAL=',Settings.TimeBarValue ) ;
     for ch := 0 to WCPChannelLimit do
         AppendFloat( Header, format('BVAL%d=',[ch]),
                      Settings.BarValue[ch] ) ;

     //AppendFloat( Header, 'SRT=',Settings.SectorWriteTime ) ;

     AppendString( Header, 'DDIR=', Settings.DataDirectory ) ;

     AppendString( Header, 'VPDIR=', Settings.VprotDirectory ) ;

     AppendString( Header, 'RF0=', Settings.RecentFiles[0] ) ;
     AppendString( Header, 'RF1=', Settings.RecentFiles[1] ) ;
     AppendString( Header, 'RF2=', Settings.RecentFiles[2] ) ;
     AppendString( Header, 'RF3=', Settings.RecentFiles[3] ) ;

     AppendInt( Header, 'LABINT=',Settings.LaboratoryInterface ) ;
     AppendInt( Header, 'LABADIP=',Settings.ADCInputMode ) ;

     { Waveform generator No Display check box setting }
     AppendLogical( Header, 'WGNDSP=', Settings.WavGenNoDisplay ) ;

     { Time first record in current data file was recorded }
     AppendFloat( Header, 'TRS=',Settings.TimeRecordingStarted ) ;

     { Differentiation mode for waveform measurements }
     AppendInt( Header, 'DIFFM=', Settings.DifferentiationMode ) ;

     { Display autoerase flag }
     AppendLogical( Header, 'AUTOERASE=', Settings.AutoErase ) ;

     { Display grid flag }
     AppendLogical( Header, 'DISPGRID=', Settings.DisplayGrid ) ;
     AppendInt( Header, 'DISPNVG=', Settings.NumVerticalGridLines ) ;
     AppendInt( Header, 'DISPNHG=', Settings.NumHorizontalGridLines ) ;


     for ch := 0 to RawFH.NumChannels-1 do begin
         AppendString( Header, format('YU%d=',[ch]), RecChannel[ch].ADCUnits ) ;
         AppendString( Header, format('YN%d=',[ch]), RecChannel[ch].ADCName ) ;
         AppendFloat( Header, format('YG%d=',[ch]),
                      RecChannel[ch].ADCCalibrationFactor ) ;
         { Zero level (in fixed mode) }
         AppendInt( Header, format('YZ%d=',[ch]), RecChannel[ch].ADCZero) ;
         { Start of zero level reference samples (-1 = fixed zero) }
         AppendInt( Header, format('YR%d=',[ch]), RecChannel[ch].ADCZeroAt) ;

         end ;

              // Triton 8 channel patch clamp settings
              for i := 0 to High(Settings.Triton.Source) do begin

                AppendInt( Header, format('TRITONS%d=',[i]), Settings.Triton.Source[i] ) ;
                AppendInt( Header, format('TRITONG%d=',[i]), Settings.Triton.Gain[i] ) ;
                AppendFloat( Header, format('TRITONIR%d=',[i]), Settings.Triton.InputRange[i] ) ;
                AppendFloat( Header, format('TRITCF%d=',[i]), Settings.Triton.CFAST[i] ) ;
                AppendFloat( Header, format('TRITCSA%d=',[i]), Settings.Triton.CSLOW_A[i] ) ;
                AppendFloat( Header, format('TRITCSB%d=',[i]), Settings.Triton.CSLOW_B[i] ) ;
                AppendFloat( Header, format('TRITCSC%d=',[i]), Settings.Triton.CSLOW_C[i] ) ;
                AppendFloat( Header, format('TRITCSD%d=',[i]), Settings.Triton.CSLOW_D[i] ) ;
                AppendFloat( Header, format('TRITRS%d=',[i]), Settings.Triton.RSERIES[i] ) ;
                AppendFloat( Header, format('TRITCOFF%d=',[i]), Settings.Triton.CHANOFF[i] ) ;
                AppendFloat( Header, format('TRITLEAK%d=',[i]), Settings.Triton.LEAK[i] ) ;
                AppendFloat( Header, format('TRITBESS%d=',[i]), Settings.Triton.BESSEL[i] ) ;

                AppendLogical( Header, format('TRITCSAEN%d=',[i]), Settings.Triton.CSLOW_AEnabled[i] ) ;
                AppendLogical( Header, format('TRITCSBEN%d=',[i]), Settings.Triton.CSLOW_BEnabled[i] ) ;
                AppendLogical( Header, format('TRITCSCEN%d=',[i]), Settings.Triton.CSLOW_CEnabled[i] ) ;
                AppendLogical( Header, format('TRITCSDEN%d=',[i]), Settings.Triton.CSLOW_DEnabled[i] ) ;
                AppendLogical( Header, format('TRITRSEN%d=',[i]), Settings.Triton.RSERIESEnabled[i] ) ;
                AppendLogical( Header, format('TRITCOFFEN%d=',[i]), Settings.Triton.CHANOFFEnabled[i] ) ;
                AppendLogical( Header, format('TRITLEAKEN%d=',[i]), Settings.Triton.LEAKEnabled[i] ) ;

                end ;


     if FileWrite( IniFileHandle, Header, Sizeof(Header) ) <> Sizeof(Header) then
        ShowMessage( IniFileName + ' Write - Failed!' ) ;
     FileCloseSafe( IniFileHandle ) ;

     if HeaderArrayFull then
        ShowMessage( INIFileName + ' File header parameter array full!' ) ;

     end ;


procedure PutRecord(
          var fHDR : TFileHeader;         { File header block }
          var rH : TRecHeader ;           { Record header block }
          RecordNum : Integer ;           { Record number }
          var dBuf : Array of SmallInt    { A/D data buffer }
          ) ;
{ -------------------------------------------------
  Write a WCP format digital signal record to file
  -------------------------------------------------}
begin

     fHDR.RecordNum := RecordNum ;
     fHDR.NumDataBytesPerRecord := fHDR.NumSamples*fHDR.NumChannels*2 ;
     fHDR.NumBytesPerRecord := fHDR.NumDataBytesPerRecord +
                               fHDR.NumAnalysisBytesPerRecord ;

     { Write data to record analysis block }
     PutRecordHeaderOnly( fHDR, RH, RecordNum ) ;

     { Move file pointer to start of record data block }
     FileSeek( FHDR.FileHandle,
               fHDR.NumBytesInHeader +
               fHDR.NumBytesPerRecord*(RecordNum-1) +
               fHDR.NumAnalysisBytesPerRecord,
               0 ) ;

     { Write A/D samples to data file }
     if FileWrite( FHDR.FileHandle, dBuf, FHDR.NumDataBytesPerRecord )
        <> FHDR.NumDataBytesPerRecord then
        WriteToLogFileNoDate( format('Error writing Rec=%d to %s',
                                     [FHDR.RecordNum,FHDR.FileName]) ) ;

     end ;


procedure PutRecordHeaderOnly(
          var fHDR : TFileHeader;    { Data file header }
          const rH : TRecHeader ;    { Record header }
          RecordNum : Integer ) ;    { Record number }
{ -------------------------------------------------------
  Write a WCP format digital signal record header to file
  -------------------------------------------------------}
var
   i : Integer ;
   cBuf : array[1..20] of char ;
begin
     fHDR.RecordNum := RecordNum ;
     fHDR.NumDataBytesPerRecord := fHDR.NumSamples*fHDR.NumChannels*2 ;
     fHDR.NumBytesPerRecord := fHDR.NumDataBytesPerRecord +
                               fHDR.NumAnalysisBytesPerRecord ;

     { Write record header block to file }
     FileSeek( FHDR.FileHandle,
               (RecordNum-1)*FHDR.NumBytesPerRecord + FHDR.NumBytesInHeader,
               0 ) ;

     for i := 1 to 8 do cBuf[i] := rH.Status[i] ;
     FileWrite( FHDR.FileHandle, cBuf, 8 ) ;

     for i := 1 to 4 do cBuf[i] := rH.RecType[i] ;
     FileWrite( FHDR.FileHandle, cBuf, 4 ) ;

     FileWrite( FHDR.FileHandle, rH.Number, sizeof(rH.Number) ) ;
     FileWrite( FHDR.FileHandle, rH.Time, sizeof(rH.Time) ) ;
     FileWrite( FHDR.FileHandle, rH.dt, sizeof(rH.dt) ) ;

     { Write A/D voltage range for each channel }
     FileWrite(FHDR.FileHandle,rH.ADCVoltageRange,sizeof(rH.ADCVoltageRange)) ;

     { Write Analysis block }
     FileWrite(FHDR.FileHandle,rH.Analysis.Available,sizeof(rH.Analysis.Available)) ;
     FileWrite(FHDR.FileHandle, rH.Analysis.Value, sizeof(rH.Analysis.Value) ) ;
     FileWrite( FHDR.FileHandle, rH.Equation, sizeof(rH.Equation) ) ;

     { Write record ident line }
     for i := 1 to 16 do if i <= Length(rH.Ident) then cBuf[i] := rH.Ident[i]
                                                  else cBuf[i] := ' ' ;
     FileWrite( FHDR.FileHandle, cBuf, 16 ) ;
     end ;


procedure GetRecord(
          var fHDR : TFileHeader;      { Data file header }
          var rH : TRecHeader ;        { Record header }
          RecordNum : Integer ;        { Record number }
          var dBuf : Array of SmallInt { A/D data array }
          ) ;
{ --------------------------------------------------
  Read a WCP format digital signal record from file
  --------------------------------------------------}
var
   i,ch,i0,i1,Sum,ChOffset : Integer ;
begin

     { Get record header data and store in analysis block RH }
     GetRecordHeaderOnly( fHDR, RH, RecordNum ) ;

     { Read A/D samples from record data block on file }
     fHDR.RecordNum := RecordNum ;
     fHDR.NumDataBytesPerRecord := fHDR.NumSamples*fHDR.NumChannels*2 ;
     fHDR.NumBytesPerRecord := fHDR.NumDataBytesPerRecord +
                               fHDR.NumAnalysisBytesPerRecord ;

     { Move file pointer to start of record data block }
     FileSeek( FHDR.FileHandle,
               fHDR.NumBytesInHeader +
               fHDR.NumBytesPerRecord*(RecordNum-1) +
               fHDR.NumAnalysisBytesPerRecord,
               0 ) ;

     { Read data block }
     if FileRead( FHDR.FileHandle, dBuf, FHDR.NumDataBytesPerRecord )
        <> FHDR.NumDataBytesPerRecord then begin
        WriteToLogFilenoDate( format('Error reading file %s, Rec=%d',
                              [fHDR.FileName,fHDR.RecordNum])) ;
        end ;

     {Apply digital filter}
     if Settings.CutOffFrequency > 0.0 then
        GaussianFilter( FHDR, dBuf, Settings.CutOffFrequency ) ;

     { Calculate zero level for each channel }
     for ch := 0 to FHDR.NumChannels - 1 do begin
         ChOffset := Channel[Ch].ChannelOffset ;
         if Channel[ch].ADCZeroAt >= 0 then begin
            i0 := Channel[ch].ADCZeroAt ;
            i0 := Min(Max( i0,0 ),FHDR.NumSamples-1 ) ;
            i1 := i0 + FHDR.NumZeroAvg - 1 ;
            i1 := Min(Max( i1,0 ),FHDR.NumSamples-1 ) ;
            Sum := 0 ;
            for i := i0 to i1 do Sum := Sum + dBuf[i*FHDR.NumChannels + ChOffset] ;
            Channel[ch].ADCZero := Sum div (i1-i0+1) ;
            end ;
         end ;
     end ;


procedure GetRecord32(
          var fHDR : TFileHeader;      { Data file header }
          var rH : TRecHeader ;        { Record header }
          RecordNum : Integer ;        { Record number }
          var dBuf : Array of Integer { A/D data array }
          ) ;
// --------------------------------------------
// Load A/D data from record into 32 bit buffer
// --------------------------------------------
var
    i : Integer ;
    dBuf16 : ^TSmallIntArray ;
begin

    New(dBuf16) ; // Create buffer

    // Read data from file
    GetRecord( fHDR, rH, RecordNum, dBuf16^ ) ;

    for i := 0 to fHDR.NumSamples*fHDR.NumChannels-1 do dBuf[i] := dBuf16[i] ;

    Dispose(dBuf16) ; // Free buffer

    end ;


procedure GetRecordHeaderOnly(
          var fHDR : TFileHeader;      { Data file header }
          var rH : TRecHeader ;        { Record header }
          RecordNum : Integer          { Record number }
          ) ;
{ --------------------------------------------------------
  Read a WCP format digital signal record header from file
  --------------------------------------------------------}
var
   cBuf : array[1..20] of char ;
   i,ch,iStart : Integer ;
begin
     fHDR.RecordNum := RecordNum ;
     fHDR.NumDataBytesPerRecord := fHDR.NumSamples*fHDR.NumChannels*2 ;
     fHDR.NumBytesPerRecord := fHDR.NumDataBytesPerRecord +
                               fHDR.NumAnalysisBytesPerRecord ;

     { Move file pointer to start of record header block }
     FileSeek( FHDR.FileHandle,
               (RecordNum-1)*fHDR.NumBytesPerRecord + fHDR.NumBytesInHeader,
               0 ) ;

     { Read record header data }

     iStart := FileSeek(FHDR.FileHandle, 0, 1) ;

     FileRead( FHDR.FileHandle, cBuf, 8 );
     rH.Status := '' ;
     for i := 1 to 8 do Rh.Status := rH.Status + cBuf[i] ;

     FileRead( FHDR.FileHandle, cBuf, 4 ) ;
     rH.RecType := '' ;
     for i := 1 to 4 do Rh.RecType := rH.RecType + cBuf[i] ;

     FileRead( FHDR.FileHandle, rH.Number, sizeof(rH.Number) ) ;
     FileRead( FHDR.FileHandle, rH.Time, sizeof(rH.Time) ) ;
     FileRead( FHDR.FileHandle, rH.dt, sizeof(rH.dt) ) ;

     { Read channel A/D converter voltage range }
     FileRead(FHDR.FileHandle,rH.ADCVoltageRange,sizeof(rH.ADCVoltageRange) ) ;
     { Ensure all channels have a valid voltage range }
     for Ch := 0 to FHDR.NumChannels-1 do begin
         if (fHDR.Version < 6.0) or (rH.ADCVoltageRange[ch]=0.0) then
            rH.ADCVoltageRange[ch] := rH.ADCVoltageRange[0] ;
         Channel[ch].ADCScale := CalibFactorToADCScale( RH, ch ) ;
         end ;

     { Read Analysis block }
     FileRead(FHDR.FileHandle,rH.Analysis.Available,sizeof(rH.Analysis.Available)) ;
     FileRead( FHDR.FileHandle, rH.Analysis.Value, sizeof(rH.Analysis.Value) ) ;
     FileRead( FHDR.FileHandle, rH.Equation, sizeof(rH.Equation) ) ;

     { Read record ident text }
     FileRead( FHDR.FileHandle, cBuf, 16 );
     rH.Ident := '' ;
     for i := 1 to 16 do Rh.Ident := rH.Ident + cBuf[i] ;
     iStart := FileSeek(FHDR.FileHandle, 0, 1) - iStart ;
     end ;


function CalibFactorToADCScale(
         var RH : TRecHeader ;
         Ch : Integer
         ) : single ;
begin
     if Channel[ch].ADCCalibrationFactor = 0.0 then Channel[ch].ADCCalibrationFactor := 0.001 ;
     Result := Abs(RH.ADCVoltageRange[ch]) /
              (Channel[ch].ADCCalibrationFactor*(MaxADCValue+1) ) ;
     end ;



procedure OpenLogFile ;
{ -------------
  Open log file
  ------------- }
begin

     { Create a log file using current date }
     DateSeparator := '-' ;
     LogFileName := Settings.ProgDirectory + DateToStr(Date)+'.log' ;
     LogFileAvailable := True ;
     AssignFile( LogFile, LogFileName ) ;
     try
           if FileExists( LogFileName ) then Append(LogFile)
                                        else ReWrite(LogFile) ;
     except
           on EInOutError do begin
              //MessageDlg( ' WinWCP - Cannot create Log File', mtWarning, [mbOK], 0 ) ;
              LogFileAvailable := False ;
              end ;
           end ;
     { If file doesn't exist ... disable access }
     Main.InspectLogFile.Enabled := LogFileAvailable ;
     end ;


procedure WriteToLogFile( Line : string ) ;
{ ---------------------------------------------
  Write a date-stamped line of text to log file
  --------------------------------------------- }
begin
     if LogFileAvailable then WriteLn( LogFile, TimeToStr(Time) + ' ' + Line ) ;
     end ;


procedure WriteToLogFileNoDate( Line : string ) ;
{ --------------------------------
  Write a line of text to log file
  -------------------------------- }
begin
     if LogFileAvailable then WriteLn( LogFile, Line ) ;
     end ;


procedure CloseLogFile ;
{ --------------
  Close log file
  -------------- }
begin
     if LogFileAvailable then begin
        try
           CloseFile(LogFile) ;
        except
              on EInOutError do begin
              MessageDlg( ' WinWCP - Error closing Log File', mtWarning, [mbOK], 0 ) ;
              LogFileAvailable := False ;
              end ;
           end ;
        end ;
     end ;


procedure GaussianFilter(
          const FHdr : TFileHeader ;     { Header of file to be filtered }
          Var Buf : Array of SmallInt ;  { Data to be filtered }
          CutOffFrequency : single       { Filter cut-off frequency (Hz) }
          ) ;
{ --------------------------------------------------
  Gaussian digital filter. (based on Sigworth, 1983)
  --------------------------------------------------}
const
     MaxCoeff = 54 ;
var
   a : Array[-MaxCoeff..MaxCoeff] of single ;
   Temp,sum,b,sigma : single ;
   i,i1,j,j1,nc,Ch,ChOffset : Integer ;
   EndofData : Integer ;
   Work : ^TLongIntArray ;
begin

      try

         New(Work) ;

         { Generate filter coefficients }
         sigma := 0.132505/CutOffFrequency ;
         if  sigma >= 0.62  then begin

	     b := -1./(2.*sigma*sigma) ;
	     nc := 0 ;
	     a[0] := 1. ;
	     sum := 1. ;
	     temp := 1. ;
	     while (temp >= 10.*MinSingle) and (nc < MaxCoeff) do begin
	           Inc(nc) ;
	           temp := exp( nc*nc*b ) ;
	           a[nc] := temp ;
                   a[-nc] := Temp ;
	           sum := sum + 2.*temp ;
                   end ;

             { Normalise coefficients so that they summate to 1. }
	     for i := -nc to nc do a[i] := a[i]/sum ;
             end
         else begin
            { Special case for very light filtering
              (See Colquhoun & Sigworth, 1983) }
            a[1] := (sigma*sigma)/2.0 ;
            a[-1] := a[1] ;
	    a[0] := 1.0 - 2.0*a[1] ;
	    nc := 1 ;
            end ;

         { Copy data into work array }
         for i := 0 to (FHdr.NumSamples*FHdr.NumChannels)-1 do Work^[i] := Buf[i] ;

         { Apply filter to each channel }
         for Ch := 0 to FHdr.NumChannels-1 do begin

             { Apply gaussian filter to each point
               and store result back in buffer }
             ChOffset := GetChannelOffset(Ch,FHdr.NumChannels) ;
             i1 := ChOffset ;
             EndOfData := FHdr.NumSamples -1 ;
             for i := 0 to EndOfData do begin
	         sum := 0. ;
	         for j := -nc to nc do begin
                     j1 := j+i ;
                     if j1 < 0 then j1 := 0 ;
                     if j1 > EndofData then j1 := EndofData ;
                     j1 := j1*FHdr.NumChannels + ChOffset ;
	             sum := sum + (Work^[j1])*a[j] ;
                     end ;
                 Buf[i1] := Trunc(sum) ;
                 i1 := i1 + FHdr.NumChannels ;
                 end ;
             end ;
      finally
             Dispose(Work) ;
             end ;
      end ;

procedure FileCloseSafe( var FileHandle : Integer ) ;
// -------------------------
// Safe close file procedure
// -------------------------
begin
     // Close file (if it is open)
     if FileHandle >= 0 then FileClose( FileHandle ) ;
     // File handle = -1 indicates closed file
     FileHandle := -1 ;
     end ;


function FileOverwriteCheck(
         var FileName : string 
         ) : boolean ;
{ ----------------------------------------------------------------------
  To avoid overwriting an existing data file, check whether the file
  "FileName" exists and give the user the chance to change the name or
  abandon the operation.
  Returns FALSE if the user has chosen to abandon the operation
  ---------------------------------------------------------------------}
const
     OK = True ;
begin
     { Check whether file exists }
     if FileExists(FileName) then begin
          { If it exists, let user change it's name }
          Main.SaveDialog.options := [ofHideReadOnly,ofPathMustExist] ;
          Main.SaveDialog.DefaultExt := DataFileExtension ;
          Main.SaveDialog.FileName := ExtractFileName( FileName ) ;
          Main.SaveDialog.Filter := format( ' WCP Files (*%s)|*%s',
                                    [DataFileExtension,DataFileExtension]) ;
          Main.SaveDialog.Title := ExtractFileName(FileName)
                                   + ' already exists! Change Name? ';

          if Main.SaveDialog.execute then begin
             { Save data directory }
             Settings.DataDirectory := ExtractFilePath( Main.SaveDialog.FileName ) ;
             { Use new file name entered by user }
             FileName := Main.SaveDialog.FileName ;
             { User has clicked OK, tell calling routine to go ahead }
             Result := OK ;
             end
          else begin
               { User has clicked CANCEL, tell calling routine to give up }
               Result := not OK ;
               end ;
          end
     else begin
          { File doesn't exist, no overwrite possible }
          Result := OK ;
          end ;
     end ;





initialization
    LogFileAvailable := False ;

end.
