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.
  }


interface

uses shared,global,sysUtils,dialogs, maths;

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 : LongInt ;
                     var dBuf : Array of Integer ) ;
procedure PutRecordHeaderOnly( var fHDR : TFileHeader;
                               Const rH : TRecHeader ;
                               RecordNum : LongInt ) ;
procedure GetRecord( var fHDR : TFileHeader;
                     var rH : TRecHeader ;
                     RecordNum : LongInt ;
                     var dBuf : Array of Integer ) ;
procedure GetRecordHeaderOnly( var fHDR : TFileHeader;
                               var rH : TRecHeader ;
                               RecordNum : LongInt ) ;
procedure OpenLogFile ;
procedure WriteToLogFile( Line : string ) ;
procedure WriteToLogFileNoDate( Line : string ) ;
procedure CloseLogFile ;

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

uses mdiform ;

var
   LogFile : TextFile ;
   LogFileAvailable : boolean ;


procedure SaveHeader( fHDR : TFileHeader ) ;
{ ---------------------------------------
  Save file header data to WCP data file
  ---------------------------------------}
var
   Header : array[1..512] of char ;
   i : LongInt ;
   y : single ;
   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 );

     AppendInt( Header, 'NC=', fHDR.NumChannels ) ;
     fHDR.NumDataBytesPerRecord := fHDR.NumSamples*fHDR.NumChannels*2 ;
     fHDR.NumAnalysisBytesPerRecord := 512 ;
     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*1000. );  { 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*1000. ) ;
         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 ) ;
     i := FileWrite( fHDR.FileHandle, Header, Sizeof(Header) ) ;
     if i <> Sizeof(Header) then
     MessageDlg( ' File Header Write - Failed ', mtWarning, [mbOK], 0 ) ;

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

     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
  -----------------------------------------------------------}
var
   Header : array[1..512] of char ;
   i,NumBytesInFile,NumRecords : LongInt ;
   ch : Integer ;
begin

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

          fHDR.NumBytesInHeader := 512 ;

          ReadFloat( Header, 'VER=',fHDR.Version );
          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 ;

          i := 1 ;
          ReadInt( Header, 'NBA=', i ) ;
          i := 1 ;
          fHDR.NumAnalysisBytesPerRecord := i*512 ;

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

          { Note. For compatibility with older Strathclyde
            programs 'ADCVoltageRange' is written to file
            as VOLTS but is mV internally' }
          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 );
          fHDR.dt := fHDR.dt / 1000. ;        { Convert ms -> s }

          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 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 /
                                                  1000. ;

              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 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..2048] of char ;
   ch,IniFileHandle : 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 ) ;

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

           { Send new values to CED 1902 }
           if Settings.CED1902.InUse then SetCED1902(Settings.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 := 512 ;

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

           { 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.VCommand.DivideFactor ) ;
           { Default voltage clamp holding potential setting }
           ReadFloat( Header, 'VCHOLD=', Settings.VCommand.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 ) ;

           { 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 ) ;
           for ch := 0 to RawFH.NumChannels-1 do begin
              ReadInt(Header,format('STYMIN%d=',[ch]),Settings.SealTest.yMin[ch] ) ;
              ReadInt(Header,format('STYMAX%d=',[ch]),Settings.SealTest.yMax[ch] ) ;
              end ;


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

           RawFH.dt := RawFH.dt*SecsToMs ;
           ReadFloat( Header, 'DT=', RawFH.dt );
           RawFH.dt := RawFH.dt*MsToSecs ;        { Convert ms -> s }

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

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

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

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

           { Digidata 1200 port settings }
           ReadInt( Header, 'DD12IO=',Settings.DD1200IOPort ) ;
           ReadInt( Header, 'DD12IRQ=',Settings.DD1200IRQ ) ;
           ReadInt( Header, 'DD12DMA=',Settings.DD1200DMA ) ;
           { National Instruments boards Disable DMA channels flag }
           ReadLogical( Header, 'NINODMA=',Settings.NIDisableDMA ) ;

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


           { Get channel names, units, scale factors }
           for ch := 0 to RawFH.NumChannels-1 do begin
              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 := '??' ;
              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 /
                                                  1000. ;

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

              end ;
           end ;
        end ;
        FileClose( IniFileHandle ) ;

     end ;


procedure SaveInitializationFile( const IniFileName : string ) ;
{ --------------------------------------------
  Save program settings to Initialization file
  --------------------------------------------}
type
    TKeyword = string[6] ;
var
   Header : array[1..2048] of char ;
   i,ch : Integer ;
   IniFileHandle : Integer ;
   Keyword : TKeyword ;
   y : single ;
begin
     IniFileHandle := FileCreate( IniFileName ) ;
     { 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 ) ;

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

     { 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, 'AER=', Settings.AutoErase ) ;
     AppendInt( Header, 'NRQ=', Settings.NumRecordsRequired ) ;
     AppendString( Header, 'FILEVPR=', Settings.VProgramFileName ) ;
     AppendFloat( Header, 'VCDIV=', Settings.VCommand.DivideFactor ) ;
     AppendFloat( Header, 'VCHOLD=', Settings.VCommand.HoldingVoltage ) ;
     AppendInt( Header, 'DIGPORT=', Settings.DigitalPort.Value ) ;
     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 ) ;
     for ch := 0 to RawFH.NumChannels-1 do begin
         AppendInt(Header,format('STYMIN%d=',[ch]),Settings.SealTest.yMin[ch] ) ;
         AppendInt(Header,format('STYMAX%d=',[ch]),Settings.SealTest.yMax[ch] ) ;
         end ;

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

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

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

     AppendFloat( Header, 'DT=',RawFH.dt*SecsToMs );  { Note conversion ms->s}

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

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

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

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

     { Digidata 1200 port settings }
     AppendInt( Header, 'DD12IO=',Settings.DD1200IOPort ) ;
     AppendInt( Header, 'DD12IRQ=',Settings.DD1200IRQ ) ;
     AppendInt( Header, 'DD12DMA=',Settings.DD1200DMA ) ;
     { National Instruments boards Disable DMA channels flag }
     AppendLogical( Header, 'NINODMA=',Settings.NIDisableDMA ) ;

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

     for ch := 0 to RawFH.NumChannels-1 do begin
         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*1000. ) ;
         AppendFloat( Header, format('YAG%d=',[ch]), Channel[ch].ADCAmplifierGain) ;
         end ;

     if FileWrite( IniFileHandle, Header, Sizeof(Header) ) <> Sizeof(Header) then
        MessageDlg( IniFileName + ' Write - Failed ', mtWarning, [mbOK], 0 ) ;
     FileClose( IniFileHandle ) ;
     end ;


