unit Fileio;
{ ======================================================================
  WinEDR - File Input/Output routines
  (c) J.Dempster, University of Strathclyde 1996-2003. All Rights Reserved
  ======================================================================
  4/6/97 Error in retaining Seal test amplitude fixed
  7/6/97 ADCAmplifierGain now correctly included in scale factor
  5/7/98 Settings.UnitCurrent added
  1/4/99 Size of file header increased to 2048
  27/8/99 ... WinEDR V2.0
              dt now stored as (s) Cal. factors as V/Units rather than mV/units
  17/10/99 ... FileOverwriteCheck transferred from Convert
  27/2/00 ... Bug ready .WCP files fixed
  12/4/00 ... V2.0.5 SaveWCPHeader and GetWCPHeader now has 1024 byte header space
  15/2/01 ... ADCMAX flag added to both WCP and EDR data file headers
              to distinguish between 12 and 16 bit data files
  23/10/01 ... Pad : Array[1..2] of Byte added to TEVENT
                to make record length 60 bytes to be
                compatible with V2.1.2 and earlier. .EDE
                Problem arose when going from Delphi V3 to V5 compiler
  8/12/01  ... ReadCDRBuffer and WriteCDRBuffer updated to fix Append File bug
  24/6/3 ..... Settings.DisplayDuration, Settings.NumHorizontalGridLines
               Settings.NumVerticalGridLines now saved in INI file
  26/6/6 ..... Marker list now saved to header
  30/6/6 ..... Stimulator settings added to INI file
  30/9/3 ..... MaxADCValue now set to SESLabIO.ADCMaxValue when a new data file created
  5/2/4 ...... Single-channel event file I/O procedures moved ton SingleChanAnal.pas
  6/8/4 ...... Settings.ExternalTriggerActiveHigh added INI file
  29/7/5 ..... Settings.ADCInputMode (LABADIP=) added to ini file
  5/1/6 .. 'DETAW=', Settings.EventDetector.AnalysisWindow added
  30/7/6 .. RecChannel settings now saved in INI file (instead of Channel settings)
  25/9/6 .. Error in LoadInitialisationFile when no EDR.INI fixed
  26/9/6 .. Only ADCVoltageRange passed to CalibFactorToADCScale and
            ADCScaleToCalibFactor instead of CDRFH
  19/03/08 .. Real-time event frequency settings added toi INI file
  09/02/09 .. EDR.INI file size increased to 10000 to avoid header full with very long file paths
  24/02/09 .. CAPINVG= added to INI file
  02/06/09 .. CAPRSCOMP= and CAPCCOMP=  added to INI file
  03/06/09 .. MakeBackupFile and RestoreFromBackupFile now read & write correctly without error messages
              'CAPCOMPINUSE=', 'CAPINUSE=' added
  15/02/10 .. Settings.EventDetector.RisingEdgeWindow added
  08/03/10 .. Settings.RTResistance parameters added
              Settings.EventDetector.AvgFrequencyInterval

  }


interface

uses shared,global,sysUtils,dialogs,math;

procedure SaveWCPHeader( var fHDR : TWCPFileHeader ) ;
procedure GetWCPHeader( var fHDR : TWCPFileHeader ) ;
procedure SaveCDRHeader( var fHDR : TCDRFileHeader ) ;
procedure GetCDRHeader( var fHDR : TCDRFileHeader ) ;
function ADCScaleToCalibFactor(
         ADCVoltageRange : Single ;
         var Channel : TChannel )  : single ;
function CalibFactorToADCScale(
         ADCVoltageRange : Single ;
         var Channel : TChannel )  : single ;

function FileOverwriteCheck( var FileName : string ) : boolean ;


function ReadCDRBuffer(
         var FHdr : TCDRFileHeader ;
         BlockPointer : Integer ;
         var Buf : Array of SmallInt ;
         NumBlocksToRead : Integer ) : Integer ;

function WriteCDRBuffer(
         var FHdr : TCDRFileHeader ;
         BlockPointer : Integer ;
         var Buf : Array of SmallInt ;
         NumBlocksToWrite : Integer      
         ) : Integer ;

procedure LoadInitializationFile( const IniFileName : string ) ;
procedure SaveInitializationFile( const IniFileName : string ) ;
procedure PutWCPRecord (
          var fHDR : TWCPFileHeader;
          var rH : TWCPRecHeader ;
          RecordNum : Integer ;
          var dBuf : Array of SmallInt ) ;
procedure PutWCPRecordHeaderOnly(
          var fHDR : TWCPFileHeader;
          Const rH : TWCPRecHeader ;
          RecordNum : Integer
          ) ;
procedure GetWCPRecord(
          var fHDR : TWCPFileHeader;
          var rH : TWCPRecHeader ;
          RecordNum : Integer ;
          var dBuf : Array of SmallInt
          ) ;
procedure GetWCPRecordHeaderOnly(
          var fHDR : TWCPFileHeader;
          var rH : TWCPRecHeader ;
          RecordNum : Integer
          ) ;


procedure OpenLogFile ;
procedure WriteToLogFile( Line : string ) ;
procedure CloseLogFile ;
procedure MakeBackupFile ;
procedure RestoreFromBackupFile ;


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

implementation

uses mdiform, maths, ced1401, AmpModule ;

const
     HeaderSize = 2048 ;
     IniFileHeaderSize = 10000 ;

