unit Sealtest;
{ ==================================================
  WinEDR - Patch clamp pipette seal test module
  (c) J. Dempster, University of Strathclyde 1996-97
  3/6/98 ... V1.1 Modified from WinWCP V2.1
             (Includes fix for 0V glitches with digidata 1200)
  22/3/99 ... TScopeDisplay now used to display signal
  9/6/99 ...  Now uses WinRT device driver for Digidata 1200
  19/8/99 ... Incorporated into WinWCP V3.0
  1/9/99 ... ChangeDisplayGrid added
  4/9/99 ... Problems with -10080 error when A/D converter voltage range invalid
             now fixed
  14/9/00 ... Resistance now switches MOhm/Gohm
  23/11/00 ... Scaling error in Holding Voltage #3 fixed
  19/1/01 ... Error in calculation of IPulse corrected
  31/2/02 ... Cell access resistance now calculated
  19/02/01 ... Capacity measurement nearly fixed
  14/8/01 .... Elapsed timer added
               FP errors when pulse step is small now trapped
               Window is now resizable
               FP error when maximised now fixed
  20/12/01 ... D/A and digital default values now set using Main.WriteOutputPorts (V3.2.1)
  13/2/02 .... Intermittent floating point errors with Windows 2000 fixed
               Resistance and other readouts only updated if they have
               valid data now
  18/3/02 .... A/D buffer empty flags now alternate positive and negative
               to avoid potential hang-up when 16 A/D resolution value
               equals empty flag value.
  16.4.03 .... Sweep now terminates correctly when A/D input = emptyflag
  28.4.03 .... Seal test pulse should now start automatically
  24.6.03 ... No. of display grid lines can be changed
  25.8.03 ... Auto-Display magnification now different for each channel
  7.9.03 .... Lo limits now specific for current, voltage and membrane parameters
  10.9.03 ... edGaccess FP error fixed
  05.02.04 .. DAC 1 Trigger pulse can now be inverted
  10.03.04 .. Floating point errors now trapped in AnalyseTestPulse
  11.06.04 .. Cell parameters LP filter now faster (0.1/0.9)
  08.08.04 .. Current and voltage channels now set automatically
              when channel units switch.
  01.10.04 .. Pulse #1..#3 now set correctly when seal test window opened
              Cell parameters can now be written to log file
  16.12.04 .. Manual display scale settings now updated correctly when form opened
  15.04.05 .. Voltage pulses no longer wrap round at high/low voltages
  10.09.05 .. Manual gain entry now works for both Amplifier 1 and 2
  04.11.05 .. Gaccess now estimated from initial amplitude of exponential
              fit to decaying capacity current
  09.12.05 .. Holding potentials now set for Amplifier #2
              TestPulseAmplitude, HoldingVoltage can now be set via properties
  13.12.05 .. Readout smoothing factor can now be changed (.SmoothingFactor property added)
  14.12.05 .. Window can now made smaller
  16.06.06 .. Now updates display when channel name changed
  25.07.06 .. Recording channel settings now stored in RecChannel and Settings (global.pas)
  15.08.06 .. Displayed trace maintained on screen until next one is available
  05.09.06 .. Im/Vm readout selection list now updated on first sweep
  06.04.07 .. Amplifier gain factor now updated correct when Amplifier= None or Manual
              Channel[].ADCAmplifierGain setting no longer altered
  14.08.07 .. Display magnification now controlled with buttons when
              autoscale turned off.
  28.01.08 .. Test pulse can now be switched between DAC 0 and DAC 1
  02.06.08 .. Gain settings of both amplifiers now displayed
              Activation of seal test form now disables record form (and vice versa)
  10.07.08 .. Pulse #3 now includes a test pulse
              Seal test pulse selection updated. Selected pulse remembered correctly
              when returning to Seal Test window
              Display traces can be sized vertically and disabled
              Display ticks now chosen from 1,2, or 5 unit steps
              
  ==================================================}

interface

uses WinTypes, WinProcs, Classes, Graphics, Forms, Controls, Buttons,
  StdCtrls, ExtCtrls, global, shared, SysUtils, Spin,
  math, maths, dialogs,
  ValEdit, ScopeDisplay, SESLabIO, ComCtrls, ValidatedEdit, FileIO ;