procedure PutRecord( var fHDR : TFileHeader;
                     var rH : TRecHeader ;
                     RecordNum : LongInt ;
                     var dBuf : Array of Integer ) ;
{ -------------------------------------------------
  Write a WCP format digital signal record to file
  -------------------------------------------------}
var
   err,FilePointer,StartAt,nWritten : LongInt ;
   Value : single ;
   i,ch : Integer ;
   cBuf : array[1..20] of char ;
begin
     fHDR.RecordNum := RecordNum ;
     fHDR.NumDataBytesPerRecord := fHDR.NumSamples*fHDR.NumChannels*2 ;
     fHDR.NumBytesPerRecord := fHDR.NumDataBytesPerRecord +
                               fHDR.NumAnalysisBytesPerRecord ;
     FilePointer := (RecordNum-1)*fHDR.NumBytesPerRecord + fHDR.NumBytesInHeader ;
     { Write record header block to file }
     StartAt := FileSeek( fHDR.FileHandle, FilePointer, 0 ) ;

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

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

     err := FileWrite( FHDR.FileHandle, rH.Number, sizeof(rH.Number) ) ;
     err := FileWrite( FHDR.FileHandle, rH.Time, sizeof(rH.Time) ) ;
     { Note rH.dt converted s --> ms }
     Value := rH.dt*1000. ;
     err := FileWrite( FHDR.FileHandle, Value, sizeof(rH.dt) ) ;

     { Write A/D voltage range }
     err := FileWrite(FHDR.FileHandle,rH.ADCVoltageRange,sizeof(rH.ADCVoltageRange)) ;

     { Write Analysis block }
     err := FileWrite(FHDR.FileHandle,rH.Analysis.Available,sizeof(rH.Analysis.Available)) ;
     err := FileWrite(FHDR.FileHandle, rH.Analysis.Value, sizeof(rH.Analysis.Value) ) ;
     err := 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] := ' ' ;
     err := FileWrite( FHDR.FileHandle, cBuf, 16 ) ;
     {nWritten := FileSeek( FHDR.FileHandle,0,1) - StartAt ;}

     err := FileSeek(FHDR.FileHandle,FilePointer + fHDR.NumAnalysisBytesPerRecord,0) ;
     { Offset data by 2048 (to be compatible with WCP) }
     for i := 0 to (fHDR.NumSamples*fHDR.NumChannels)-1 do dBuf[i] := dBuf[i] + 2048 ;
     if FileWrite( FHDR.FileHandle, dBuf, FHDR.NumDataBytesPerRecord )
        <> FHDR.NumDataBytesPerRecord then
        MessageDlg( FHDR.FileName + ' Write - Failed ', mtWarning, [mbOK], 0 ) ;
     end ;