var
   LogFile : TextFile ;
   LogFileAvailable : boolean ;


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( ' EDR 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 ;


procedure SaveCDRHeader( var fHDR : TCDRFileHeader ) ;
{ ---------------------------------------
  Save file header data to CDR data file
  ---------------------------------------}
var
   Header : array[1..HeaderSize] of char ;
   i : Integer ;
   ch : Integer ;

begin

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

     AppendFloat( Header, 'VER=',fHDR.Version );

     // 13/2/02 Added to distinguish between 12 and 16 bit data files
     // 30/7/6 Now uses ADCMaxValue in Channel settings
     AppendInt( Header, 'ADCMAX=', Channel[0].ADCMaxValue ) ;

     { Number of bytes in file header }
     AppendInt( Header, 'NBH=', HeaderSize ) ;

     fHDR.NumChannels := Max(fHDR.NumChannels,1) ;
     AppendInt( Header, 'NC=', fHDR.NumChannels ) ;

     // A/D converter input voltage range
     AppendFloat( Header, 'AD=', fHDR.ADCVoltageRange ) ;

     { Determine number of samples in file from size of file
       - space taken up by header }
   {  fHDR.NumSamplesInFile := (FileSeek(fHDR.FileHandle,0,2)
                               - fHDR.NumBytesInHeader) div 2 ;}
     fHDR.NumSamplesPerBlock := fHDR.NumChannels*NumSamplesPerSector ;
     fHDR.NumBytesPerBlock := fHDR.NumSamplesPerBlock*2 ;
     fHDR.NumBlocksInFile := fHDR.NumSamplesInFile div fHDR.NumSamplesPerBlock ;

     AppendInt( Header, 'NP=', fHDR.NumSamplesInFile ) ;

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

     { Time duration of recorded data }
     fHDR.RecordDuration := (fHDR.NumSamplesInFile*fHDR.dt) /
                                 fHDR.NumChannels ;

     { Event detector parameters }
     AppendInt( Header,   'DETCH=', Settings.EventDetector.Channel ) ;
     AppendInt( Header,   'DETRS=', Settings.EventDetector.RecordSize ) ;
     AppendFloat( Header, 'DETYT=', Settings.EventDetector.yThreshold ) ;
     AppendFloat( Header, 'DETTT=', Settings.EventDetector.tThreshold ) ;
     AppendFloat( Header, 'DETDD=', Settings.EventDetector.DeadTime ) ;
     AppendFloat( Header, 'DETBA=', Settings.EventDetector.BaselineAverage ) ;
     AppendFloat( Header, 'DETPF=', Settings.EventDetector.PreTriggerFraction ) ;
     AppendFloat( Header, 'DETAW=', Settings.EventDetector.AnalysisWindow ) ;
     AppendLogical( Header, 'DETPOSPK=', Settings.EventDetector.PositivePeaks ) ;
     AppendLogical( Header, 'DETBASST=', Settings.EventDetector.BaselineAtStart ) ;
     AppendLogical( Header, 'DETBASSUB=', Settings.EventDetector.SubtractBaseline ) ;
     AppendInt( Header, 'DETBASPTS=', Settings.EventDetector.NumBaselinePoints ) ;
     AppendInt( Header, 'DETBASGAP=', Settings.EventDetector.NumBaselineGap ) ;
     AppendFloat( Header, 'DETTDECP=', Settings.EventDetector.TDecayPercent ) ;
     AppendInt( Header, 'DETDECFR=', Settings.EventDetector.TDecayFrom ) ;
     AppendInt( Header, 'DETREW=', Settings.EventDetector.RisingEdgeWindow ) ;

     AppendInt( Header, 'VARRS=', Settings.Variance.RecordSize ) ;
     AppendInt( Header, 'VAROV=', Settings.Variance.RecordOverlap ) ;
     AppendFloat( Header, 'VARTR=', Settings.Variance.TauRise ) ;
     AppendFloat( Header, 'VARTD=', Settings.Variance.TauDecay ) ;

     AppendFloat( Header, 'UNITC=', Settings.UnitCurrent ) ;
     AppendFloat( Header, 'DWTTH=', Settings.DwellTimes.Threshold ) ;

     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 ) ;
         //Channel[ch].ADCCalibrationFactor := ADCScaleToCalibFactor( FHDR, ch ) ;
         AppendFloat(Header,format('YCF%d=',[ch]),Channel[ch].ADCCalibrationFactor) ;
         AppendFloat( Header, format('YAG%d=',[ch]), Channel[ch].ADCAmplifierGain) ;
         AppendInt( Header, format('YZ%d=',[ch]), Channel[ch].ADCZero) ;
         AppendInt( Header, format('YR%d=',[ch]), Channel[ch].ADCZeroAt) ;
         end ;

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

     { Save the name of any associated WCP data file }
     AppendString( Header, 'WCPFNAM=',  fHDR.WCPFileName ) ;

     { Save the original file backed up flag }
     AppendLogical( Header, 'BAK=', fHDR.BackedUp ) ;

     // Save markers to header
     AppendInt( Header, 'MKN=', MarkerList.Count ) ;
     for i := 0 to MarkerList.Count-1 do begin
         AppendFloat( Header, format('MKTIM%d=',[i]), Single(MarkerList.Objects[i])) ;
         AppendString( Header, format('MKTXT%d=',[i]), MarkerList.Strings[i] ) ;
         end ;

     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 ;


function ADCScaleToCalibFactor( ADCVoltageRange : Single ;
                                var Channel : TChannel )  : single ;
begin
     if Channel.ADCAmplifierGain = 0.0 then Channel.ADCAmplifierGain := 1.0 ;
     if Channel.ADCScale = 0.0 then Channel.ADCScale := 0.001 ;
     Result := ADCVoltageRange /
              (Channel.ADCScale*Channel.ADCAmplifierGain *(Channel.ADCMaxValue+1) ) ;
     end ;


function CalibFactorToADCScale( ADCVoltageRange : Single ;
                                var Channel : TChannel ) : single ;
begin
     if Channel.ADCAmplifierGain = 0.0 then Channel.ADCAmplifierGain := 1.0 ;
     if Channel.ADCCalibrationFactor = 0.0 then Channel.ADCCalibrationFactor := 0.001 ;
     Result := ADCVoltageRange /
              (Channel.ADCCalibrationFactor*Channel.ADCAmplifierGain *(Channel.ADCMaxValue+1) ) ;
     end ;


procedure GetCDRHeader( var fHDR : TCDRFileHeader ) ;
{ ------------------------------------------------------
  Read file header block from data file,
  decode parameter list, and put into FileHeader record
  ------------------------------------------------------}
var
   Header : array[1..HeaderSize] of char ;
   i,ch,OldValue,ADCMaxValue : Integer ;
   NumMarkers : Integer ;
   MarkerTime : Single ;
   MarkerText : String ;