type
  TState = ( Idle, StartSweep, SweepInProgress, EndofSweep ) ;

  TTestPulse = record
               Duration : single ;
               TStart : single ;
               TEnd : Single ;
               RecordLength : single ;
               RepeatPeriod : single ;
               end ;

  TSealTestFrm = class(TForm)
    ChannelsGrp: TGroupBox;
    cbCurrentChannel: TComboBox;
    Label1: TLabel;
    cbVoltageChannel: TComboBox;
    Label2: TLabel;
    VoltsGrp: TGroupBox;
    Label5: TLabel;
    Label6: TLabel;
    CurrentGrp: TGroupBox;
    Label7: TLabel;
    Label8: TLabel;
    CellGrp: TGroupBox;
    Timer: TTimer;
    AmplifierGrp: TGroupBox;
    DACGrp: TGroupBox;
    rbUseHoldingVoltage1: TRadioButton;
    rbUseHoldingVoltage2: TRadioButton;
    Label3: TLabel;
    Label11: TLabel;
    Label12: TLabel;
    Label14: TLabel;
    Label4: TLabel;
    rbUseHoldingVoltage3: TRadioButton;
    Label13: TLabel;
    Shape1: TShape;
    edHoldingVoltage1: TValidatedEdit;
    edPulseHeight1: TValidatedEdit;
    edHoldingVoltage2: TValidatedEdit;
    edPulseHeight2: TValidatedEdit;
    edHoldingVoltage3: TValidatedEdit;
    edPulseWidth: TValidatedEdit;
    edVHold: TValidatedEdit;
    edVPulse: TValidatedEdit;
    edIHold: TValidatedEdit;
    edIPulse: TValidatedEdit;
    scDisplay: TScopeDisplay;
    CellParametersPage: TPageControl;
    PipetteTab: TTabSheet;
    Label9: TLabel;
    edResistance: TValidatedEdit;
    CellTab: TTabSheet;
    Label15: TLabel;
    Label17: TLabel;
    Label16: TLabel;
    edGaccess: TValidatedEdit;
    edGmembrane: TValidatedEdit;
    edCmembrane: TValidatedEdit;
    edAmplifier1Gain: TValidatedEdit;
    lbAmplifier1: TLabel;
    TimerGrp: TGroupBox;
    edTimer: TEdit;
    bResetTimer: TButton;
    bSaveToLog: TButton;
    GroupBox2: TGroupBox;
    rbGaFromPeak: TRadioButton;
    rbGaFromExp: TRadioButton;
    ckAutoScale: TCheckBox;
    DivideGrp: TGroupBox;
    Label18: TLabel;
    Label19: TLabel;
    edVDivide0: TValidatedEdit;
    edVDivide1: TValidatedEdit;
    Shape2: TShape;
    cbDACOut: TComboBox;
    Label10: TLabel;
    lbAmplifier2: TLabel;
    edAmplifier2Gain: TValidatedEdit;
    rbAmplifier1: TRadioButton;
    rbAmplifier2: TRadioButton;
    edPulseheight3: TValidatedEdit;
    Label20: TLabel;
    procedure TimerTimer(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure edVDivide0KeyPress(Sender: TObject; var Key: Char);
    procedure edHoldingVoltage1KeyPress(Sender: TObject; var Key: Char);
    procedure FormDeactivate(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure edHoldingVoltage2KeyPress(Sender: TObject; var Key: Char);
    procedure rbUseHoldingVoltage1Click(Sender: TObject);
    procedure rbUseHoldingVoltage2Click(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure FormResize(Sender: TObject);
    procedure edPulseHeight1KeyPress(Sender: TObject; var Key: Char);
    procedure edPulseheight2KeyPress(Sender: TObject; var Key: Char);
    procedure edPulseWidthKeyPress(Sender: TObject; var Key: Char);
    procedure rbUseHoldingVoltage3Click(Sender: TObject);
    procedure EdHoldingVoltage3KeyPress(Sender: TObject; var Key: Char);
    procedure bResetTimerClick(Sender: TObject);
    procedure cbCurrentChannelChange(Sender: TObject);
    procedure bSaveToLogClick(Sender: TObject);
    procedure rbAmplifier1Click(Sender: TObject);
    procedure rbAmplifier2Click(Sender: TObject);
    procedure edAmplifier1GainKeyPress(Sender: TObject; var Key: Char);
    procedure rbGaFromPeakClick(Sender: TObject);
    procedure rbGaFromExpClick(Sender: TObject);
    procedure edAmplifier2GainKeyPress(Sender: TObject; var Key: Char);
    procedure edPulseheight3KeyPress(Sender: TObject; var Key: Char);
  private
    { Private declarations }
  ADC : PSmallIntArray ;
  DAC : PSmallIntArray ;
  EndOfBuf : Integer ;
  ChangeDisplayScaling : Boolean ;
  EndOfSweepCount : Integer ;
  FirstSweep : Boolean ;
  ResetReadout : Boolean ;
  NumTestChannels : Integer ;
  TestPulse : TTestPulse ;
  DACScale : Array[0..1] of single ;
  DACdt : Single ;
  TestDAC : Integer ;                      // DAC channel with seal test
  HoldDAC : Integer ;                      // Other DAC channel
  TimerBusy : boolean ;
  EmptyFlag : Integer ;
  nDACValues : Integer ;
  TimerStarted : Integer ;
  NewAmplifier1GainValue : Boolean ;
  NewAmplifier2GainValue : Boolean ;
  SetAmplifier1GainReadout : Boolean ;
  SetAmplifier2GainReadout : Boolean ;

  DisplayScale : Array[0..EDRChannelLimit] of Integer ;

    procedure CreateTestPulse ;
    procedure AnalyseTestPulse ;
    procedure UpdateChannelScalingFactors ;
    procedure UpdateChannelLists ;
    procedure SelectAmplifier( AmpNumber : Integer ) ;
    procedure SetTestPulseAmplitude( i : Integer ; Value : Single ) ;
    function  GetTestPulseAmplitude( i : Integer ) : Single ;
    procedure SetHoldingVoltage( i : Integer ; Value : Single ) ;
    function  GetHoldingVoltage( i : Integer ) : Single ;
    procedure SetTestPulseWidth( Value : Single ) ;
    function  GetTestPulseWidth : Single ;
    procedure SetTestPulseNumber( Value : Integer ) ;
    function  GetTestPulseNumber : Integer ;
    procedure SetSmoothingFactor( Value : Single ) ;
    function  GetSmoothingFactor : Single ;
    function GetRunning : Boolean ;

    procedure InitialiseDisplay ;

  public
    { Public declarations }
    State : TState ;
    procedure StopADCandDAC ;
    procedure ChangeDisplayGrid ;
    procedure ZoomOut ;
    Property TestPulseAmplitude[i : Integer] : Single read GetTestPulseAmplitude
                                                      write SetTestPulseAmplitude ;
    Property HoldingVoltage[i : Integer] : Single read GetHoldingVoltage
                                                  write SetHoldingVoltage ;
    Property TestPulseWidth : Single read GetTestPulseWidth
                                     write SetTestPulseWidth ;
    Property TestPulseNumber : Integer read GetTestPulseNumber
                                       write SetTestPulseNumber ;
    Property SmoothingFactor : Single read GetSmoothingFactor
                                      write SetSmoothingFactor ;
    Property Running : Boolean read GetRunning ;

  end ;
var
  SealTestFrm: TSealTestFrm;

implementation

{$R *.DFM}

uses Mdiform, AmpModule, mmsystem , Rec;

const
     StimDACChannel = 0 ;
     SyncDACChannel = 1 ;
     NumDACChannels = 2 ;
     NumTestSamples = 512 ;
     MinDACInterval = 0.0002 ;
     MinPulseWidth = 0.0001 ;
     MaxPulseWidth = 1.0 ;
     MinTestChannels = 2 ;


procedure TSealTestFrm.FormShow(Sender: TObject);
{ ----------------
  Initialise form
  ---------------}
var
   ch,i : Integer ;
   OK : Boolean ;
begin

     // Exit if no interface selected
     if (Main.SESLabIO.LabInterfaceType = NoInterface12) or
        (Main.SESLabIO.LabInterfaceType = NoInterface16) then begin
        ShowMessage( 'No laboratory interface selected!' ) ;
        Close ;
        Exit ;
        end ;

     Timer.Enabled := False ;
     ClientWidth := CellGrp.Left + CellGrp.Width + 5 ;

     { Get pointers to start of lab. interface A/D and D/A buffer }
     Main.SESLabIO.GetADCBuffer( ADC ) ;
     Main.SESLabIO.GetDACBuffer( DAC ) ;
     EmptyFlag := Main.SESLabIO.ADCEmptyFlag ;

     { Check that the selected hardware/software is available }
     if not Main.SESLabIO.LabInterfaceAvailable then begin
        ShowMessage( 'Laboratory interface not available!' ) ;
        Close ;
        Exit ;
        end ;

     { Stop laboratory interface activity }
     if Main.SESLabIO.ADCActive then Main.SESLabIO.ADCStop ;
     if Main.SESLabIO.DACActive then Main.SESLabIO.DACStop ;

     // Make the currently selected A/D voltage range compatible
     //  with the laboratory interface hardware.
     Main.SESLabIO.ADCVoltageRangeIndex := Min( Settings.ADCVoltageRangeIndex,
                                                Main.SESLabIO.ADCNumVoltageRanges-1) ;

     // Set min. no. of channels depending upon whether 1/2 amplifiers are configured
     if Amplifier.AmplifierType[2] <> amNone then begin
        lbAmplifier2.Visible := True ;
        edAmplifier2Gain.Visible := True ;
        NumTestChannels := Max( Settings.NumChannels,MinTestChannels*2 ) ;
        end
     else begin
        lbAmplifier2.Visible := False ;
        edAmplifier2Gain.Visible := False ;
        NumTestChannels := Max( Settings.NumChannels,MinTestChannels ) ;
        end ;

     scDisplay.ClearHorizontalCursors ;
     scDisplay.MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     scDisplay.MinADCValue := Main.SESLabIO.ADCMinValue ;
     for ch := 0 to NumTestChannels-1 do begin
         RecChannel[ch] := RecChannel[ch] ;
         RecChannel[ch].InUse := True ;
         RecChannel[ch].ADCZero := 0 ;
         { Create horizontal cursors }
         scDisplay.AddHorizontalCursor(ch,Settings.Colors.Cursors,True,'z') ;
         scDisplay.yMax[ch] := Main.SESLabIO.ADCMaxValue ;
         scDisplay.yMin[ch] := Main.SESLabIO.ADCMinValue ;
         end ;

     edAmplifier1Gain.Value := 0.0 ;
     edAmplifier2Gain.Value := 0.0 ;
     NewAmplifier1GainValue := False ;
     SetAmplifier1GainReadout := True ;
     NewAmplifier2GainValue := False ;
     SetAmplifier2GainReadout := True ;

     // Ensure smoothing factor is valid
     Settings.SealTest.SmoothingFactor := Min(Max(Settings.SealTest.SmoothingFactor,0.1),1.0) ;

     scDisplay.xMin := 0 ;
     scDisplay.xMax := NumTestSamples-1  ;

     scDisplay.TFormat := ' %.2f ms ' ;

     // D/A output selection list
     cbDACOut.Clear ;
     cbDACOut.Items.Add('DAC 0') ;
     cbDACOut.Items.Add('DAC 1') ;

     { Set current channel selection combo boxes }
     UpdateChannelLists ;

     // Select amplifier #1
     SelectAmplifier(1) ;

     { Test pulse amplitude & width }
     edPulseHeight1.Value := Settings.SealTest.PulseHeight1 ;
     edPulseHeight2.Value := Settings.SealTest.PulseHeight2 ;
     edPulseHeight3.Value := Settings.SealTest.PulseHeight3 ;
     edPulseWidth.Value := Settings.SealTest.PulseWidth ;

     { Update holding potential text boxes }
     edHoldingVoltage1.Value := Settings.SealTest.HoldingVoltage1 ;
     edHoldingVoltage2.Value := Settings.SealTest.HoldingVoltage2 ;
     edHoldingVoltage3.Value := Settings.SealTest.HoldingVoltage3 ;
     edVDivide0.Value := Settings.VCommand[0].DivideFactor ;
     edVDivide1.Value := Settings.VCommand[1].DivideFactor ;

     { Select test pulse to use }
     case Settings.SealTest.Use of
          2 : rbUseHoldingVoltage2.checked := True ;
          3 : rbUseHoldingVoltage3.checked := True ;
          else rbUseHoldingVoltage1.checked := True ;
          end ;

     // Auto/manual display scaling
     ckAutoScale.checked := True ;

     ChangeDisplayScaling := True ;

     // Set no. of horizontal/vertical lines in display grid
     ChangeDisplayGrid ;

     // Initialise elapsed time readout
     TimerStarted := TimeGetTime ;

     Timer.Enabled := True ;
     TimerBusy := False ;
     ResetReadout := True ;
     FirstSweep := True ;
     State := StartSweep ;

     // Clear display
     InitialiseDisplay ;
     for ch := 0 to scDisplay.NumChannels-1 do scDisplay.ChanVisible[ch] := True ;

     Resize ;

     end ;


procedure TSealTestFrm.UpdateChannelLists ;
// --------------------------------------
// Update current & voltage channel lists
// --------------------------------------
var
    ch : Integer ;
begin

     { Fill current & voltage channel selection combo boxes }
     cbCurrentChannel.Clear ;
     cbVoltageChannel.Clear ;
     for ch := 0 to NumTestChannels-1 do begin
         cbCurrentChannel.Items.Add(format('Ch.%d %s',[ch,RecChannel[ch].ADCName]));
         cbVoltageChannel.Items.Add(format('Ch.%d %s',[ch,RecChannel[ch].ADCName]));
         end ;
     cbCurrentChannel.ItemIndex := 0 ;
     cbVoltageChannel.ItemIndex := Max(1,NumTestChannels-1) ;

     // Set
     for ch := NumTestChannels-1 downto 0 do begin
         // Current channel (units = pA,nA,uA)
         if Pos('A',UpperCase(RecChannel[ch].ADCUnits)) > 0 then
            cbCurrentChannel.ItemIndex := ch ;
         // Voltage channel (units= mV)
         if Pos('V',UpperCase(RecChannel[ch].ADCUnits)) > 0 then
            cbVoltageChannel.ItemIndex := ch ;
         end ;

     end ;


procedure TSealTestFrm.TimerTimer(Sender: TObject);
{ ---------------------
  Timed Event scheduler
  ---------------------}

var
   iAmp,ch,T : Integer ;
   OldADCUnits : Array[0..EDRChannelLimit] of String ;
   OldADCName : Array[0..EDRChannelLimit] of String ;
   Changed : Boolean ;
   IChannel : Integer ;
   VChannel : Integer ;
begin
     if not TimerBusy then begin
          TimerBusy := True ;
          case State of

               StartSweep : Begin

                   { Start recording sweep(s) }

                   if cbCurrentChannel.ItemIndex >= 0 then
                      Settings.SealTest.CurrentChannel := cbCurrentChannel.ItemIndex ;
                   if cbVoltageChannel.ItemIndex >= 0 then
                      Settings.SealTest.VoltageChannel := cbVoltageChannel.ItemIndex ;

                   if edVDivide0.Value <= 0. then edVDivide0.Value := 1. ;
                   if edVDivide1.Value <= 0. then edVDivide1.Value := 1. ;

                   // Save old channel units
                   for ch := 0 to NumTestChannels-1 do begin
                       OldADCUnits[ch] := RecChannel[ch].ADCUnits ;
                       OldADCName[ch] := RecChannel[ch].ADCName ;
                       end ;

                   // Update channel scaling factors in case amplifier gain has changed
                   for ch := 0 to NumTestChannels-1 do begin
                       Amplifier.GetChannelSettings( ch,
                                                     RecChannel[ch].ADCName,
                                                     RecChannel[ch].ADCUnits,
                                                     RecChannel[ch].ADCCalibrationFactor,
                                                     RecChannel[ch].ADCAmplifierGain ) ;
                       end ;

                   UpdateChannelScalingFactors ;

                   // If units have changed, update current/voltage channels
                   Changed := False ;
                   for ch := 0 to NumTestChannels-1 do begin
                       if OldADCUnits[ch] <> RecChannel[ch].ADCUnits then Changed := True ;
                       if OldADCName[ch] <> RecChannel[ch].ADCName then Changed := True ;
                       end ;
                   if Changed or FirstSweep then UpdateChannelLists ;

                   // Update Amplifier #1 gain display

                   IChannel := Amplifier.CurrentChannel( 1 ) ;
                   VChannel := Amplifier.VoltageChannel( 1 ) ;
                   lbAmplifier1.Caption := 'Amp#1 Gain (Ch.0)' ;
                   if Amplifier.GainTelegraphAvailable[1] then begin
                      // Display current amplifier gain

                      edAmplifier1Gain.Units := 'V/' + RecChannel[IChannel].ADCUnits ;
                      edAmplifier1Gain.Value := Abs(Main.SESLabIO.ADCVoltageRange) /
                                                 (RecChannel[IChannel].ADCScale
                                                *(Main.SESLabIO.ADCMaxValue+1) ) ;
                      end
                   else begin
                      // Let user type in gain
                     if SetAmplifier1GainReadout then begin
                        edAmplifier1Gain.Units := 'V/' + RecChannel[IChannel].ADCUnits ;
                        edAmplifier1Gain.Value := RecChannel[IChannel].ADCCalibrationFactor ;
                        SetAmplifier1GainReadout := False ;
                        end ;
                     if NewAmplifier1GainValue then begin
                        // Gain factor cannot be manually changed if records have been collected
                        // or zero or negative value entered
                        if (CDRFH.NumSamplesInFile > 0) or (edAmplifier1Gain.Value <= 0.0) then begin
                           edAmplifier1Gain.Value := RecChannel[IChannel].ADCCalibrationFactor ;
                           end ;
                        RecChannel[IChannel].ADCCalibrationFactor := edAmplifier1Gain.Value ;
                        NewAmplifier1GainValue := False ;
                        end ;
                     end ;

                   // Update Amplifier #2 gain display

                   IChannel := Amplifier.CurrentChannel( 2 ) ;
                   VChannel := Amplifier.VoltageChannel( 2 ) ;
                   lbAmplifier2.Caption := 'Amp#2 Gain (Ch.2)' ;
                   if Amplifier.GainTelegraphAvailable[2] then begin
                      // Display current amplifier gain

                      edAmplifier2Gain.Units := 'V/' + RecChannel[IChannel].ADCUnits ;
                      edAmplifier2Gain.Value := Abs(Main.SESLabIO.ADCVoltageRange) /
                                                 (RecChannel[IChannel].ADCScale
                                                *(Main.SESLabIO.ADCMaxValue+1) ) ;
                      end
                   else begin
                      // Let user type in gain
                     if SetAmplifier2GainReadout then begin
                        edAmplifier2Gain.Units := 'V/' + RecChannel[IChannel].ADCUnits ;
                        edAmplifier2Gain.Value := RecChannel[IChannel].ADCCalibrationFactor ;
                        SetAmplifier2GainReadout := False ;
                        end ;
                     if NewAmplifier2GainValue then begin
                        // Gain factor cannot be manually changed if records have been collected
                        // or zero or negative value entered
                        if (CDRFH.NumSamplesInFile > 0) or (edAmplifier2Gain.Value <= 0.0) then begin
                           edAmplifier2Gain.Value := RecChannel[IChannel].ADCCalibrationFactor ;
                           end ;
                        RecChannel[IChannel].ADCCalibrationFactor := edAmplifier2Gain.Value ;
                        NewAmplifier2GainValue := False ;
                        end ;
                     end ;


                   { A/D sampling interval }
                   EndofBuf := (NumTestChannels*NumTestSamples) - 1;
                   // Stop any existing A/D sweep
                   if Main.SESLabIO.ADCActive then Main.SESLabIO.ADCStop ;
                   // Set no. of A/D channels and samples
                   Main.SESLabIO.ADCNumChannels := NumTestChannels ;
                   Main.SESLabIO.ADCNumSamples := NumTestSamples ;
                   // A/D input voltage range
                   Main.SESLabIO.ADCVoltageRangeIndex := Min( Settings.ADCVoltageRangeIndex,
                                                              Main.SESLabIO.ADCNumVoltageRanges-1) ;

                   // Set A/D sweep triggering
                   Main.SESLabIO.ADCTriggerMode := tmWaveGen ;

                   Main.SESLabIO.ADCCircularBuffer := False ;

                   CreateTestPulse ;

                   { Start D/A waveform output }
                   Main.SESLabIO.DACNumChannels := NumDACChannels ;
                   Main.SESLabIO.DACNumSamples := nDACValues ;
                   Main.SESLabIO.DACUpdateInterval := DACdt ;
                   EndOfSweepCount := 0 ;

                   Main.SESLabIO.ADCStart ;
                   Main.SESLabIO.DACStart ;

                   State := SweepInProgress ;
                   End ;

               SweepInProgress : Begin
                   { Erase display & Draw horizontal zero level cursor(s) }

                   { Update A/D buffer with samples from interface }
                   Main.SESLabIO.ADCBufferRefresh ;

                   // if all samples have been acquired, display on screen
                   if ADC[EndOfBuf] <> -EmptyFlag then Inc(EndOfSweepCount) ;
                   if ADC[EndOfBuf-1] <> EmptyFlag then Inc(EndOfSweepCount) ;
                   if EndOfSweepCount >= 2 then begin
                      // Clear display
                      InitialiseDisplay ;
                      // Update with new points
                      scDisplay.DisplayNewPoints( NumTestSamples ) ;
                      // Stop recording
                      if Main.SESLabIO.ADCActive then Main.SESLabIO.ADCStop ;
                      if Main.SESLabIO.DACActive then Main.SESLabIO.DACStop ;
                      State := EndOfSweep ;
                      end ;

                   { Procedure to be done when recording sweep completes }
                   if State = EndOfSweep then begin
                       { Analyse and display test pulse results }
                       Main.SESLabIO.ADCStop ;
                       Main.SESLabIO.DACStop ;

                       AnalyseTestPulse ;

                       FirstSweep := False ;

                       { Do another sweep }
                       State := StartSweep ;
                       end ;

                   end ;

               Idle : begin
                    { Procedures when recording is in idle mode }
                    end ;
               end ;

          //Update elapsed time readout
          T := (TimeGetTime - TimerStarted) div 1000 ;
          edTimer.Text := format( ' %.2d:%.2d',[T div 60, T mod 60]) ;

          TimerBusy := False ;
          end ;
     end ;


procedure TSealTestFrm.InitialiseDisplay ;
// ---------------------------------
// Setup oscilloscope display window
// ---------------------------------
var
    ch : Integer ;
begin
     scDisplay.MaxPoints := NumTestSamples ;
     scDisplay.NumPoints := 0 ;
     scDisplay.NumChannels := NumTestChannels ;
     scDisplay.xMax := NumTestSamples-1 ;
     scDisplay.xMin := 0 ;
     { Set channel information }
     for ch := 0 to NumTestChannels-1 do begin
         RecChannel[ch].ChannelOffset := Main.SESLabIO.ADCChannelOffset[ch] ;
         scDisplay.ChanOffsets[ch] := Main.SESLabIO.ADCChannelOffset[ch] ;
         scDisplay.ChanUnits[ch] := RecChannel[Ch].ADCUnits ;
         scDisplay.ChanScale[ch] := RecChannel[Ch].ADCScale ;
         scDisplay.ChanName[ch] := RecChannel[Ch].ADCName ;
         scDisplay.HorizontalCursors[ch] := RecChannel[ch].ADCZero ;

         end ;
     scDisplay.TScale := Main.SESLabIO.ADCSamplingInterval*1000.0 ;
     scDisplay.SetDataBuf( ADC ) ;
     end ;


procedure TSealTestFrm.CreateTestPulse ;
{ ----------------------------
  Create test pulse waveform
  ----------------------------}
var
   i,iStart,iEnd,iOffLevel,iOnLevel : Integer ;
   HoldDACLevel : Integer ;
begin


     // Select output channel
     TestDAC := cbDACOut.ItemIndex ;
     if TestDAC = 0 then HoldDAC := 1
                    else HoldDAC := 0 ;

     { Select test pulse to use }
     case Settings.SealTest.Use of
          2 : begin
              Settings.Vcommand[TestDAC].HoldingVoltage := Settings.SealTest.HoldingVoltage2 ;
              Settings.SealTest.PulseHeight := Settings.SealTest.PulseHeight2 ;
              end ;
          3 : begin
              Settings.Vcommand[TestDAC].HoldingVoltage := Settings.SealTest.HoldingVoltage3 ;
              Settings.SealTest.PulseHeight := Settings.SealTest.PulseHeight3 ;
              end ;
          else begin
              Settings.Vcommand[TestDAC].HoldingVoltage := Settings.SealTest.HoldingVoltage1 ;
              Settings.SealTest.PulseHeight := Settings.SealTest.PulseHeight1 ;
              end ;
          end ;

     DACGrp.Caption := Format( ' Test Pulse (DAC %d)',[TestDAC]) ;

     { D/A channel voltage -> bits scaling factors }
     DACScale[0] := (Main.SESLabIO.DACMaxValue*edVDivide0.Value)/
                     Main.SESLabIO.DACVoltageRange ;
     DACScale[1] := (Main.SESLabIO.DACMaxValue*edVDivide1.Value)/
                     Main.SESLabIO.DACVoltageRange ;

     { Test pulse duration and recording sweep length }
     TestPulse.Duration := Settings.SealTest.PulseWidth ;
     TestPulse.TStart := Max(TestPulse.Duration*0.15,0.002) ;
     TestPulse.TEnd := TestPulse.TStart + TestPulse.Duration ;
     TestPulse.RecordLength := TestPulse.TEnd + TestPulse.TStart*1.5 ;

     { Test pulse repeat period }
     TestPulse.RepeatPeriod := Max(TestPulse.RecordLength,0.1 ) ;

     { No. of A/D samples & sampling interval MUST be set up here [Digidata 132X] }
     Main.SESLabIO.ADCNumChannels := NumTestChannels ;
     Main.SESLabIO.ADCSamplingInterval := TestPulse.RecordLength / NumTestSamples ;

     { Set D/A update interval }
     //DACdt := MaxFlt( [TestPulse.RepeatPeriod/NumTestSamples,MinDACInterval] ) ;


     //DACdt := 1E-3 ;
//     Main.SESLabIO.DACUpdateInterval := DACdt ;
     Main.SESLabIO.DACNumChannels := 2 ;
     DACdt := Max( TestPulse.RepeatPeriod/NumTestSamples,MinDACInterval ) ;
     Main.SESLabIO.DACUpdateInterval := DACdt ;
     DACdt := Main.SESLabIO.DACUpdateInterval ;
     { Note that interval may be changed due to limitations of lab. interface.
       E.g. Digidata 132X update rate always the same as A/D sampling interval }

     { No. of D/A values in buffer }
     nDacValues := Round( TestPulse.RecordLength / DACdt ) ;

     { Create test pulse waveform }
     iStart := Max(Round(TestPulse.TStart/DACdt),1 ) ;
     iEnd := Max(Round(TestPulse.TEnd/DACdt),1 ) ;

     // Seal test On and off pulse levels for selected DAC output
     iOffLevel := Min(Max( Round(Settings.Vcommand[TestDAC].HoldingVoltage*DACScale[TestDAC] ),
                           Main.SESLabIO.DACMinValue),Main.SESLabIO.DACMaxValue) ;

     iOnLevel :=  Min(Max( Round( (Settings.SealTest.PulseHeight +
                                  Settings.Vcommand[TestDAC].HoldingVoltage)*DACScale[TestDAC] ),
                           Main.SESLabIO.DACMinValue),Main.SESLabIO.DACMaxValue) ;

     // DAC 1
     HoldDACLevel := Min(Max( Round(Settings.Vcommand[HoldDAC].HoldingVoltage*DACScale[HoldDAC] ),
                              Main.SESLabIO.DACMinValue),Main.SESLabIO.DACMaxValue) ;

     for i := 0 to nDacValues-1 do begin
         // Seal test DAC channel
         if (i >= iStart) and (i<iEnd) then DAC^[i*2+TestDAC] := iOnLevel
                                       else DAC^[i*2+TestDAC] := iOffLevel ;
         // Other channel held at holding potential
         DAC^[i*2+HoldDAC] := HoldDACLevel ;

         end ;

     end ;


procedure TSealTestFrm.AnalyseTestPulse ;
{ --------------------------------------------------------------------
  Calculate and display seal test pulse amplitudes and seal properties
  --------------------------------------------------------------------}
const
    LoVoltageLimit = 1E-6 ;  // Smallest valid test voltage (1uV)
    LoCurrentLimit = 1E-13 ; // Smallest valid current (0.1 pA)
    LoLimit = 1E-15 ;
var
   i,j,ch,iStart,iEnd,n : Integer ;
   iAvgStart,iAvgEnd,nAvg,EndofVHold,NumSamplesIn : Integer ;
   NewDisplayScale,NewDisplayRange,HalfRange,Mid : Integer ;
   ChIm,ChVm,ChOffset,yADC : Integer ;
   HoldLevel,VThreshold,iOffLevel,iOnLevel,PeakAt : Integer ;
   YMin,YMax : Array[0..EDRChannelLimit] of Integer ;
   VHold,VPulse,IHold,IPulse,Avg,Sum,IPeak,IValue : single ;
   Voltage,SteadyCurrent,PeakCurrent,VScale,IScale : single ;
   TauC,Slope,YIntercept,SteadyStateLevel,GMembrane,GAccess,Capacity,dt : single ;
   Resistance : Single ;
   x,y : Array[0..NumTestSamples] of Single ;
   OK,Done : Boolean ;
begin

     try

     // Current and voltage channels
     ChIm := cbCurrentChannel.ItemIndex ;
     ChVm := cbVoltageChannel.ItemIndex ;

     { Find Min./Max. limits of voltage and current }
     for ch := 0 to NumTestChannels-1 do begin
         YMax[ch] := Main.SESLabIO.ADCMinValue ;
         YMin[ch] := Main.SESLabIO.ADCMaxValue ;
         j := RecChannel[ch].ChannelOffset ;
         for i := 0 to NumTestSamples-1 do begin
             yADC := ADC^[j] ;
             if YMax[ch] <= yADC then YMax[ch] := yADC ;
             if YMin[ch] >= yADC then YMin[ch] := yADC ;
             j := j + NumTestChannels ;
             end ;
         end ;

     { Get current display magnification setting }
     for ch := 0 to NumTestChannels-1 do
         DisplayScale[ch] := Min(Max(DisplayScale[ch],1),100);

     { If in auto-scale mode ... determine whether a new magnification is needed }
     if ckAutoScale.Checked or FirstSweep then begin
        for ch := 0 to NumTestChannels-1 do begin
            NewDisplayRange := Max( YMax[Ch] - YMin[Ch] + 20,50) ;
            NewDisplayScale := (Main.SESLabIO.ADCMaxValue -Main.SESLabIO.ADCMinValue)
                               div NewDisplayRange ;
            NewDisplayScale := Max(NewDisplayScale,1) ;
            if (NewDisplayScale < DisplayScale[ch]) or
               (NewDisplayScale >= (8*DisplayScale[ch])) or
               FirstSweep then begin
               DisplayScale[ch] := NewDisplayScale ;
               end ;
            end ;
        ChangeDisplayScaling := True ;
        //FirstSweep := False ;
        end ;

     { Update display scaling if either the user has manually
       change the scale OR auto-scale is in use }
     if  ChangeDisplayScaling = True then begin
        for ch := 0 to NumTestChannels-1 do begin
            HalfRange := (Main.SESLabIO.ADCMaxValue - Main.SESLabIO.ADCMinValue) div
                          DisplayScale[ch] ;
            Mid := (YMax[Ch] + YMin[Ch]) div 2 ;
           scDisplay.yMax[ch] := Min( Mid+HalfRange, Main.SESLabIO.ADCMaxValue ) ;
            scDisplay.yMin[ch] := Max( Mid-HalfRange, Main.SESLabIO.ADCMinValue) ;
            end ;
         ChangeDisplayScaling := False ;
         end ;

     // Calculate current/voltage test pulse amplitudes
     // -----------------------------------------------

     // Holding voltage
     Sum := 0. ;
     EndofVHold := Max( Round(TestPulse.TStart/
                           (2.0*Main.SESLabIO.ADCSamplingInterval)),1 ) ;
     for i := 0 to EndofVHold do begin
         j := i*NumTestChannels + RecChannel[chVm].ChannelOffset ;
         Sum := Sum + ADC^[j] ;
         end ;
     Avg := Sum/(EndofVHold+1) ;
     HoldLevel := Round(Avg) ;
     VHold := (Avg - RecChannel[ChVm].ADCZero)*RecChannel[ChVm].ADCScale ;

     // Holding current
     Sum := 0. ;
     for i := 0 to EndofVHold do begin
         j := i*NumTestChannels + RecChannel[chIm].ChannelOffset ;
         Sum := Sum + ADC^[j] ;
         end ;
     Avg := Sum/(EndofVHold+1) ;
     IHold := (Avg - RecChannel[ChIm].ADCZero)*RecChannel[ChIm].ADCScale ;

     // Find start / end of test pulse
     VThreshold := Abs( YMax[ChVm] - YMin[ChVm] ) div 2 ;
     iStart := 0 ;
     iEnd := 0 ;
     for i := 0 to NumTestSamples-1 do begin
         j := i*NumTestChannels + RecChannel[chVm].ChannelOffset ;
         yADC := Abs(ADC^[j] - HoldLevel) ;
         if (yADC >= VThreshold) and (iStart = 0) then iStart := i ;
         if (iStart <> 0) and (iEnd=0) and (yADC < VThreshold) then iEnd := i ;
         end ;

     { Calculate amplitude of voltage pulse (from last half of pulse) }
     nAvg := Max( (iEnd - iStart + 1) div 2, 1 ) ;
     iAvgStart := Max( iEnd - nAvg + 1,iStart ) ;
     iAvgEnd := Max( iEnd - 2,iAvgStart ) ;
     Sum := 0. ;
     for i := iAvgStart to iAvgEnd do begin
         j := i*NumTestChannels + RecChannel[chVm].ChannelOffset ;
         Sum := Sum + ADC^[j] ;
         end ;
     if (iAvgEnd-iAvgStart+1) <> 0 then Avg := Sum / (iAvgEnd-iAvgStart+1)
                                   else Avg := 0.0 ;

     VPulse := (Avg - RecChannel[ChVm].ADCZero)*RecChannel[ChVm].ADCScale
                - VHold ;

     { Calculate steady-state amplitude of current pulse (from last half of pulse) }
     Sum := 0. ;
     for i := iAvgStart to iAvgEnd do begin
         j := i*NumTestChannels + RecChannel[chIm].ChannelOffset ;
         Sum := Sum + ADC^[j] ;
         end ;
     if (iAvgEnd-iAvgStart+1) <> 0 then Avg := Sum / (iAvgEnd-iAvgStart+1)
                                   else Avg := 0.0 ;

     SteadyStateLevel := Avg ;
     IPulse := (Avg - RecChannel[ChIm].ADCZero)*RecChannel[ChIm].ADCScale ;
     IPulse := IPulse - IHold ;

     edVHold.Units := RecChannel[ChVm].ADCUnits ;
     edVHold.Value := VHold ;


     edVPulse.Units := RecChannel[ChVm].ADCUnits ;
     EdVPulse.Value := VPulse ;
     edIHold.Units := RecChannel[ChIm].ADCUnits ;
     edIHold.Value := IHold ;


     edIPulse.Units := RecChannel[ChIm].ADCUnits ;
     EdIPulse.Value := IPulse ;

     // Calculate pipette/cell parameters
     // ---------------------------------

     { Set scaling factors to convert current and voltage from user's
       units to Volts and Amps }
     if Contains('MV',UpperCase(RecChannel[ChVm].ADCUnits)) then VScale := 1E-3
                                                             else VScale := 1. ;
     if Contains('NA',UpperCase(RecChannel[ChIm].ADCUnits)) then IScale := 1E-9
     else if Contains('PA',UpperCase(RecChannel[ChIm].ADCUnits)) then IScale := 1E-12
     else if Contains('UA',UpperCase(RecChannel[ChIm].ADCUnits)) then IScale := 1E-6
     else if Contains('MA',UpperCase(RecChannel[ChIm].ADCUnits)) then IScale := 1E-3
     else IScale := 1. ;

     // Holding voltage and current
     //Main.Vm := VScale*VHold ;
     //Main.Im := IScale*IHold ;

     // Voltage and current pulse amplitude in Volts and Amps
     Voltage := VPulse*VScale ;
     SteadyCurrent := IPulse*IScale ;

     // Abort calculations if voltages are too small
     if Abs(Voltage) < LoVoltageLimit then Exit ;
     if Abs(SteadyCurrent) < LoCurrentLimit then Exit ;

     // Calculate seal resistance
     Resistance := Voltage/SteadyCurrent ;
     // Note the use of low pass filter to smooth reading
     if ResetReadout then EdResistance.Value := Resistance ;

     EdResistance.Value := Settings.SealTest.SmoothingFactor*Resistance +
                           (1.0 - Settings.SealTest.SmoothingFactor)*EdResistance.Value ;
     Main.RSeal := EdResistance.Value ;

     { Calculate peak current from first half of pulse }
     PeakCurrent := 0. ;
     PeakAt := 0 ;
     for i := 0 to iAvgStart do begin
         j := i*NumTestChannels + RecChannel[chIm].ChannelOffset ;
         IValue := ((ADC^[j] - RecChannel[ChIm].ADCZero)*RecChannel[ChIm].ADCScale
                    - IHold)*IScale ;
         if Abs(IValue) > Abs(PeakCurrent) then begin
            PeakCurrent := IValue ;
            PeakAt := i ;
            end ;
         end ;
     if Abs(PeakCurrent) < LoCurrentLimit then Exit ;

     // Calculate decay time constant of capacity current
     dt := Main.SESLabIO.ADCSamplingInterval ;
     Done := False ;
     i := PeakAt ;
     n := 0 ;
     while not Done do begin
         j := i*NumTestChannels + RecChannel[chIm].ChannelOffset ;
         IValue := ((ADC^[j] - RecChannel[ChIm].ADCZero)*RecChannel[ChIm].ADCScale
                    - IHold)*IScale - SteadyCurrent ;
         // Invert value if negative peak
         if PeakCurrent < 0.0 then IValue := -IValue ;
         if (IValue > LoCurrentLimit) and (i < iAvgStart) then begin
            x[n] := (i-((iStart + PeakAt) div 2))*dt ;
            y[n] := Ln(IValue) ;
            Inc(n) ;
            Inc(i) ;
            end
         else Done := True ;
         end ;
     OK := LinearRegression( x, y, n, Slope, YIntercept ) ;
     if (not OK) or (Abs(Slope) <= LoLimit) or (Abs(Slope) > 1E30) then Exit ;

     // Calculate Access conductance (Ga)
     // See Gillis K.D. p. 161 Single-channel recording (Neher & Sakmann)
     if rbGaFromPeak.Checked then begin
        if Abs(PeakCurrent) < LoCurrentLimit then Exit ;
        GAccess := PeakCurrent/Voltage ;
        end
     else begin
        if Abs(YIntercept) > 30 then Exit ;
        GAccess := exp(YIntercept)*sign(PeakCurrent)/Voltage ;
        end ;

     if GAccess > 1E-14 then begin
        if ResetReadout then edGAccess.Value := GAccess ;
        edGAccess.Value := Settings.SealTest.SmoothingFactor*GAccess +
                           (1.0 - Settings.SealTest.SmoothingFactor)*edGAccess.Value
        end
     else edGAccess.Value := 0.0 ;
     if edGAccess.Value < 1E-14 then Exit ;
     Main.Ga := edGAccess.Value ;

     // Calculate membrane conductance (Gm)
     if Abs(Voltage - (SteadyCurrent/edGAccess.Value)) > LoVoltageLimit then begin
        GMembrane := SteadyCurrent/(Voltage - (SteadyCurrent/edGAccess.Value)) ;
        if ResetReadout then edGmembrane.Value := GMembrane ;
        edGmembrane.Value := Settings.SealTest.SmoothingFactor*GMembrane +
                             (1.0 - Settings.SealTest.SmoothingFactor)*edGmembrane.Value ;
        end
     else edGmembrane.Value := 0.0 ;
     if edGmembrane.Value < 1E-14 then Exit ;
     Main.Gm := edGmembrane.Value ;

     // Calculate membrane capacity
     if OK and (Abs(Slope) > LoLimit) and (Abs(Slope) < 1E30)then begin
        TauC := -1.0 / Slope ;
        Capacity := TauC*(edGAccess.Value + edGmembrane.Value) ;
        if ResetReadout then EdCmembrane.Value := Capacity ;
        EdCmembrane.Value := Settings.SealTest.SmoothingFactor*Capacity +
                             (1.0 - Settings.SealTest.SmoothingFactor)*EdCmembrane.Value ;
        end
     else EdCmembrane.Value := 0.0 ;
     Main.Cm := EdCmembrane.Value ;

     except
        Main.StatusBar.SimpleText := 'SealTest: Floating Point Error' ;
        end ;

     ResetReadout := False ;

     end ;


procedure TSealTestFrm.StopADCandDAC ;
{ ---------------------------------
  Shut down A/D and D/A sub-systems
  ---------------------------------}
var
    VDACS : Array[0..15] of Single ;
    iDAC : Integer ;
begin

    if Main.SESLabIO.ADCActive then Main.SESLabIO.ADCStop ;

    if Main.SESLabIO.DACActive then Main.SESLabIO.DACStop ;

    { Return voltage command to holding voltage and Sync. O/P to OFF }
    // Set outputs back to holding potential
    for iDAC := 0 to NumDACChannels-1 do begin
        VDACS[iDAC] := Settings.VCommand[iDAC].HoldingVoltage*Settings.VCommand[iDAC].DivideFactor ;
        end ;
    //Main.SESLabIO.WriteDACS( VDACS, NumDACChannels ) ;

    Timer.Enabled := False ;

    end ;


procedure TSealTestFrm.edVDivide0KeyPress(Sender: TObject; var Key: Char);
// -------------------------------------------
// Command voltage divide factor value changed
// -------------------------------------------
begin
     if key = #13 then begin
        Settings.Vcommand[0].DivideFactor := Max(edVDivide0.Value,1.0) ;
        Settings.Vcommand[1].DivideFactor := Max(edVDivide1.Value,1.0) ;
        end ;
     end;


procedure TSealTestFrm.edHoldingVoltage1KeyPress(Sender: TObject; var Key: Char);
{ ------------------------------------
  Set Voltage clamp holding voltage #1
  ------------------------------------}
begin
     if key = char(13) then begin
         Settings.SealTest.HoldingVoltage1 := edHoldingVoltage1.Value ;
        ResetReadout := True ;
        end ;

     end;


procedure TSealTestFrm.edHoldingVoltage2KeyPress(Sender: TObject; var Key: Char);
{ ------------------------------------
  Set Voltage clamp holding voltage #2
  ------------------------------------}
begin
     if key = char(13) then begin
        Settings.SealTest.HoldingVoltage2 := edHoldingVoltage2.Value ;
        ResetReadout := True ;
        end ;

     end;


procedure TSealTestFrm.EdHoldingVoltage3KeyPress(Sender: TObject;
  var Key: Char);
{ ------------------------------------
  Set Voltage clamp holding voltage #3
  ------------------------------------}
begin
     if key = char(13) then begin
        Settings.SealTest.HoldingVoltage3 := edHoldingVoltage3.Value ;
        ResetReadout := True ;
        end ;
     end ;


procedure TSealTestFrm.rbUseHoldingVoltage1Click(Sender: TObject);
{ ---------------------------------
  Set holding voltage to voltage #1
  ---------------------------------}
begin
     { Set holding voltage and pulse height to group #1 }
     Settings.SealTest.Use := 1 ;
     Settings.SealTest.HoldingVoltage1 := edHoldingVoltage1.Value ;
     Settings.SealTest.PulseHeight1 :=edPulseHeight1.Value ;
     end;


procedure TSealTestFrm.rbUseHoldingVoltage2Click(Sender: TObject);
{ ---------------------------------
  Set holding voltage to voltage #2
  ---------------------------------}
begin
     { Set holding voltage and pulse height to group #2 }
     Settings.SealTest.Use := 2 ;
     Settings.SealTest.HoldingVoltage2 := edHoldingVoltage2.Value ;
     Settings.SealTest.PulseHeight2 := edPulseHeight2.Value ;
     end;


procedure TSealTestFrm.rbUseHoldingVoltage3Click(Sender: TObject);
{ ---------------------------------
  Set holding voltage to voltage #3
  Note. No pulse with this option
  ---------------------------------}
begin
     { Set holding voltage and pulse height to group #3 }
     Settings.SealTest.Use := 3 ;
     Settings.SealTest.HoldingVoltage3 := edHoldingVoltage3.Value ;
     Settings.SealTest.PulseHeight3 :=edPulseHeight3.Value ;
     end;


procedure TSealTestFrm.edPulseHeight1KeyPress(Sender: TObject;
  var Key: Char);
{ -------------------------------
  Set test pulse amplitude #1
  -------------------------------}
begin
     if key = char(13) then begin
        Settings.SealTest.PulseHeight1 := edPulseHeight1.Value ;
        ResetReadout := True ;
        end ;
     end;


procedure TSealTestFrm.edPulseheight2KeyPress(Sender: TObject;
  var Key: Char);
  { -----------------------------
    Set test pulse amplitude #2
    -----------------------------}
begin
     if key = char(13) then begin
        Settings.SealTest.PulseHeight2 := edPulseHeight2.Value ;
        ResetReadout := True ;
        end ;
     end;


procedure TSealTestFrm.edPulseWidthKeyPress(Sender: TObject; var Key: Char);
{ ----------------------------
  Set width of seal test pulse
  ----------------------------}
begin
     if key = chr(13) then begin
        Settings.SealTest.PulseWidth := edPulseWidth.Value ;
        { Note. Force a re-start of D/A waveform so that D/A cycle
          time is updated }
        if Main.SESLabIO.DACActive then Main.SESLabIO.DACStop ;
        State := StartSweep ;
        ResetReadout := True ;
        end ;
     end ;


procedure TSealTestFrm.FormDeactivate(Sender: TObject);
{ -----------------------------------------
  Called when focus moves to another window
  -----------------------------------------}
begin
     { Terminate seal test pulses if form loses focus }
 //    Timer.enabled := false ;
 //    StopADCandDAC ;
 //    State := Idle ;
     end;


procedure TSealTestFrm.FormActivate(Sender: TObject);
{ -------------------------------------------
  Called when focus moves to seal test window
  -------------------------------------------}
begin

     // Stop record form if it is open
     if Main.FormExists( 'RecordFrm') then begin
        // Exit if recording is in progress
        if RecordFrm.Recording then begin
           Close ;
           Exit ;
           end ;
        RecordFrm.StopADCandDAC ;
        end ;

     { Start seal test pulses when form gains focus }
     Timer.enabled := True ;
     TimerBusy := False ;
     SetAmplifier1GainReadout := True ;
     SetAmplifier2GainReadout := True ;
     State := StartSweep ;
     end;


procedure TSealTestFrm.FormClose(Sender: TObject;
  var Action: TCloseAction);
{ -----------------------------
  Called when window is closed
  -----------------------------}
begin

     { Shut down A/D and D/A sub-systems }
     StopADCandDAC ;
     { Note. If A/D and D/A sub-systems are still active when the
       form is closed the program will crash. }

     Timer.enabled := false ;
     { Close the form }
     Action := caFree ;

     end;


procedure TSealTestFrm.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
{ --------------
  Function keys
  -------------}
begin
     case Key of
          { F3 selects holding voltage #1 }
          VK_F3 : begin
               rbUseHoldingVoltage1.checked := True ;
               end ;
          { F4 selects holding voltage #2 }
          VK_F4 : begin
               rbUseHoldingVoltage2.checked := True ;
               end ;
          { F5 selects holding voltage #3 }
          VK_F5 : begin
               rbUseHoldingVoltage3.checked := True ;
               end ;
          end ;
     end;


procedure TSealTestFrm.FormResize(Sender: TObject);
{ ----------------------------------
  Called when window size is changed
  ----------------------------------}
begin

     // Stop sweep
     StopADCandDAC ;

     TimerGrp.Top := ClientHeight - TimerGrp.Height - 5 ;
     AmplifierGrp.Top :=  Timergrp.Top - AmplifierGrp.Height - 5 ;
     DACGrp.Height := AmplifierGrp.Top - DACGrp.Top - 5 ;

     VoltsGrp.Top := ClientHeight - VoltsGrp.Height - 5 ;
     CurrentGrp.Top := VoltsGrp.Top ;
     CellGrp.Top := VoltsGrp.Top ;

     ckAutoScale.Top := VoltsGrp.Top - ckAutoScale.Height - 5 ;
     scDisplay.Width := Max(ClientWidth - scDisplay.Left - 5,2) ;
     scDisplay.Height := Max( ckAutoScale.Top - scDisplay.Top - 2,2) ;

     State := StartSweep ;
     Timer.Enabled := True ;
     
     end;


procedure TSealTestFrm.ChangeDisplayGrid ;
{ --------------------------------------------
  Update grid pattern on oscilloscope display
  -------------------------------------------- }
begin
     scDisplay.MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     scDisplay.MinADCValue := Main.SESLabIO.ADCMinValue ;
     scDisplay.NumHorizontalGridLines := Settings.NumHorizontalGridLines ;
     scDisplay.NumVerticalGridLines := Settings.NumVerticalGridLines ;

     scDisplay.DisplayGrid := Settings.DisplayGrid ;
     scDisplay.Invalidate ;
     end ;


procedure  TSealTestFrm.ZoomOut ;
{ ---------------------------------
  Set minimum display magnification
  --------------------------------- }
begin
     scDisplay.MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     scDisplay.MinADCValue := Main.SESLabIO.ADCMinValue ;
     scDisplay.ZoomOut ;
     end ;


procedure TSealTestFrm.bResetTimerClick(Sender: TObject);
begin
    // Initialise elapsed time readout
    TimerStarted := TimeGetTime ;
    end;


procedure TSealTestFrm.UpdateChannelScalingFactors ;
// ------------------------------
// Update channel scaling factors
// ------------------------------
var
   ch : Integer ;
begin
     for ch := 0 to NumTestChannels-1 do begin

         // Ensure that calibration factor is non-zero
         if RecChannel[ch].ADCCalibrationFactor = 0.0 then
            RecChannel[ch].ADCCalibrationFactor := 0.001 ;

         // Ensure that amplifier gain is non-zero
         if RecChannel[ch].ADCAmplifierGain = 0.0 then
            RecChannel[ch].ADCAmplifierGain := 1.0 ;

         // Calculate bits->units scaling factor
         RecChannel[ch].ADCScale := Main.SESLabIO.ADCVoltageRange /
                                     (RecChannel[ch].ADCCalibrationFactor*
                                     RecChannel[ch].ADCAmplifierGain
                                     *(Main.SESLabIO.ADCMaxValue+1) ) ;
         end ;
     end ;


procedure TSealTestFrm.cbCurrentChannelChange(Sender: TObject);
begin
     ResetReadout := True ;
     end;

procedure TSealTestFrm.bSaveToLogClick(Sender: TObject);
// --------------------------------
// Save cell parameters to log file
// --------------------------------
begin

     if CellParametersPage.ActivePage = CellTab then begin
        WriteToLogFile( format( 'Gaccess= %.5g %s',
                     [edGAccess.Value*edGAccess.Scale,edGAccess.Units])) ;
        WriteToLogFile( format( 'Gmembrane= %.5g %s',
                     [edGMembrane.Value*edGMembrane.Scale,edGMembrane.Units])) ;
        WriteToLogFile( format( 'Cmembrane= %.5g %s',
                     [edCMembrane.Value*edCMembrane.Scale,edCMembrane.Units])) ;
        end
     else begin
        WriteToLogFile( format( 'Pipette Resistance= %.5g %s',
                     [edResistance.Value*edResistance.Scale,edResistance.Units])) ;
        end ;
     end;

procedure TSealTestFrm.rbAmplifier1Click(Sender: TObject);
// -------------------------------
// Select patch clamp amplifier #1
// -------------------------------
begin
     SelectAmplifier(1) ;
     end;

procedure TSealTestFrm.rbAmplifier2Click(Sender: TObject);
// -------------------------------
// Select patch clamp amplifier #2
// -------------------------------
begin
     SelectAmplifier(2) ;
     end;


procedure TSealTestFrm.SelectAmplifier(
          AmpNumber : Integer
          ) ;
// ------------------------------
// Select amplifier for seal test
// ------------------------------
begin

     cbCurrentChannel.ItemIndex := 2*(AmpNumber-1)  ;
     cbVoltageChannel.ItemIndex := 2*(AmpNumber-1) + 1 ;

     cbDACOut.ItemIndex := AmpNumber - 1 ;

     TestDAC := cbDACOut.ItemIndex ;
     if TestDAC = 1 then HoldDAC := 0
                    else HoldDAC := 1 ;

     end ;



procedure TSealTestFrm.edAmplifier1GainKeyPress(Sender: TObject;
  var Key: Char);
begin
     if Key = #13 then NewAmplifier1GainValue := True ;
     end;

procedure TSealTestFrm.rbGaFromPeakClick(Sender: TObject);
begin
     ResetReadout := True ;
     end;

procedure TSealTestFrm.rbGaFromExpClick(Sender: TObject);
begin
     ResetReadout := True ;
     end;


procedure TSealTestFrm.SetTestPulseAmplitude(
          i : Integer ;
          Value : Single
          ) ;
// -----------------------------
// Set seal test pulse amplitude
// -----------------------------
begin
    case i of
        1 : begin
            edPulseHeight1.Value := Value ;
            Settings.SealTest.PulseHeight1 := Value ;
           if rbUseHoldingVoltage1.checked then begin
              Settings.SealTest.PulseHeight := edPulseHeight1.Value ;
              ResetReadout := True ;
              end ;
           end ;

        2 : begin
            edPulseHeight2.Value := Value ;
            Settings.SealTest.PulseHeight2 := Value ;
           if rbUseHoldingVoltage2.checked then begin
              Settings.SealTest.PulseHeight := edPulseHeight2.Value ;
              ResetReadout := True ;
              end ;
            end ;
        end ;
    end ;


function  TSealTestFrm.GetTestPulseAmplitude( i : Integer ) : Single ;
// -----------------------------
// Set seal test pulse amplitude
// -----------------------------
begin
    Result := 0.0 ;
    case i of
        1 : Result := edPulseHeight1.Value ;
        2 : Result := edPulseHeight2.Value ;
        end ;
    end ;


procedure TSealTestFrm.SetHoldingVoltage( i : Integer ; Value : Single ) ;
// -----------------------------
// Set holding potential
// -----------------------------
begin
    case i of
        1 : begin
            edHoldingVoltage1.Value := Value ;
            Settings.SealTest.HoldingVoltage1 := Value ;
            if rbUseHoldingVoltage1.checked then begin
               Settings.VCommand[TestDAC].HoldingVoltage := edHoldingVoltage1.Value ;
               ResetReadout := True ;
               end ;
            end ;

        2 : begin
            edHoldingVoltage2.Value := Value ;
            Settings.SealTest.HoldingVoltage2 := Value ;
            if rbUseHoldingVoltage1.checked then begin
               Settings.VCommand[TestDAC].HoldingVoltage := edHoldingVoltage2.Value ;
               ResetReadout := True ;
               end ;
            end ;
        3 : begin
            edHoldingVoltage3.Value := Value ;
            Settings.SealTest.HoldingVoltage3 := Value ;
            if rbUseHoldingVoltage1.checked then begin
               Settings.VCommand[TestDAC].HoldingVoltage := edHoldingVoltage3.Value ;
               ResetReadout := True ;
               end ;
            end ;
        end ;
    end ;


function  TSealTestFrm.GetHoldingVoltage( i : Integer ) : Single ;
// -----------------------------
// Set seal test pulse amplitude
// -----------------------------
begin
    Result := 0.0 ;
    case i of
        1 : Result := edHoldingVoltage1.Value ;
        2 : Result := edHoldingVoltage2.Value ;
        3 : Result := edHoldingVoltage3.Value ;
        end ;
    end ;


procedure TSealTestFrm.SetTestPulseWidth( Value : Single ) ;
// --------------------
// Set test pulse width
// --------------------
begin
     edPulseWidth.Value := Value ;
     Settings.SealTest.PulseWidth :=  Value ;

     end ;


function  TSealTestFrm.GetTestPulseWidth : Single ;
// --------------------
// Get test pulse width
// --------------------
begin
     Result := edPulseWidth.Value ;
     end ;


procedure TSealTestFrm.SetTestPulseNumber( Value : Integer ) ;
// ----------------------------
// Set test pulse number (1-3)
// ---------------------------
begin
     case Value of
        1 : rbUseHoldingVoltage1.Checked := True ;
        2 : rbUseHoldingVoltage2.Checked := True ;
        3 : rbUseHoldingVoltage3.Checked := True ;
        end ;
     end ;


function  TSealTestFrm.GetTestPulseNumber : Integer ;
// --------------------
// Get test pulse number
// --------------------
begin
     Result := Settings.SealTest.Use ;
     end ;


procedure TSealTestFrm.SetSmoothingFactor( Value : Single ) ;
// -----------------------------------
// Set cell parameter smoothing factor
// -----------------------------------
begin
     Settings.SealTest.SmoothingFactor := Min(Max(Value,0.1),1.0) ;
     end ;


function  TSealTestFrm.GetSmoothingFactor : Single ;
// -----------------------------------
// Get cell parameter smoothing factor
// -----------------------------------
begin
     Result := Settings.SealTest.SmoothingFactor ;
     end ;


function  TSealTestFrm.GetRunning : Boolean ;
// -----------------------------------
// Return TRUE if seal test is running
// -----------------------------------
begin
     Result := Timer.Enabled ;
     end ;

procedure TSealTestFrm.edAmplifier2GainKeyPress(Sender: TObject;
  var Key: Char);
begin
     if Key = #13 then NewAmplifier2GainValue := True ;
     end;

procedure TSealTestFrm.edPulseheight3KeyPress(Sender: TObject;
  var Key: Char);
  { -----------------------------
    Set test pulse amplitude #3
    -----------------------------}
begin
     if key = char(13) then begin
        Settings.SealTest.PulseHeight3 := edPulseHeight3.Value ;
        if rbUseHoldingVoltage3.checked then begin
           Settings.SealTest.PulseHeight := edPulseHeight3.Value ;
           end ;
        ResetReadout := True ;
        end ;
     end;

end.