procedure PutRecordHeaderOnly( var fHDR : TFileHeader;
                               const rH : TRecHeader ;
                               RecordNum : LongInt ) ;
{ -------------------------------------------------------
  Write a WCP format digital signal record header to file
  -------------------------------------------------------}
var
   err,FilePointer : LongInt ;
   Value : single ;
   i,ch : Integer ;
   cBuf : array[1..20] of char ;
begin
     fHDR.RecordNum := RecordNum ;
     fHDR.NumDataBytesPerRecord := fHDR.NumSamples*fHDR.NumChannels*2 ;
     fHDR.NumBytesPerRecord := fHDR.NumDataBytesPerRecord +
                               fHDR.NumAnalysisBytesPerRecord ;
     FilePointer := (RecordNum-1)*FHDR.NumBytesPerRecord + FHDR.NumBytesInHeader ;
     { Write record header block to file }
     err := FileSeek( FHDR.FileHandle, FilePointer, 0 ) ;

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

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

     err := FileWrite( FHDR.FileHandle, rH.Number, sizeof(rH.Number) ) ;
     err := FileWrite( FHDR.FileHandle, rH.Time, sizeof(rH.Time) ) ;
     { Note rH.dt converted s --> ms }
     Value := rH.dt*1000. ;
     err := FileWrite( FHDR.FileHandle, Value, sizeof(rH.dt) ) ;

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

     { Write Analysis block }
     err := FileWrite(FHDR.FileHandle,rH.Analysis.Available,sizeof(rH.Analysis.Available)) ;
     err := FileWrite(FHDR.FileHandle, rH.Analysis.Value, sizeof(rH.Analysis.Value) ) ;
     err := 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] := ' ' ;
     err := FileWrite( FHDR.FileHandle, cBuf, 16 ) ;
     end ;


procedure GetRecord( var fHDR : TFileHeader;
                     var rH : TRecHeader ;
                     RecordNum : LongInt ;
                     var dBuf : Array of Integer ) ;
{ --------------------------------------------------
  Read a WCP format digital signal record from file
  --------------------------------------------------}
var
   err,FilePointer : LongInt ;
   cBuf : array[1..20] of char ;
   i,ch,i0,i1,Sum,ChOffset : LongInt ;