begin

     fHDR.FilePointer := FileSeek( fHDR.FileHandle, 0, 0 ) ;
     if FileRead( fHDR.FileHandle, Header, Sizeof(Header) )
        = Sizeof(Header) then begin

          { Get default size of file header }
          fHDR.NumBytesInHeader := HeaderSize ;
          { Get size of file header for this file }
          ReadInt( Header, 'NBH=', fHDR.NumBytesInHeader ) ;
          if fHDR.NumBytesInHeader <> HeaderSize then begin
             ShowMessage( 'File header size mismatch' ) ;
             end ;

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

          // 14/2/02 Added to distinguish between 12 and 16 bit data files
          OldValue := Channel[0].ADCMaxValue ;
          ReadInt( Header, 'ADCMAX=', Channel[0].ADCMaxValue ) ;
          if Channel[0].ADCMaxValue = 0 then Channel[0].ADCMaxValue := 2047 ;
          for ch := 0 to ChannelLimit do Channel[ch].ADCMaxValue := Channel[0].ADCMaxValue ;

          if Channel[0].ADCMaxValue <> OldValue then begin
             for ch := 0 to ChannelLimit do begin
                Channel[ch].yMin := -Channel[0].ADCMaxValue -1;
                Channel[ch].yMax := Channel[0].ADCMaxValue ;
                end ;
             end ;

          ReadInt( Header, 'NC=', fHDR.NumChannels ) ;

          ReadInt( Header, 'NP=', fHDR.NumSamplesInFile ) ;
          if fHDR.NumSamplesInFile < 0 then begin
             fHdr.NumSamplesInFile := FileSeek( fHDR.FileHandle, 0, 2 ) ;
             fHdr.NumSamplesInFile := (fHdr.NumSamplesInFile + 1
                                   - fHDR.NumBytesInHeader) div 2 ;
             end ;

          fHDR.RecordDuration := (fHDR.NumSamplesInFile*fHDR.dt) /
                                 fHDR.NumChannels ;
          fHDR.NumSamplesPerBlock := fHDR.NumChannels*NumSamplesPerSector ;
          fHDR.NumBytesPerBlock := fHDR.NumSamplesPerBlock*2 ;
          fHDR.NumBlocksInFile := fHDR.NumSamplesInFile div fHDR.NumSamplesPerBlock ;

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

          ReadFloat( Header, 'DT=', fHDR.dt );
          if fHDR.dt = 0.0 then begin
             fHDR.dt := 1.0 ;
             ShowMessage( 'Sampling Interval = 0.0 s. Changed to 1.0! (GetCDRHeader) ' );
             end ;

          { Time duration of recorded data }
          fHDR.RecordDuration := (fHDR.NumSamplesInFile*fHDR.dt) /
                                 fHDR.NumChannels ;

          { Event detector parameters }
          ReadInt( Header, 'DETCH=', Settings.EventDetector.Channel ) ;
          ReadInt( Header, 'DETRS=', Settings.EventDetector.RecordSize ) ;
          ReadFloat( Header, 'DETYT=', Settings.EventDetector.yThreshold ) ;
          ReadFloat( Header, 'DETTT=', Settings.EventDetector.tThreshold ) ;
          ReadFloat( Header, 'DETDD=', Settings.EventDetector.DeadTime ) ;
          ReadFloat( Header, 'DETBA=', Settings.EventDetector.BaselineAverage ) ;
          ReadFloat( Header, 'DETPF=', Settings.EventDetector.PreTriggerFraction ) ;
          ReadFloat( Header, 'DETAW=', Settings.EventDetector.AnalysisWindow ) ;
          ReadLogical( Header, 'DETPOSPK=', Settings.EventDetector.PositivePeaks ) ;
          ReadLogical( Header, 'DETBASST=', Settings.EventDetector.BaselineAtStart ) ;
          ReadLogical( Header, 'DETBASSUB=', Settings.EventDetector.SubtractBaseline ) ;
          ReadInt( Header, 'DETBASPTS=', Settings.EventDetector.NumBaselinePoints ) ;
          ReadInt( Header, 'DETBASGAP=', Settings.EventDetector.NumBaselineGap ) ;
          ReadFloat( Header, 'DETTDECP=', Settings.EventDetector.TDecayPercent ) ;
          ReadInt( Header, 'DETDECFR=', Settings.EventDetector.TDecayFrom ) ;
          ReadInt( Header, 'DETREW=', Settings.EventDetector.RisingEdgeWindow ) ;          

          ReadInt( Header, 'VARRS=', Settings.Variance.RecordSize ) ;
          ReadInt( Header, 'VAROV=', Settings.Variance.RecordOverlap ) ;
          ReadFloat( Header, 'VARTR=', Settings.Variance.TauRise ) ;
          ReadFloat( Header, 'VARTD=', Settings.Variance.TauDecay ) ;

          ReadFloat( Header, 'UNITC=', Settings.UnitCurrent ) ;
          ReadFloat( Header, 'DWTTH=', Settings.DwellTimes.Threshold ) ;

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

              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 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 Channel[ch].ADCName[1] = chr(0) then Channel[ch].ADCName := '??' ;
              ReadFloat( Header, format('YCF%d=',[ch]), Channel[ch].ADCCalibrationFactor) ;

              ReadFloat( Header, format('YAG%d=',[ch]), Channel[ch].ADCAmplifierGain) ;
              Channel[ch].ADCScale := CalibFactorToADCScale(
                                      FHDR.ADCVoltageRange,
                                      Channel[ch] ) ;

              ReadInt( Header, format('YZ%d=',[ch]), Channel[ch].ADCZero) ;
              ReadInt( Header, format('YR%d=',[ch]), Channel[ch].ADCZeroAt) ;
              end ;

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

          { Read Markers }
          NumMarkers := 0 ;
          ReadInt( Header, 'MKN=', NumMarkers ) ;
          MarkerList.Clear ;
          for i := 0 to NumMarkers-1 do begin
              ReadFloat( Header, format('MKTIM%d=',[i]), MarkerTime ) ;
              ReadString( Header, format('MKTXT%d=',[i]), MarkerText ) ;
              MarkerList.AddObject( MarkerText, TObject(MarkerTime) ) ;
              end ;


           { 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] )) ;

          { Name of any associated WCP data file }
          fHDR.WCPFileName := '' ;
          ReadString( Header, 'WCPFNAM=', fHDR.WCPFileName ) ;

          { Save the original file backed up flag }
          ReadLogical( Header, 'BAK=', fHDR.BackedUp ) ;

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

     end ;


function ReadCDRBuffer(
         var FHdr : TCDRFileHeader ;     { Data file header }
         BlockPointer : Integer ;        { Sample block to start reading at }
         var Buf : Array of SmallInt ;   { Buffer to hold samples }
         NumBlocksToRead : Integer       { Number of sample blocks to read }
         ) : Integer ;
{ -----------------------------------------------
  Read a buffer of A/D samples from EDR data file
  ----------------------------------------------- }
var
   NumBytes : Integer ;
begin
     FHdr.FilePointer := FileSeek( FHdr.FileHandle,
                                   (BlockPointer*FHdr.NumChannels*2)
                                   + FHdr.NumBytesInHeader, 0 ) ;
     NumBytes := NumBlocksToRead*FHdr.NumChannels*2 ;
     Result := FileRead(FHdr.FileHandle,Buf,NumBytes) div (FHdr.NumChannels*2) ;
     end ;


function WriteCDRBuffer(
         var FHdr : TCDRFileHeader ;     { Data file header }
         BlockPointer : Integer ;        { Sample block to start Writeing at }
         var Buf : Array of SmallInt ;   { Buffer to hold samples }
         NumBlocksToWrite : Integer       { Number of sample blocks to Write }
         ) : Integer ;
{ -----------------------------------------------
  Write a buffer of A/D samples to EDR data file
  ----------------------------------------------- }
var
   NumBytes : Integer ;
begin
     FHdr.FilePointer := FileSeek( FHdr.FileHandle,
                                   (BlockPointer*FHdr.NumChannels*2)
                                   + FHdr.NumBytesInHeader, 0 ) ;
     NumBytes := NumBlocksToWrite*FHdr.NumChannels*2 ;
     Result := FileWrite(FHdr.FileHandle,Buf,NumBytes) div (FHdr.NumChannels*2) ;
     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..IniFileHeaderSize] of char ;
   IniFileHandle : Integer ;
   i,ch,iValue : Integer ;
begin

     if not FileExists( IniFileName ) then Exit ;

     IniFileHandle := FileOpen( IniFileName, fmOpenReadWrite ) ;

     // Read file
     FileRead(IniFileHandle,Header,Sizeof(Header)) ;

     { Last raw data file used }
     //ReadString( Header, 'FILE=', CdrFH.FileName ) ;
     //CdrFH.FileName := '' ;

     { Get default no. channels }
     ReadInt( Header, 'NC=', Settings.NumChannels ) ;
     Settings.NumChannels := Max( 1,Settings.NumChannels ) ;

     { 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 (old keys)
     ReadInt( Header, 'AMP=',iValue ) ;
     Amplifier.AmplifierType[1] := iValue ;
     ReadInt( Header, 'AMPCH=',iValue ) ;
     Amplifier.GainTelegraphChannel[1] := iValue ;
     ReadInt( Header, 'AMPGAINCH=',iValue ) ;
     Amplifier.GainTelegraphChannel[1] := iValue ;
     ReadInt( Header, 'AMPMODECH=',iValue ) ;
     Amplifier.ModeTelegraphChannel[1] := iValue ;

     // Patch clamp amplifier data
     for ch := 1 to 2 do begin
         ReadInt( Header, format('AMP%d=',[ch]),iValue ) ;
         Amplifier.AmplifierType[ch] := iValue ;
         ReadInt( Header, format('AMPGAINCH%d=',[ch]), iValue ) ;
         Amplifier.GainTelegraphChannel[ch] := iValue ;
         ReadInt( Header, format('AMPMODECH%d=',[ch]), iValue ) ;
         Amplifier.ModeTelegraphChannel[ch] := iValue ;
         end ;

     ReadInt( Header, 'VARRS=', Settings.Variance.RecordSize ) ;
     ReadInt( Header, 'VAROV=', Settings.Variance.RecordOverlap ) ;
     ReadFloat( Header, 'VARTR=', Settings.Variance.TauRise ) ;
     ReadFloat( Header, 'VARTD=', Settings.Variance.TauDecay ) ;

     ReadFloat( Header, 'UNITC=', Settings.UnitCurrent ) ;
     ReadFloat( Header, 'DWTTH=', Settings.DwellTimes.Threshold ) ;

           { Read global settings }

           { *** Recording settings *** }
     { Recording trigger mode }
     ReadString( Header, 'TRG=', Settings.TriggerMode ) ;
     ReadLogical( Header, 'EXTTRIGAH=', Settings.ExternalTriggerActiveHigh ) ;

     { Event detector parameters }
     ReadInt( Header, 'DETCH=', Settings.EventDetector.Channel ) ;
     ReadInt( Header, 'DETRS=', Settings.EventDetector.RecordSize ) ;
     ReadFloat( Header, 'DETYT=', Settings.EventDetector.yThreshold ) ;
     ReadFloat( Header, 'DETTT=', Settings.EventDetector.tThreshold ) ;
     ReadFloat( Header, 'DETDD=', Settings.EventDetector.DeadTime ) ;
     ReadFloat( Header, 'DETBA=', Settings.EventDetector.BaselineAverage ) ;
     ReadFloat( Header, 'DETPF=', Settings.EventDetector.PreTriggerFraction ) ;
     ReadFloat( Header, 'DETAW=', Settings.EventDetector.AnalysisWindow ) ;
     ReadInt( Header, 'DETREW=', Settings.EventDetector.RisingEdgeWindow ) ;
     ReadFloat( Header, 'DETAVFI=', Settings.EventDetector.AvgFrequencyInterval ) ;

     { Number of records required (in free run/ext. trigger modes}
     ReadInt( Header, 'NRQ=', Settings.NumTriggerSweeps ) ;
     { Voltage clamp command voltage division factor }
     ReadFloat( Header, 'VCDIV=', Settings.VCommand[0].DivideFactor ) ;
     { Default voltage clamp holding potential setting }
     ReadFloat( Header, 'VCHOLD=', Settings.VCommand[0].HoldingVoltage ) ;
     ReadFloat( Header, 'VCHOLD2=', Settings.VCommand[0].HoldingVoltageAlt ) ;

     for i := 0 to High(Settings.VCommand) do begin
         ReadFloat( Header, format('VCDIV%d=',[i]), Settings.VCommand[i].DivideFactor ) ;
         ReadFloat( Header, format('VCHOLD%d=',[i]), Settings.VCommand[i].HoldingVoltage ) ;
         ReadFloat( Header, format('VCHOLD%d2=',[i]), Settings.VCommand[i].HoldingVoltageAlt ) ;
         end ;

     { Default digital control port output byte setting }
     ReadInt( Header, 'DIGPORT=', Settings.DigitalOutputs ) ;
     Settings.UpdateOutputs := True ;

     ReadFloat( Header, 'DTMINDAC=', Settings.MinDACInterval ) ;

     { 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 ) ;
     ReadLogical( Header, 'STASC=', Settings.SealTest.AutoScale ) ;

     Settings.ADCVoltageRangeIndex := 0 ;
     ReadInt( Header, 'ADVRI=', Settings.ADCVoltageRangeIndex ) ;

     ReadFloat( Header, 'DT=', Settings.ADCSamplingInterval );

     { Width/height of clipboard bitmaps }
     ReadInt( Header, 'BMAPW=', Settings.BitmapWidth ) ;
     ReadInt( Header, 'BMAPH=', Settings.BitmapHeight ) ;

     ReadLogical( Header, 'DISPGRID=', Settings.DisplayGrid ) ;
     ReadInt( Header, 'DISPNVG=', Settings.NumVerticalGridLines ) ;
     ReadInt( Header, 'DISPNHG=', Settings.NumHorizontalGridLines ) ;
     ReadFloat( Header, 'DISPDUR=', Settings.DisplayDuration ) ;

     { 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 ) ;

     // Page display module (PageView.pas) settings
     ReadInt( Header, 'PVLPP=',Settings.PageViewLinesPerPage ) ;
     ReadFloat( Header, 'PVLD=',Settings.PageViewLineDuration ) ;

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

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

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

     { Recently used data files }
     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] ) ;

     // Capacity settings
     ReadFloat( Header, 'CAPCMMX=', Settings.Capacity.CmDisplayMax ) ;
     ReadFloat( Header, 'CAPGSMX=', Settings.Capacity.GsDisplayMax ) ;
     ReadFloat( Header, 'CAPGMMX=', Settings.Capacity.GmDisplayMax ) ;
     ReadFloat( Header, 'CAPFREQ=', Settings.Capacity.Frequency ) ;
     ReadFloat( Header, 'CAPVREV=', Settings.Capacity.VRev ) ;
     ReadInt( Header, 'CAPGR=', Settings.Capacity.GrealChan) ;
     ReadInt( Header, 'CAPGI=', Settings.Capacity.GimagChan) ;
     ReadInt( Header, 'CAPIM=', Settings.Capacity.ImChan) ;
     ReadInt( Header, 'CAPVM=', Settings.Capacity.VmChan) ;
     ReadInt( Header, 'CAPGM=', Settings.Capacity.GmChan) ;
     ReadInt( Header, 'CAPGS=', Settings.Capacity.GsChan) ;
     ReadInt( Header, 'CAPCM=', Settings.Capacity.CmChan) ;
     ReadLogical( Header, 'CAPINVGR=', Settings.Capacity.InvertGReal) ;
     ReadLogical( Header, 'CAPINVGI=', Settings.Capacity.InvertGIMag) ;
     ReadFloat( Header, 'CAPRSCOMP=', Settings.Capacity.RSeriesComp ) ;
     REadFloat( Header, 'CAPCCOMP=', Settings.Capacity.CellCapacityComp ) ;
     ReadLogical( Header, 'CAPCOMPINUSE=', Settings.Capacity.CapacityCompensationInUse) ;
     ReadLogical( Header, 'CAPINUSE=', Settings.Capacity.InUse) ;

     // Fluorescence settings
     ReadFloat( Header, 'FLRMAX=', Settings.Fluorescence.RMax ) ;
     ReadFloat( Header, 'FLRMIN=', Settings.Fluorescence.RMin ) ;
     ReadFloat( Header, 'FLKEFF=', Settings.Fluorescence.KEff ) ;
     ReadFloat( Header, 'FLTHRE=', Settings.Fluorescence.FThreshold ) ;
     ReadInt( Header, 'FLNUM=', Settings.Fluorescence.NumerChan ) ;
     ReadInt( Header, 'FLDEN=', Settings.Fluorescence.DenomChan ) ;
     ReadInt( Header, 'FLRAT=', Settings.Fluorescence.RatioChan ) ;
     ReadInt( Header, 'FLCON=', Settings.Fluorescence.ConcChan ) ;
     ReadFloat( Header, 'FLRDMX=', Settings.Fluorescence.RatioDisplayMax ) ;
     ReadFloat( Header, 'FLCDMX=', Settings.Fluorescence.ConcDisplayMax ) ;

     // Real time event frequency settings
     ReadInt( Header, 'EFCH=', Settings.RTEventAnalysis.Channel ) ;
     ReadFloat( Header, 'EFTHRESH=', Settings.RTEventAnalysis.DetectionThreshold ) ;
     ReadFloat( Header, 'EFRMEAN=', Settings.RTEventAnalysis.RunningMeanTime ) ;
     ReadFloat( Header, 'EFDEADT=', Settings.RTEventAnalysis.DeadTime ) ;

     // Real time resistance settings
     ReadInt( Header, 'RESICH=', Settings.RTResistance.ImChannel ) ;
     ReadInt( Header, 'RESVCH=', Settings.RTResistance.VmChannel ) ;
     ReadFloat( Header, 'RESAMPLITUDE=', Settings.RTResistance.Amplitude ) ;
     ReadFloat( Header, 'RESDUR=', Settings.RTResistance.Duration ) ;
     ReadFloat( Header, 'RESINT=', Settings.RTResistance.Interval ) ;
     ReadInt( Header, 'RESPLT=', Settings.RTResistance.Plot ) ;

     // Currently selected stimulus file
     ReadString( Header, 'STIMFILE=', Settings.StimFileName ) ;

     { Get channel names, units, scale factors }
     for ch := 0 to Settings.NumChannels-1 do begin
              RecChannel[ch].ADCUnits := '??' ;
              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 := '??' ;
              RecChannel[ch].ADCName := '??' ;
              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('YCF%d=',[ch]),RecChannel[ch].ADCCalibrationFactor) ;
              ReadFloat( Header, format('YAG%d=',[ch]), RecChannel[ch].ADCAmplifierGain) ;
              RecChannel[ch].ADCScale := CalibFactorToADCScale(
                                         Main.SESLabIO.ADCVoltageRange,
                                         RecChannel[ch] ) ;
              end ;

     FileClose( IniFileHandle ) ;

     end ;