begin
     fHDR.RecordNum := RecordNum ;
     fHDR.NumDataBytesPerRecord := fHDR.NumSamples*fHDR.NumChannels*2 ;
     fHDR.NumBytesPerRecord := fHDR.NumDataBytesPerRecord +
                               fHDR.NumAnalysisBytesPerRecord ;
     FilePointer := (RecordNum-1)*fHDR.NumBytesPerRecord + fHDR.NumBytesInHeader ;
     { Read record header block from file }
     err := FileSeek( FHDR.FileHandle, FilePointer, 0 ) ;
     err := FileRead( FHDR.FileHandle, cBuf, 8 );
     rH.Status := '' ;
     for i := 1 to 8 do Rh.Status := rH.Status + cBuf[i] ;

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

     err := FileRead( FHDR.FileHandle, rH.Number, sizeof(rH.Number) ) ;
     err := FileRead( FHDR.FileHandle, rH.Time, sizeof(rH.Time) ) ;
     err := FileRead( FHDR.FileHandle, rH.dt, sizeof(rH.dt) ) ;
     rH.dt := rH.dt / 1000. ; { Convert to ms-->s }

     { Read channel A/D converter voltage range }
     err := FileRead(FHDR.FileHandle,rH.ADCVoltageRange,sizeof(rH.ADCVoltageRange)) ;
     for ch := 0 to FHDR.NumChannels-1 do begin
         if FHDR.Version < 6.0 then rH.ADCVoltageRange[ch] := rH.ADCVoltageRange[0] ;
         Channel[ch].ADCScale := Abs(rH.ADCVoltageRange[ch]) /
                          ( Channel[ch].ADCCalibrationFactor * (MaxADCValue+1) ) ;
         end ;

     { Read Analysis block }
     err := FileRead(FHDR.FileHandle,rH.Analysis.Available,sizeof(rH.Analysis.Available)) ;
     err := FileRead(FHDR.FileHandle,rH.Analysis.Value, sizeof(rH.Analysis.Value)) ;
     err := FileRead(FHDR.FileHandle,rH.Equation,sizeof(rH.Equation) ) ;
     { Read record ident text }
     err := FileRead( FHDR.FileHandle, cBuf, 16 );
     rH.Ident := '' ;
     for i := 1 to 16 do Rh.Ident := rH.Ident + cBuf[i] ;

     { Read record data block from file }
     err := FileSeek( FHDR.FileHandle, FilePointer +
            (FHDR.NumBytesPerRecord-FHDR.NumDataBytesPerRecord), 0 ) ;
     err := FileRead( FHDR.FileHandle, dBuf, FHDR.NumDataBytesPerRecord ) ;
     { Remove offset of 2048 (to be compatible with WCP) }
     for i := 0 to MinInt([High(dBuf),FHDR.NumChannels*FHDR.NumSamples-1]) do
         dBuf[i] := dBuf[i] - 2048 ;

     {Apply digital filter}
     if Settings.CutOffFrequency > 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 := 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 GetRecordHeaderOnly( var fHDR : TFileHeader;
                               var rH : TRecHeader ;
                               RecordNum : LongInt ) ;
{ --------------------------------------------------------
  Read a WCP format digital signal record header from file
  --------------------------------------------------------}
var
   err,FilePointer : LongInt ;
   cBuf : array[1..20] of char ;
   i,ch,i0,i1,Sum,MaxCh : LongInt ;
begin
     fHDR.RecordNum := RecordNum ;
     fHDR.NumDataBytesPerRecord := fHDR.NumSamples*fHDR.NumChannels*2 ;
     fHDR.NumBytesPerRecord := fHDR.NumDataBytesPerRecord +
                               fHDR.NumAnalysisBytesPerRecord ;
     FilePointer := (RecordNum-1)*fHDR.NumBytesPerRecord + fHDR.NumBytesInHeader ;
     { Read record header block from file }
     err := FileSeek( FHDR.FileHandle, FilePointer, 0 ) ;
     err := FileRead( FHDR.FileHandle, cBuf, 8 );
     rH.Status := '' ;
     for i := 1 to 8 do Rh.Status := rH.Status + cBuf[i] ;

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

     err := FileRead( FHDR.FileHandle, rH.Number, sizeof(rH.Number) ) ;
     err := FileRead( FHDR.FileHandle, rH.Time, sizeof(rH.Time) ) ;
     err := FileRead( FHDR.FileHandle, rH.dt, sizeof(rH.dt) ) ;
     rH.dt := rH.dt / 1000. ; { Convert to ms-->s }

     { Read channel A/D converter voltage range }
     err := FileRead(FHDR.FileHandle,rH.ADCVoltageRange,sizeof(rH.ADCVoltageRange) ) ;
     for Ch := 0 to FHDR.NumChannels-1 do begin
         if fHDR.Version < 6.0 then rH.ADCVoltageRange[ch] := rH.ADCVoltageRange[0] ;
         Channel[ch].ADCScale := rH.ADCVoltageRange[ch] /
                          ( Channel[ch].ADCCalibrationFactor * (MaxADCValue+1) ) ;
         end ;

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

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

     end ;

procedure OpenLogFile ;
var
   ok : boolean ;
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 ) ;
begin
     if LogFileAvailable then WriteLn( LogFile, TimeToStr(Time) + ' ' + Line ) ;
     end ;

procedure WriteToLogFileNoDate( Line : string ) ;
begin
     if LogFileAvailable then WriteLn( LogFile, Line ) ;
     end ;


procedure CloseLogFile ;
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 ;
                          Var Buf : Array of Integer ;
                          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 : LongInt ;
   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[2] ;
	    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 ;

initialization
    LogFileAvailable := False ;

end.