procedure SaveInitializationFile( const IniFileName : string ) ;
{ --------------------------------------------
  Save program settings to Initialization file
  --------------------------------------------}
var
   Header : array[1..IniFileHeaderSize] 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=', CdrFH.FileName ) ;
     AppendLogical( Header, '16BIT=', Settings.Resolution16Bit ) ;

     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
     for ch := 1 to 2 do begin
         AppendInt( Header, format('AMP%d=',[ch]),Amplifier.AmplifierType[ch] ) ;
         AppendInt( Header, format('AMPGAINCH%d=',[ch]), Amplifier.GainTelegraphChannel[ch] ) ;
         AppendInt( Header, format('AMPMODECH%d=',[ch]), Amplifier.ModeTelegraphChannel[ch] ) ;
         end ;

     { Recording settings }
     AppendString( Header, 'TRG=', Settings.TriggerMode ) ;
     AppendLogical( Header, 'EXTTRIGAH=', Settings.ExternalTriggerActiveHigh ) ;

     { Event detector parameters }
     AppendInt( Header, 'DETCH=', Settings.EventDetector.Channel ) ;
     AppendInt( Header, 'DETRS=', Settings.EventDetector.RecordSize ) ;
     AppendFloat( Header, 'DETYT=', Settings.EventDetector.yThreshold ) ;
     AppendFloat( Header, 'DETTT=', Settings.EventDetector.tThreshold ) ;
     AppendFloat( Header, 'DETDD=', Settings.EventDetector.DeadTime ) ;
     AppendFloat( Header, 'DETBA=', Settings.EventDetector.BaselineAverage ) ;
     AppendFloat( Header, 'DETPTF=', Settings.EventDetector.PreTriggerFraction ) ;
     AppendFloat( Header, 'DETAW=', Settings.EventDetector.AnalysisWindow ) ;
     AppendInt( Header, 'DETREW=', Settings.EventDetector.RisingEdgeWindow ) ;
     AppendFloat( Header, 'DETAVFI=', Settings.EventDetector.AvgFrequencyInterval ) ;

     AppendInt( Header, 'VARRS=', Settings.Variance.RecordSize ) ;
     AppendInt( Header, 'VAROV=', Settings.Variance.RecordOverlap ) ;
     AppendFloat( Header, 'VARTR=', Settings.Variance.TauRise ) ;
     AppendFloat( Header, 'VARTD=', Settings.Variance.TauDecay ) ;

     AppendFloat( Header, 'UNITC=', Settings.UnitCurrent ) ;
     AppendFloat( Header, 'DWTTH=', Settings.DwellTimes.Threshold ) ;

     AppendInt( Header, 'NRQ=', Settings.NumTriggerSweeps ) ;

     AppendFloat( Header, 'VCDIV=', Settings.VCommand[0].DivideFactor ) ;
     AppendFloat( Header, 'VCHOLD=', Settings.VCommand[0].HoldingVoltage ) ;
     AppendFloat( Header, 'VCHOLD2=', Settings.VCommand[0].HoldingVoltageAlt ) ;

     for i := 0 to High(Settings.VCommand) do begin
         AppendFloat( Header, format('VCDIV%d=',[i]), Settings.VCommand[i].DivideFactor ) ;
         AppendFloat( Header, format('VCHOLD%d=',[i]), Settings.VCommand[i].HoldingVoltage ) ;
         AppendFloat( Header, format('VCHOLD%d2=',[i]), Settings.VCommand[i].HoldingVoltageAlt ) ;
         end ;

     AppendInt( Header, 'DIGPORT=', Settings.DigitalOutputs ) ;
     AppendFloat( Header, 'DTMINDAC=', Settings.MinDACInterval ) ;
     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 ) ;
     AppendLogical( Header, 'STASC=', Settings.SealTest.AutoScale ) ;

     AppendInt( Header, 'NC=', Settings.NumChannels ) ;
     AppendInt( Header, 'ADVRI=', Settings.ADCVoltageRangeIndex ) ;
     AppendFloat( Header, 'DT=', Settings.ADCSamplingInterval ) ;

     { Width/height of clipboard bitmaps }
     AppendInt( Header, 'BMAPW=', Settings.BitmapWidth ) ;
     AppendInt( Header, 'BMAPH=', Settings.BitmapHeight ) ;

     AppendLogical( Header, 'DISPGRID=', Settings.DisplayGrid ) ;
     AppendInt( Header, 'DISPNVG=', Settings.NumVerticalGridLines ) ;
     AppendInt( Header, 'DISPNHG=', Settings.NumHorizontalGridLines ) ;
     AppendFloat( Header, 'DISPDUR=', Settings.DisplayDuration ) ;

     { 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 ) ;

     // Page display module (PageView.pas) settings
     AppendInt( Header, 'PVLPP=',Settings.PageViewLinesPerPage ) ;
     AppendFloat( Header, 'PVLD=',Settings.PageViewLineDuration ) ;

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

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

     { Recently used data files }
     AppendString( Header, 'RF0=', Settings.RecentFiles[0] ) ;
     AppendString( Header, 'RF1=', Settings.RecentFiles[1] ) ;
     AppendString( Header, 'RF2=', Settings.RecentFiles[2] ) ;
     AppendString( Header, 'RF3=', Settings.RecentFiles[3] ) ;

     AppendFloat( Header, 'CAPCMMX=', Settings.Capacity.CmDisplayMax ) ;
     AppendFloat( Header, 'CAPGSMX=', Settings.Capacity.GsDisplayMax ) ;
     AppendFloat( Header, 'CAPGMMX=', Settings.Capacity.GmDisplayMax ) ;
     AppendFloat( Header, 'CAPFREQ=', Settings.Capacity.Frequency ) ;
     AppendFloat( Header, 'CAPVREV=', Settings.Capacity.VRev ) ;
     AppendInt( Header, 'CAPGR=', Settings.Capacity.GrealChan) ;
     AppendInt( Header, 'CAPGI=', Settings.Capacity.GimagChan) ;
     AppendInt( Header, 'CAPIM=', Settings.Capacity.ImChan) ;
     AppendInt( Header, 'CAPVM=', Settings.Capacity.VmChan) ;
     AppendInt( Header, 'CAPGM=', Settings.Capacity.GmChan) ;
     AppendInt( Header, 'CAPGS=', Settings.Capacity.GsChan) ;
     AppendInt( Header, 'CAPCM=', Settings.Capacity.CmChan) ;
     AppendLogical( Header, 'CAPINVGR=', Settings.Capacity.InvertGReal) ;
     AppendLogical( Header, 'CAPINVGI=', Settings.Capacity.InvertGImag) ;
     AppendFloat( Header, 'CAPRSCOMP=', Settings.Capacity.RSeriesComp ) ;
     AppendFloat( Header, 'CAPCCOMP=', Settings.Capacity.CellCapacityComp ) ;
     AppendLogical( Header, 'CAPCOMPINUSE=', Settings.Capacity.CapacityCompensationInUse) ;
     AppendLogical( Header, 'CAPINUSE=', Settings.Capacity.InUse) ;

     // Fluorescence settings
     AppendFloat( Header, 'FLRMAX=', Settings.Fluorescence.RMax ) ;
     AppendFloat( Header, 'FLRMIN=', Settings.Fluorescence.RMin ) ;
     AppendFloat( Header, 'FLKEFF=', Settings.Fluorescence.KEff ) ;
     AppendFloat( Header, 'FLTHRE=', Settings.Fluorescence.FThreshold ) ;
     AppendInt( Header, 'FLNUM=', Settings.Fluorescence.NumerChan ) ;
     AppendInt( Header, 'FLDEN=', Settings.Fluorescence.DenomChan ) ;
     AppendInt( Header, 'FLRAT=', Settings.Fluorescence.RatioChan ) ;
     AppendInt( Header, 'FLCON=', Settings.Fluorescence.ConcChan ) ;
     AppendFloat( Header, 'FLRDMX=', Settings.Fluorescence.RatioDisplayMax ) ;
     AppendFloat( Header, 'FLCDMX=', Settings.Fluorescence.ConcDisplayMax ) ;

     // Real time event frequency settings
     AppendInt( Header, 'EFCH=', Settings.RTEventAnalysis.Channel ) ;
     AppendFloat( Header, 'EFTHRESH=', Settings.RTEventAnalysis.DetectionThreshold ) ;
     AppendFloat( Header, 'EFRMEAN=', Settings.RTEventAnalysis.RunningMeanTime ) ;
     AppendFloat( Header, 'EFDEADT=', Settings.RTEventAnalysis.DeadTime ) ;

     // Real time resistance settings
     AppendInt( Header, 'RESICH=', Settings.RTResistance.ImChannel ) ;
     AppendInt( Header, 'RESVCH=', Settings.RTResistance.VmChannel ) ;
     AppendFloat( Header, 'RESAMPLITUDE=', Settings.RTResistance.Amplitude ) ;
     AppendFloat( Header, 'RESDUR=', Settings.RTResistance.Duration ) ;
     AppendFloat( Header, 'RESINT=', Settings.RTResistance.Interval ) ;
     AppendInt( Header, 'RESPLT=', Settings.RTResistance.Plot ) ;

     // Currently selected stimulus file
     AppendString( Header, 'STIMFILE=', Settings.StimFileName ) ;

     for ch := 0 to Settings.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('YCF%d=',[ch]),RecChannel[ch].ADCCalibrationFactor) ;
         AppendFloat( Header, format('YAG%d=',[ch]), RecChannel[ch].ADCAmplifierGain) ;
         end ;

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

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

     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
              //ShowMessage( ' WinEDR - Cannot create Log File' ) ;
              LogFileAvailable := False ;
              end ;
           end ;
     { If file doesn't exist ... disable access }
     Main.InspectLogFile.Enabled := LogFileAvailable ;

     end ;


procedure WriteToLogFile( Line : string ) ;
begin
     if LogFileAvailable then WriteLn( LogFile, TimeToStr(Time) + ' ' + Line ) ;
     end ;


procedure CloseLogFile ;
begin

     if not LogFileAvailable then Exit ;

     try
        CloseFile(LogFile) ;
     except
        on EInOutError do begin
              ShowMessage( ' WinEDR - Error closing Log File' ) ;
              LogFileAvailable := False ;
              end ;
           end ;
     end ;


procedure MakeBackupFile ;
{ -------------------------------------------------
  Copy data file to backup file with .BAK extension
  ------------------------------------------------- }
var
   FileName : string ;
   FileHandle : Integer ;
   NumBytes,nRead : Integer ;
   Buf : Array[0..255] of SmallInt ;
   Done : Boolean ;
begin

     if CdrFH.BackedUp then Exit ;

     { Create backup file }
     FileName := ChangeFileExt( cdrFH.FileName, '.bak' ) ;
     FileHandle := FileCreate( FileName ) ;
     if FileHandle < 0 then begin
        ShowMessage( 'Error creating ' + FileName ) ;
        Exit ;
        end ;

     // Save latest header block data
     CdrFH.BackedUp := True ;
     SaveCDRHeader( CdrFH ) ;

     { Copy data to backup }

     { No. of bytes to be copied }
     NumBytes := FileSeek( CdrFH.FileHandle, 0, 2 ) + 1 ;

     { Point to start of data files }
     CdrFH.FilePointer := FileSeek( CdrFH.FileHandle, 0, 0 ) ;
     FileSeek( FileHandle, 0, 0 ) ;

     Done := False ;
     while not Done do begin
           // Read from EDR file
           nRead := FileRead(CdrFH.FileHandle,Buf,Sizeof(Buf)) ;
           // Write to backup file
           if nRead > 0 then begin
              if FileWrite(FileHandle,Buf,nRead) < nRead then begin
                 ShowMessage( 'Backup: Error writing ' + FileName ) ;
                 WriteToLogFile( 'Backup: Error writing ' + FileName ) ;
                 Done := True ;
                 end ;
              end
           else Done := True ;

           NumBytes := NumBytes - nRead ;
           if NumBytes <= 0 then Done := True ;

           end ;

     { Close backup file }
     FileClose( FileHandle ) ;

     Main.mnRestoreOriginal.Enabled := CdrFH.BackedUp ;

     WriteToLogFile( cdrFH.FileName + ' saved to ' + FileName ) ;

     end ;


procedure RestoreFromBackupFile ;
{ -------------------------------------------------
  Restore original data file from backup file
  ------------------------------------------------- }
var
   FileName : string ;
   FileHandle : Integer ;
   NumBytes,nRead : Integer ;
   Buf : Array[0..255] of SmallInt ;
   Done : Boolean ;
begin

     // Create back up file name
     FileName := ChangeFileExt( cdrFH.FileName, '.bak' ) ;

     if (not CdrFH.BackedUp) or (not FileExists(FileName)) then Exit ;

     { Open backup file }
     FileHandle := FileOpen( FileName, fmOpenRead ) ;
     if FileHandle < 0 then begin
        ShowMessage( 'Restore: Unable to open ' + FileName ) ;
        Exit ;
        end ;

     { No. of bytes to be copied }
     NumBytes := FileSeek( FileHandle, 0, 2 ) + 1 ;

     { Point to start of files }
     FileSeek( FileHandle, 0, 0 ) ;
     CdrFH.FilePointer := FileSeek( CdrFH.FileHandle, 0, 0 ) ;

     { Copy data from backup }
     Done := False ;
     while not Done do begin

           // Read data from backup
           nRead := FileRead(FileHandle,Buf,Sizeof(Buf)) ;

           // Write to EDR file
           if nRead > 0 then begin
              if FileWrite(CdrFH.FileHandle,Buf,nRead) <> nRead then begin
                 ShowMessage( 'Restore: Error writing ' + CdrFH.FileName ) ;
                 WriteToLogFile( 'Restore: Error writing ' + FileName ) ;
                 Done := True ;
                 end ;
              end
           else Done := True ;

           NumBytes := NumBytes - nRead ;
           if NumBytes <= 0 then Done := True ;

           end ;

     { Update header record with restored data }
     GetCDRHeader( CdrFH ) ;

     { Close backup file }
     FileClose( FileHandle ) ;

     WriteToLogFile( cdrFH.FileName + ' restored from ' + FileName ) ;

     end ;


{ ***** WinWCP V3.0 data file I/O routines ************************* }

procedure SaveWCPHeader(
          var fHDR : TWCPFileHeader
          ) ;
{ ---------------------------------------
  Save file header data to WCP data file
  ---------------------------------------}
var
   Header : array[1..NumBytesInWCPFileHeader] 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) ;

     { File header version 24/2/98 }
     fHDR.Version := 6.2 ;

     AppendFloat( Header, 'VER=',fHDR.Version );

     // 15/2/02 Added to distinguish between 12 and 16 bit data files
     AppendInt( Header, 'ADCMAX=', Main.SESLabIO.ADCMaxValue ) ;

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

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

     AppendInt( Header, 'NR=', fHDR.NumRecords ) ;
     AppendFloat( Header, 'DT=',fHDR.dt );  { Note conversion ms->s}

     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 ) ;
         AppendFloat( Header, format('YAG%d=',[ch]), Channel[ch].ADCAmplifierGain) ;
         AppendInt( Header, format('YZ%d=',[ch]), Channel[ch].ADCZero) ;
         AppendInt( Header, format('YR%d=',[ch]), Channel[ch].ADCZeroAt) ;
         end ;

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

     i := 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]) ) ;

     end ;


procedure GetWCPHeader(
          var fHDR : TWCPFileHeader
          ) ;
{ -----------------------------------------------------------
  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
  -----------------------------------------------------------}
var
   Header : array[1..NumBytesInWCPFileHeader] of char ;
   RecordStatusArray : array[0..3] of char ;
   RecordStatus : string ;
   i,NumBytesInFile,NumRecords,RecordCounter,ch,FilePointer,ADCMaxValue : Integer ;
   Done : Boolean ;
begin

     { 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
               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 ;

     if RecordCounter = 0 then begin
        fHDR.NumBytesInHeader := 1024 ;
        fHDR.NumAnalysisBytesPerRecord := 1024 ;
        fHDR.NumBytesPerRecord := 0 ;
        end
     else if RecordCounter = 1 then begin
        fHDR.NumBytesPerRecord := FileSeek(fHDR.FileHandle,0,2) - fHDR.NumBytesInHeader ;
        end ;

     FileSeek( fHDR.FileHandle, 0, 0 ) ;
     if FileRead( fHDR.FileHandle, Header, Sizeof(Header) )
        = Sizeof(Header) then begin

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

          // 15/2/02 Added to distinguish between 12 and 16 bit data files
          ADCMaxValue := 0 ;
          ReadInt( Header, 'ADCMAX=', ADCMaxValue ) ;
          if ADCMaxValue = 0 then ADCMaxValue := 2047 ;

          fHDR.NumBytesInHeader := NumBytesInWCPFileHeader ;
          ReadInt( Header, 'NBH=', fHDR.NumBytesInHeader ) ;

          ReadInt( Header, 'NC=', fHDR.NumChannels ) ;

          i := fHDR.NumChannels*2 ;
          ReadInt( Header, 'NBD=', i ) ;
          fHDR.NumDataBytesPerRecord := i * 512 ;
          fHDR.NumSamplesPerRecord := fHDR.NumDataBytesPerRecord div 2 ;
          fHDR.NumSamples := fHDR.NumSamplesPerRecord div fHDR.NumChannels ;

          { 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 ;



          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
                MessageDlg( ' Number of records in file corrected.',
                              mtWarning, [mbOK], 0 ) ;
                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].ADCMaxValue := ADCMaxValue ;

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

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

              Channel[ch].ADCAmplifierGain := 1. ;
              ReadFloat( Header, format('YAG%d=',[ch]), Channel[ch].ADCAmplifierGain) ;
              if Channel[ch].ADCAmplifierGain = 0. then Channel[ch].ADCAmplifierGain := 1. ;

              { 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 ;

              end ;

          { 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
          MessageDlg( ' File Header Read - Failed ', mtWarning, [mbOK], 0 ) ;

     end ;


procedure PutWCPRecord(
          var fHDR : TWCPFileHeader;         { File header block }
          var rH : TWCPRecHeader ;           { 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

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

     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 ) ;

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

     end ;


procedure PutWCPRecordHeaderOnly(
          var fHDR : TWCPFileHeader;    { Data file header }
          const rH : TWCPRecHeader ;    { Record header }
          RecordNum : Integer ) ;    { Record number }
{ -------------------------------------------------------
  Write a WCP V3.0 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 GetWCPRecord(
          var fHDR : TWCPFileHeader;      { Data file header }
          var rH : TWCPRecHeader ;        { Record header }
          RecordNum : Integer ;        { Record number }
          var dBuf : Array of SmallInt { A/D data array }
          ) ;
{ --------------------------------------------------
  Read a WCP V3.0 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 }
     GetWCPRecordHeaderOnly( fHDR, RH, RecordNum ) ;

     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
        WriteToLogFile( format('Error reading file %s, Rec=%d',
                              [fHDR.FileName,fHDR.RecordNum])) ;

     { If this is an old (pre WinWCP V3.0) file format shift data by 2048 }
     if FHDR.NumAnalysisBytesPerRecord = 512 then begin
        for i := 0 to FHDR.NumChannels*FHDR.NumSamples-1 do dBuf[i] := dBuf[i] - 2048 ;
        for ch := 0 to FHDR.NumChannels-1 do
            Channel[ch].ADCZero := Channel[ch].ADCZero - 2048 ;
        end ;

     { 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 := MinInt([MaxInt( [i0,0] ),FHDR.NumSamples-1] ) ;
            i1 := i0 + FHDR.NumZeroAvg - 1 ;
            i1 := MinInt([MaxInt( [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 GetWCPRecordHeaderOnly(
          var fHDR : TWCPFileHeader;      { Data file header }
          var rH : TWCPRecHeader ;        { Record header }
          RecordNum : Integer          { Record number }
          ) ;
{ --------------------------------------------------------
  Read a WCP V3.0 format digital signal record header from file
  --------------------------------------------------------}
var
   cBuf : array[1..20] of char ;
   i,ch  : 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 }

     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) ) ;

     { If this is an old (pre WinWCP V3.0) file format re-scale sampling interval }
     if fHDR.NumAnalysisBytesPerRecord = 512 then begin
        rH.dt := rH.dt*msToSecs ;
        end ;

     { 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 := rH.ADCVoltageRange[ch] /
                          ( Channel[ch].ADCCalibrationFactor * (Channel[ch].ADCMaxValue+1) ) ;
         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] ;

     end ;

{ ******************************************************************************** }


procedure GaussianFilter(
          const FHdr : TWCPFileHeader ;
          Var Buf : Array of SmallInt ;
          CutOffFrequency : single
          ) ;
{ --------------------------------------------------
  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 : ^TIntArray ;
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. ;
            a[-1] := a[1] ;
	    a[0] := 1. - 2.*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 ;
end.
