unit Rec;
{ ==============================================================================
  WinEDR - Analogue Recording module (c) J. Dempster 1998, All Rights Reserved.
    9/6/99 ...  Now uses WinRT device driver for Digidata 1200
  26/8/99 ... Real-time fluorescence ratio computation added
  1/9/99 ... File storage now preallocated by Record button pressed
             ChangeDisplayGrid added
  4/9/99 ... Problems with -10080 error when A/D converter voltage range invalid
             now fixed
  26/6/01 ... V2.1.3 Live display duration controlled by slider bar
              Signal readout now at left edge of display
              Stop watch removed
  11/12/01 ... Stimulator now turned off when record form closed or de-activated
  9/1/02 ... V2.2.6 Stimulator now uses SESLABIO.DACMaxValue (fixes PCI-6035E problems)
  20/3/02 ... Stimulator function disabled with Instrutech and Digidata 132X interfaces
  6.6.02 .... Holding voltage increment now works
  12.9.02 ... Calibration factor variable for calculated channels now saved
              (Fixes problem where capacity scaling factors were stored incorrectly)
              Now displays signal level readout while recording
  3.12.02 ... Stimulus timing now done within SESLABIO component
  16.12.02 ... Display now works correctly at slow sampling rates (<50ms)
  19.6.03 .... Recording now prevented if amplifier gain changed when file
               contains sample data
  24.6.03 .... No. horizontal/vertical grid lines changeable
  26.6.03 .... Display now uses min/max signal compression
               Flashing Recording indicator added
               Markers can now be added to chart
  28.6.03 .... Write buffer sizing function changed (to work with ITC-16)
  24.3.04 .... SESLabIO.ADCMaxValue now used consistently throughout
               Min/MaxADCValue updated when recording started
  06.08.04 ... Ext Trigger can now be Active High or Low (interface dependent)
  09.08.04 ... Amplifier channels automatically switched when changed
  13.09.04 ... Update Gain button added allowing user to force amp. gain update.
  14.09.04 ... Capacitance calculation now works correctly (no memory overflow)
  07.03.05 ... Stimulus pulse output now works again (repeated DAC output)
  16.05.05 ... UpdateOutputPorts now before start of A/D conversion (in ADCStart)
               to get A/D sampling to work with Digidata 1200
  23.09.05 ... Display Duration control buttons &, ms,s,mins check boxes added
               X2 Zoom In and Out added
  09.06.06 ... Bug which caused loss of samples during record when display resized fixed
  11.06.06 ... Capacity calculation working again (Plymouth 2006)
  25.06.06 ... ADCVoltageRangeIndex initialised in formshow
               FP Error when no Ch.1 defined fixed
  26.09.06 ... Form now exits if no laboratory interface defined
  21.05.07 ... Multi-protocol stimuli now supported
  06.06.07 ... Stimulus now started within timer proc after A/D is re-started
  29.08.07 ... Data file clusters no longer pre-allocated before recording
  25.07.08 ... RecChannel[ch].ADCMaxValue now updated correctly and recorded
               in EDR file header when file saved
  24.02.09 ... Capacity calculation updated.
               Vm and Im now smoothed.
               Greal & Gimag can now be inverted
               Greal & Gimag scaled now takes into account current gain setting
  27.05.09 ... Greal & Gimag scaling can now be either fixed or multiplied by
               gain telegraph.
  14.09.09 ... Stored GReal and GImag data now inverted when inversion options selected
  ==============================================================================}
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, ExtCtrls, Global, Shared, FileIO,
  Spin, ChartDisplay, ValEdit, ScopeDisplay, SESLabIO, mmsystem, ced1902u,
  Grids, ComCtrls, ValidatedEdit, CursorLabel, HTMLLabel, Math, strutils ;

const
     MinScansPerWriteBuf = 16 ;
     NumWriteBufsMax = 32768 div 16 ;

type

     // Circular write buffer control record
     TWriteBufs = record
        NumScans : Integer ;          // Total number of scans all buffers
        NumSamples : Integer ;        // Total number of samples
        NumBufs : Integer ;           // No. of sub-buffers in circular buffer
        NumScansPerBuf : Integer ;    // No. of scans in sub-buffer
        NumSamplesPerBuf : Integer ;  // No. of samples on sub-buffer
        NumBytesPerBuf : Integer ;    // No. of bytes in sub-buffer
        ActiveBuf : Integer ;         // Index no. of sub-buffer currently filling
        WriteBuf : Integer ;          // Index no. of next sub-buffer to be written
        BufStart : Array[0..NumWriteBufsMax-1] of Integer ; // Sub-buffer starting indexes (in ADC^)
        BufEnd : Array[0..NumWriteBufsMax-1] of Integer ;   // Sub-buffer end indexes (in ADC^)
        NumScansPerBlock : Integer ;         // No. of scans in display compression block
        NumPointsPerBlock : Integer ;        // No. display points per block
        BlockCount : Integer ;               // Compression block scan counter
        DispPointer : Integer ;              // Display buffer index
        NumBlocksDisplayed : Integer ;       // No. of blocks displayed
        MaxBlocksDisplayed : Integer ;       // Max. no. of blocks in display
        OldestScan : Integer ;               //
        LatestScan : Integer ;               // Index of latest available scan (ADC^)
        EmptyPointer : Integer ;             // Index of empty region (ADC^)
        yMin : Array[0..EDRChannelLimit] of Integer ;
        yMax : Array[0..EDRChannelLimit] of Integer ;
        yMinAt : Array[0..EDRChannelLimit] of Integer ;
        yMaxAt : Array[0..EDRChannelLimit] of Integer ;
        Recording : Boolean ;
        NumSamplesInFileRequired : Integer ;
        NumRecordsToAcquire : Integer ;
        TAcquired : Single ;
        end ;

       TCapacity = record
          GrealScale : single ;
          GrealOffset : Integer ;
          GrealZero : Integer ;
          GimagScale : single ;
          GimagOffset : Integer ;
          GimagZero : Integer ;
          ImScale : single ;
          ImOffset : Integer ;
          ImZero : Integer ;
          VmScale : single ;
          VmOffset : Integer ;
          VmZero : Integer ;
          GsScale : single ;
          GsOffset : Integer ;
          GmScale : single ;
          GmOffset : Integer ;
          CmScale : single ;
          CmOffset : Integer ;
          ImSmoothed : Single ;
          VmSmoothed : Single ;
          end ;

          TFluorescence = record
              NumerOffset : Integer ;
              NumerZero : Integer ;
              NumerScale : single ;
              DenomOffset : Integer ;
              DenomZero : Integer ;
              DenomScale : single ;
              RatioOffset : Integer ;
              RatioScale : single ;
              ConcOffset : Integer ;
              ConcScale : single ;
              end ;

          TEventAnalysis = record
              Channel : Integer ;
              ChannelOffset : Integer ;
              Baseline : Single ;
              Smooth : Single ;
              Threshold : Integer ;
              Polarity : Integer ;
              DeadScanCounter : Integer ;
              ScanCounter : Integer ;
              EventAtScan : Integer ;
              NumEvents : Integer ;
              StartOfCountPeriod : Integer ;
              EndOfCountPeriod : Integer ;
              end ;

    TResistanceAnalysis = record
       VmChan : Integer ;
       ImChan : Integer ;
       NumPulseSamples : Integer ;
       NumHoldSamples : Integer ;
       NumPulseStart : Integer ;
       NumPulseEnd : Integer ;
       NumHoldStart : Integer ;
       NumHoldEnd : Integer ;
       Polarity : Integer ;
       Threshold : Single ;
       VPulseSum : Single ;
       IPulseSum : Single ;
       VHoldSum : Single ;
       IHoldSum : Single ;
       PreviousVm : Single ;
       ScaleToVolts : Single ;
       ScaleToAmps : Single ;
       end ;

    TCursors = record
             Base : Array[0..EDRChannelLimit] of Integer ;
             end ;

  TRecordFrm = class(TForm)
    ControlGrp: TGroupBox;
    bRecord: TButton;
    bStop: TButton;
    TriggerGrp: TGroupBox;
    rbFreeRun: TRadioButton;
    Timer: TTimer;
    edIdent: TEdit;
    Label4: TLabel;
    StatusGrp: TGroupBox;
    Bevel1: TBevel;
    Label1: TLabel;
    rbExtTrigger: TRadioButton;
    lbFileSize: TLabel;
    edRecordDuration: TValidatedEdit;
    scDisplay: TScopeDisplay;
    StimulatorGrp: TGroupBox;
    bStartStimulus: TButton;
    GroupBox3: TGroupBox;
    ckCapacity: TCheckBox;
    ckFluorescence: TCheckBox;
    bSpecial: TButton;
    shSeparator: TShape;
    ReadoutGrp: TGroupBox;
    ckFixedZeroLevels: TCheckBox;
    lbRecording: TLabel;
    edMarker: TEdit;
    bMark: TButton;
    EdNumMarkers: TEdit;
    ExtTriggerGrp: TGroupBox;
    rbExtTriggerHigh: TRadioButton;
    rbExttriggerLow: TRadioButton;
    lbReadOut: THTMLLabel;
    bStopStimulus: TButton;
    TDisplayPanel: TPanel;
    edTDisplay: TValidatedEdit;
    bTDisplayDouble: TButton;
    bTDisplayHalf: TButton;
    lbTDisplay: TLabel;
    rbTDisplayUnitsMSecs: TRadioButton;
    rbTDisplayUnitsSecs: TRadioButton;
    rbTDisplayUnitsMins: TRadioButton;
    cbStimProgram: TComboBox;
    VHoldGrp: TGroupBox;
    VHold0Panel: TPanel;
    lbVHold0: TLabel;
    edVHold0: TValidatedEdit;
    VHold1Panel: TPanel;
    Label7: TLabel;
    edVHold1: TValidatedEdit;
    edNumTriggerSweeps: TValidatedEdit;
    Label2: TLabel;
    ckEventFrequency: TCheckBox;
    DisplayGrp: TGroupBox;
    lbAmplifier: TLabel;
    edAmplifierGain: TValidatedEdit;
    Amplifier2Panel: TPanel;
    lbAmplifier2: TLabel;
    edAmplifier2Gain: TValidatedEdit;
    bUpdateGain: TButton;
    ckResistance: TCheckBox;
    procedure TimerTimer(Sender: TObject);
    procedure bRecordClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure bStopClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure rbFreeRunClick(Sender: TObject);
    procedure rbExtTriggerClick(Sender: TObject);
    procedure FormDeactivate(Sender: TObject);
    procedure edIdentKeyPress(Sender: TObject; var Key: Char);
    procedure FormActivate(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure edTDisplayKeyPress(Sender: TObject; var Key: Char);
    procedure edRecordDurationKeyPress(Sender: TObject; var Key: Char);
    procedure bStartStimulusClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure bSpecialClick(Sender: TObject);
    procedure ckFluorescenceClick(Sender: TObject);
    procedure scDisplayCursorChange(Sender: TObject);
    procedure ckCapacityClick(Sender: TObject);
    procedure ckFixedZeroLevelsClick(Sender: TObject);
    procedure edIdentChange(Sender: TObject);
    procedure bMarkClick(Sender: TObject);
    procedure edMarkerKeyPress(Sender: TObject; var Key: Char);
    procedure rbExtTriggerHighClick(Sender: TObject);
    procedure rbExttriggerLowClick(Sender: TObject);
    procedure bUpdateGainClick(Sender: TObject);
    procedure bStopStimulusClick(Sender: TObject);
    procedure edAmplifierGainKeyPress(Sender: TObject; var Key: Char);
    procedure bTDisplayHalfClick(Sender: TObject);
    procedure bTDisplayDoubleClick(Sender: TObject);
    procedure rbTDisplayUnitsMSecsClick(Sender: TObject);
    procedure rbTDisplayUnitsSecsClick(Sender: TObject);
    procedure rbTDisplayUnitsMinsClick(Sender: TObject);
    procedure edVHold0KeyPress(Sender: TObject; var Key: Char);
    procedure edVHold1KeyPress(Sender: TObject; var Key: Char);
    procedure cbStimProgramChange(Sender: TObject);
    procedure edNumTriggerSweepsKeyPress(Sender: TObject; var Key: Char);
    procedure ckEventFrequencyClick(Sender: TObject);
    procedure ckResistanceClick(Sender: TObject);
  private
    { Private declarations }

    FileName : String ;           // Data file in use for recording

    WriteBufs : TWriteBufs ;      // Circular A/D data buffer control record

    ADC : PSmallIntArray ;         { A/D data buffer }
    DAC : PSmallIntArray ;         { D/A data buffer }
    DigBuf : PSmallIntArray ;      { Digital port output array }
    DisplayBuf : TSmallIntArray ; { Scope display data buffer}

    StimulatorAvailable : Boolean ;
    ActiveStimFileName : String ;
    StimulusStartTime : Integer ;
    StimulusNumber : Integer ;
    StimulusStatus : String ;      // Name of stimulus in use
  SumProcTime : Integer ;
  nProcTime : Integer ;

    Initialised : Boolean ; { FormShow initialisations complete }
    EmptyFlag : Integer ;
    StimulatorStartRequired : Boolean ; // Stimulator start request flag

    SweepStatus : string ;  // Current recording sweep information
    RecordingStatus : string ;
    ReadUserEnteredGain : Boolean ;
    Cursors : TCursors ;         { Display cursors }

    Cap : TCapacity ;            { Internal real-time capacity parameters }
    Fluor : TFluorescence ;      { Internal real-time fluorescence parameters }
    EventAnalysis : TEventAnalysis ; // Event analysis parameters
    ResistanceAnalysis : TResistanceAnalysis ; // Cell resistance analysis

    TLastReadout : Integer ;

    ii : Integer ;

    procedure StartADC ;
    procedure StartStimulator ;
    procedure StopStimulator ;
    procedure CalcComputedChannels ;
    procedure StartSineWave ;
    procedure DisplaySignalLevels( var Buf : Array of SmallInt ;
                                   StartAt : Integer ) ;
    procedure DisplayLive( Initialise : Boolean ) ;


    function GetRecordDuration : Single ;
    procedure SetRecordDuration( Value : Single ) ;
    function GetStimulusProtocol : String ;
    procedure SetStimulusProtocol( Value : String ) ;
    function GetTriggerMode : Integer ;
    procedure SetTriggerMode( Value : Integer ) ;
    function GetNumTriggerSweeps : Integer ;
    procedure SetNumTriggerSweeps( Value : Integer ) ;
    function GetRunning : Boolean ;
    function GetRecording : Boolean ;
    procedure CalculateResistance ;
    procedure CalculateCapacity ;

  public
    { Public declarations }
    TimerBusy : boolean ;     // Timer scheduled operation in progress

    procedure StopADCandDAC ;
    procedure NewFile ;
    procedure ChangeDisplayGrid ;
    procedure ZoomIn( ChanNum : Integer ) ;
    procedure ZoomOut( ChanNum : Integer ) ;
    procedure ZoomOutAll ;
    procedure UpdateStimProgramList ;
    procedure RestartADC ;
    procedure StartRecording ;
    procedure StopRecording ;
    procedure StartStimulus ;
    procedure StopStimulus ;
    procedure UpdateOutputs ;

    property RecordDuration : Single
             read GetRecordDuration write SetRecordDuration ;
    property StimulusProtocol : String
             read GetStimulusProtocol write SetStimulusProtocol ;
    property TriggerMode : Integer
             read GetTriggerMode write SetTriggerMode ;
    property NumTriggerSweeps : Integer
             read GetNumTriggerSweeps write SetNumTriggerSweeps ;
    property Running : Boolean read GetRunning ;
    property Recording : Boolean read GetRecording ;

  end;

var
  RecordFrm: TRecordFrm;

implementation

{$R *.DFM}

uses MDiform, maths, SetComp, AmpModule , StimModule, RecPlotUnit,
  Sealtest;
const
     MinDACInterval = 0.0002 ;
     NumDACChannels = 2 ;
     MaxPointsPerTimerTick = 8 ;
     MaxDisplayPoints = 1000 ;
     BufferWriteInterval = 0.5 ;
     MinDTForDisplayLevels = 0.02 ;
     MaxDisplayScans = 2000 ;


procedure TRecordFrm.FormShow(Sender: TObject);
{ ------------------------------------
  Initialisation when form is created
  ----------------------------------- }
var
    i : Integer ;
begin

     ii := 0 ;
     Initialised := False ;

     Resize ;

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

     { Get locations of A/D, D/A and digital I/O buffers from lab. interface }
     Main.SESLabIO.GetADCBuffer( ADC ) ;
     Main.SESLabIO.GetDACBuffer( DAC ) ;
     Main.SESLabIO.GetDIGBuffer( DigBuf ) ;
     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 ;

     // Determine whether stimulator function is supported
     if (Main.SESLabIO.LabInterfaceType = NationalInstruments) or  // National Instruments
        (Main.SESLabIO.LabInterfaceType = NIDAQMX) or
        (Main.SESLabIO.LabInterfaceType = CED1401_12) or           // CED 1401
        (Main.SESLabIO.LabInterfaceType = CED1401_16) then
        StimulatorAvailable := True
     else StimulatorAvailable := False ;

     bStartStimulus.Enabled := StimulatorAvailable ;
     bStopStimulus.Enabled := False ;
     StimulatorStartRequired := False ;

     // Update list of stimulus programs
     UpdateStimProgramList ;

     // Set display duration
     edTDisplay.Value := Settings.DisplayDuration ;

     edIdent.text := CdrFH.IdentLine ;

     { Set trigger mode }
     if Settings.TriggerMode = 'E' then rbExtTrigger.checked := True
                                   else rbFreeRun.checked := True ;
     // Set external trigger polarity
     Main.SESLabIO.ADCExternalTriggerActiveHigh := Settings.ExternalTriggerActiveHigh ;
     rbExtTriggerHigh.Checked := Main.SESLabIO.ADCExternalTriggerActiveHigh ;
     // Set voltage range
     Main.SESLabIO.ADCVoltageRangeIndex := Settings.ADCVoltageRangeIndex ;

     { If a CED 1902 is in use, update its settings }
     for i := 1 to 2 do begin
         if Amplifier.AmplifierType[i] = amCED1902 then Amplifier.SetCED1902 ;
         end ;

     // Hide Amplifier #2 panel if it not in use
     if Amplifier.AmplifierType[2] = amNone then Amplifier2Panel.Visible := False
                                            else Amplifier2Panel.Visible := True ;

     edRecordDuration.Value := Settings.RecordDuration ;
     edNumTriggerSweeps.Value := Settings.NumTriggerSweeps ;

     TimerBusy := False ;

     // Initialise time scale
     if Settings.TScale = 1000.0 then rbTDisplayUnitsMsecs.Checked := True
     else if Settings.TScale = 1.0 then rbTDisplayUnitsSecs.Checked := True
     else rbTDisplayUnitsMins.Checked := True ;
     edTDisplay.Scale := Settings.TSCale ;
     edTDisplay.Units := Settings.TUnits ;

     { No recording }
     WriteBufs.Recording := False ;
     lbRecording.Visible := False ;
     bMark.Enabled := False ;

     { Make sure voltage clamp command voltage is at current setting }
     edVHold0.Value := Settings.VCommand[0].HoldingVoltage ;
     edVHold1.Value := Settings.VCommand[1].HoldingVoltage ;
     UpdateOutputs ;

     NewFile ;

     StimulusStatus := '' ;
     RecordingStatus := '' ;
     SweepStatus := '' ;
     ReadUserEnteredGain := False ;

     Timer.Enabled := True ;
     Initialised := True ;
     end ;


procedure TRecordFrm.NewFile ;
{ ---------------------------------------------------------------
  Re-initialise display and A/D system when a new file is created
  --------------------------------------------------------------- }
var
   i,ch : Integer ;
begin

     { Get the A/D sample offsets for each input channel }
     Main.SESLabIO.ADCNumChannels := Settings.NumChannels ;
     edAmplifierGain.Value := RecChannel[0].ADCCalibrationFactor ;
     edAmplifierGain.Units := 'V/' + RecChannel[0].ADCUnits ;
     for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do
         RecChannel[ch].ChannelOffset := Main.SESLabIO.ADCChannelOffsets[ch] ;

     { Initialise scope display area }
     scDisplay.MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     scDisplay.MinADCValue := Main.SESLabIO.ADCMinValue ;
     scDisplay.DisplayGrid := Settings.DisplayGrid ;
     scDisplay.NumHorizontalGridLines := Settings.NumHorizontalGridLines ;
     scDisplay.NumVerticalGridLines := Settings.NumVerticalGridLines ;

     scDisplay.NumPoints := 0 ;
     scDisplay.NumChannels := Settings.NumChannels ;

     { Fixed A/D channel zero levels }
     ckFixedZeroLevels.Checked := Settings.FixedZeroLevels ;
     if Settings.FixedZeroLevels then begin
        for ch := 0 to Settings.NumChannels-1 do RecChannel[ch].ADCZero := 0 ;
        end ;

     { Create a set of zero level cursors }
     scDisplay.ClearHorizontalCursors ;
     for ch := 0 to Settings.NumChannels-1 do begin
        Cursors.Base[ch] := scDisplay.AddHorizontalCursor( ch, clGray, True,'z' ) ;
        scDisplay.HorizontalCursors[ch] := RecChannel[ch].ADCZero ;
        end ;

     { Set channel information }
     for ch := 0 to Settings.NumChannels-1 do begin
        scDisplay.ChanOffsets[ch] := RecChannel[ch].ChannelOffset ;
        scDisplay.ChanUnits[ch] := RecChannel[Ch].ADCUnits ;
        scDisplay.ChanName[ch] := RecChannel[Ch].ADCName ;
        scDisplay.ChanScale[ch] := Abs(RecChannel[Ch].ADCScale) ;
        scDisplay.HorizontalCursors[ch] := RecChannel[ch].ADCZero ;
        scDisplay.yMin[ch] := RecChannel[ch].yMin ;
        scDisplay.yMax[ch] := RecChannel[ch].yMax ;
        scDisplay.ChanVisible[ch] := RecChannel[ch].InUse ;
        end ;
     scDisplay.Invalidate ;

     for i := 0 to High(TSmallIntArray) do DisplayBuf[i] := 0 ;

     { Enable/disable real-time fluorescence ratio computation }
     if Settings.NumChannels < Settings.Fluorescence.MinChannels then begin
        Settings.Fluorescence.InUse := False ;
        ckFluorescence.Enabled := False ;
        end
     else ckFluorescence.Enabled := True ;
     ckFluorescence.Checked :=  Settings.Fluorescence.InUse ;

     { Enable/disable real-time capacity computation }
     if Settings.NumChannels < Settings.Capacity.MinChannels then begin
        Settings.Capacity.InUse := False ;
        ckCapacity.Enabled := False ;
        end
     else ckCapacity.Enabled := True ;
     ckCapacity.Checked :=  Settings.Capacity.InUse ;

     // Enable/disable event frequency plot
     Settings.RTEventAnalysis.InUse := ckEventFrequency.Checked ;

     // Enable/disable cell resistance plot
     Settings.RTResistance.InUse := ckResistance.Checked ;

     // Number of chart markers still available
     edNumMarkers.text := format('%d',[MaxMarkers-MarkerList.Count]);

     Main.SESLabIO.ADCStop ;

     end ;


procedure TRecordFrm.TimerTimer(Sender: TObject);
{ ---------------------
  Timed Event scheduler
  ---------------------}
var
   nCount,i,iEnd : Integer ;
   iTicks : Integer ;
   FilePointer : Integer ;
   ProcessingTime : Single ;
   StimulusTime : Single ;
begin

     // Abort execution if last call still running
     if TimerBusy then Exit ;
     TimerBusy := True ;

     iTicks := TimeGetTime ;

     if CDRFH.NumSamplesInFile > 0 then bUpdateGain.Enabled := False
                                   else bUpdateGain.Enabled := True ;

     if not Main.SESLabIO.ADCActive then begin
        { Start continuous A/D conversion into cyclic buffer }
        StartADC ;
        DisplayLive( True ) ;
        Settings.NewCalculation := True ;

        // If recording and a stimulus is protocol selected, request its start
        if bStop.Enabled then bStartStimulus.Click ;

        End
     else begin

        { * A/D running .. process samples * }

        { Copy A/D samples from A/D converter's internal buffers }
        Main.SESLabIO.ADCBufferRefresh ;

        // Find latest A/D samples
        nCount := 0 ;
        While ((ADC^[WriteBufs.EmptyPointer] <> EmptyFlag) or
              (ADC^[WriteBufs.EmptyPointer+1] <> -EmptyFlag)) and
              (nCount < WriteBufs.NumSamplesPerBuf) do begin
              WriteBufs.EmptyPointer := WriteBufs.EmptyPointer + Main.SESLabIO.ADCNumChannels*2 ;
              if WriteBufs.EmptyPointer >= WriteBufs.NumSamples then
                 WriteBufs.EmptyPointer := WriteBufs.EmptyPointer - WriteBufs.NumSamples ;
              Inc(nCount) ;
              end ;

        WriteBufs.LatestScan := WriteBufs.EmptyPointer - Main.SESLabIO.ADCNumChannels*4 ;
        if WriteBufs.LatestScan < 0 then
           WriteBufs.LatestScan := WriteBufs.LatestScan + WriteBufs.NumSamples ;

        // Calculate computed channels
        CalcComputedChannels ;

        // Update display
        DisplayLive( False ) ;

        // If latest write block full .. write to file
        WriteBufs.ActiveBuf := WriteBufs.OldestScan div WriteBufs.NumSamplesPerBuf ;

        if (WriteBufs.ActiveBuf <> WriteBufs.WriteBuf) then begin

           // Write block to file (if recording)
           if WriteBufs.Recording then begin
              FilePointer := (CDRFH.NumSamplesInFile*2) + CDRFH.NumBytesInHeader ;
              FileSeek( CDRFH.FileHandle, FilePointer, 0 ) ;
              if FileWrite( CDRFH.FileHandle,
                            ADC^[WriteBufs.BufStart[WriteBufs.WriteBuf]],
                            WriteBufs.NumBytesPerBuf )
                 <> WriteBufs.NumBytesPerBuf then
                 Main.StatusBar.SimpleText := 'Error writing to ' + CDRFH.FileName ;
              CDRFH.NumSamplesInFile := CDRFH.NumSamplesInFile + WriteBufs.NumSamplesPerBuf ;
              WriteBufs.TAcquired := WriteBufs.TAcquired
                                     + CDRFH.dt*(WriteBufs.NumSamplesPerBuf div CDRFH.NumChannels) ;
              lbRecording.Visible := not lbRecording.Visible ;

              RecordingStatus := format( 'Recording: %.1f/%.1f s (%d Kb) ',
                                           [WriteBufs.TAcquired,
                                            Settings.RecordDuration,
                                            CDRFH.NumSamplesInFile div 512
                                            ]) ;

              Main.StatusBar.SimpleText := SweepStatus + RecordingStatus + StimulusStatus ;

              end ;

           // Fill buffer with empty flags
           i := WriteBufs.BufStart[WriteBufs.WriteBuf] ;
           iEnd := WriteBufs.BufEnd[WriteBufs.WriteBuf] ;
           while i <= iEnd do begin
                 ADC^[i] := EmptyFlag ;
                 Inc(i) ;
                 ADC^[i] := -EmptyFlag ;
                 Inc(i) ;
                 end ;
           WriteBufs.WriteBuf := WriteBufs.WriteBuf + 1 ;
           if WriteBufs.WriteBuf >= WriteBufs.NumBufs then WriteBufs.WriteBuf := 0 ;

           ProcessingTime := (100.0*SumProcTime/nProcTime)/Timer.Interval ;
           end ;

        if WriteBufs.Recording and
           (CDRFH.NumSamplesInFile >= WriteBufs.NumSamplesInFileRequired) then begin
           { Restart recording if more records to be done }
           Dec(WriteBufs.NumRecordsToAcquire) ;
           if WriteBufs.NumRecordsToAcquire > 0 then bRecord.Click
                                                else bStop.Click  ;
           end ;

        end ;

           iTicks := TimeGetTime - iTicks ;
           SumProcTime :=  SumProcTime + iTicks ;
           Inc(nProcTime) ;


     // Start stimulus (if one requested)
     // (Note. Wait till some A/D samples have been collected)
     if StimulatorStartRequired and (WriteBufs.LatestScan > 0) then begin
        StopStimulator ;
        StartStimulator ;
        StimulatorStartRequired := False ;
        end ;

     // Check for end of stimulus (non-repeating stimuli)
     if bStopStimulus.Enabled and (not Stimulator.Prog.RepeatedStimulus) then begin
        StimulusTime := (TimeGetTime - StimulusStartTime)*1E-3 ;
        if StimulusTime >= Stimulator.ProtocolDuration then begin
           // Stop existing stimulus
           StopStimulator ;
           // Start next stimulus (if required)
           if Stimulator.Prog.NextProtocolFileName <> '' then begin
              ActiveStimFileName := Stimulator.Prog.NextProtocolFileName ;
              StimulatorStartRequired := True ;
              end ;
           end ;
        end ;

     // Update voltage and digital outputs
     if Settings.UpdateOutputs then begin
        if not WriteBufs.Recording then UpdateOutputs ;
        end ;

     TimerBusy := False ;

     end ;


procedure TRecordFrm.DisplayLive(
          Initialise : Boolean         // TRUE = Clear/initialise display
          ) ;
// -----------------------------------------------
// Display digitised signals in acquisition buffer
// -----------------------------------------------
var

    NumScans : Integer ;           // No. of multi-channel scans in display window
    NumSamplesPerBlock : Integer ; // No. of A/D samples per compression block
    Done : Boolean ;               // Loop done flag
    ch,y : Integer ;             // Counters/index variables
    s : string ;
begin

     // Erase display and initialise live display counters
     // --------------------------------------------------

     if Initialise or
        (WriteBufs.NumBlocksDisplayed >= WriteBufs.MaxBlocksDisplayed) then begin

       // No. of multi-channel scans to be displayed
       NumScans := Max( Round(edTDisplay.Value/Main.SESLabIO.ADCSamplingInterval),1 ) ;

       // Size of display compression block
       WriteBufs.NumScansPerBlock := (NumScans div MaxDisplayScans) + 1 ;
       NumSamplesPerBlock := WriteBufs.NumScansPerBlock*Main.SESLabIO.ADCNumChannels ;

       // No. of displayed points per block
       WriteBufs.NumPointsPerBlock := Min(WriteBufs.NumScansPerBlock,2) ;

       // Max. number of points in display
       WriteBufs.MaxBlocksDisplayed := (NumScans div WriteBufs.NumScansPerBlock) ;
       scDisplay.MaxPoints := WriteBufs.MaxBlocksDisplayed*WriteBufs.NumPointsPerBlock ;

       scDisplay.TScale := (Main.SESLabIO.ADCSamplingInterval*WriteBufs.NumScansPerBlock*Settings.TScale)
                           /WriteBufs.NumPointsPerBlock ;
       scDisplay.TFormat := '%.5g ' + Settings.TUnits + ' ' ;

       scDisplay.xMin := 0 ;
       scDisplay.xMax := scDisplay.MaxPoints-1 ;
       // Enable/disable display calibration grid
       scDisplay.DisplayGrid := Main.mnDisplayGrid.Checked ;
       scDisplay.SetDataBuf( @DisplayBuf ) ;

       scDisplay.XOffset := (CDRFH.NumSamplesInFile*WriteBufs.NumPointsPerBlock)
                            div (WriteBufs.NumScansPerBlock*Main.SESLabIO.ADCNumChannels) ;

       scDisplay.NumPoints := 0 ;
       scDisplay.Invalidate ;

       // Clear markers on display
       scDisplay.ClearMarkers ;

       // Initialise counters
       WriteBufs.BlockCount := WriteBufs.NumScansPerBlock ;
       WriteBufs.DispPointer := 0 ;
       WriteBufs.NumBlocksDisplayed := 0 ;

       end ;

     // Read and display samples from A/D sample buffer
     // -----------------------------------------------

     if WriteBufs.OldestScan = WriteBufs.LatestScan then Done := True
                                                    else Done := False ;
     While not Done do begin

        // Initialise block
        if WriteBufs.BlockCount >= WriteBufs.NumScansPerBlock then begin
           for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin
               WriteBufs.yMin[ch] := Main.SESLabIO.ADCMaxValue ;
               WriteBufs.yMax[ch] := Main.SESLabIO.ADCMinValue ;
               end ;
           WriteBufs.BlockCount := 0 ;
           end ;

        // Determine min. / max. value & order of samples within compression block
        for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin

            y := ADC^[WriteBufs.OldestScan+ch] ;

            if y < WriteBufs.yMin[ch] then begin
               WriteBufs.yMin[ch] := y ;
               WriteBufs.yMinAt[ch] := WriteBufs.BlockCount ;
               end ;
            if y > WriteBufs.yMax[ch] then begin
               WriteBufs.yMax[ch] := y ;
               WriteBufs.yMaxAt[ch] := WriteBufs.BlockCount ;
               end ;
            end ;
        Inc(WriteBufs.BlockCount) ;

        // When block complete ... write min./max. to display buffer
        if WriteBufs.BlockCount >= WriteBufs.NumScansPerBlock then begin

           // First point
           for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin
               if WriteBufs.yMaxAt[ch] <= WriteBufs.yMinAt[ch] then
                  DisplayBuf[WriteBufs.DispPointer] := WriteBufs.yMax[ch]
               else DisplayBuf[WriteBufs.DispPointer] := WriteBufs.yMin[ch] ;
               WriteBufs.DispPointer := WriteBufs.DispPointer + 1 ;
               end ;

           // Second point
           if WriteBufs.BlockCount > 1 then begin
              for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin
                  if WriteBufs.yMaxAt[ch] >= WriteBufs.yMinAt[ch] then
                     DisplayBuf[WriteBufs.DispPointer] := WriteBufs.yMax[ch]
                  else DisplayBuf[WriteBufs.DispPointer] := WriteBufs.yMin[ch] ;
                  WriteBufs.DispPointer := WriteBufs.DispPointer + 1 ;
                  end ;
              end ;

           Inc(WriteBufs.NumBlocksDisplayed) ;
           end ;

        // Increment pointer to next available scan
        WriteBufs.OldestScan := WriteBufs.OldestScan + Main.SESLabIO.ADCNumChannels ;
        if WriteBufs.OldestScan >= WriteBufs.NumSamples then
           WriteBufs.OldestScan := WriteBufs.OldestScan - WriteBufs.NumSamples ;
        if (WriteBufs.OldestScan = WriteBufs.LatestScan) or
           (WriteBufs.NumBlocksDisplayed >= scDisplay.MaxPoints) then Done := True ;

        end ;

     // Display latest points added to display buffer
     scDisplay.DisplayNewPoints( WriteBufs.NumBlocksDisplayed*WriteBufs.NumPointsPerBlock );
     //scDisplay.Invalidate ;

     //Main.statusbar.simpletext := format('%d/%d',[WriteBufs.NumBlocksDisplayed,
     //WriteBufs.MaxBlocksDisplayed]);

     // Update cursor readout
     if (WriteBufs.NumBlocksDisplayed > 0) and
        (WriteBufs.NumBlocksDisplayed < WriteBufs.MaxBlocksDisplayed) and
        (TimeGetTime > (TLastReadout+500)) then begin
        s := '' ;
        for ch := 0 to scDisplay.NumChannels-1 do begin
            RecChannel[ch].CursorValue := RecChannel[ch].ADCScale *
                        ( DisplayBuf[(WriteBufs.NumPointsPerBlock*(WriteBufs.NumBlocksDisplayed-1)*Main.SESLabIO.ADCNumChannels)
                        + scDisplay.ChanOffsets[ch]] - scDisplay.ChanZero[ch] ) ;
            s := s + format( ' %s= %.4g %s<br>',
                                    [scDisplay.ChanName[ch],
                                     RecChannel[ch].CursorValue ,
                                     scDisplay.ChanUnits[ch]] ) ;
            end ;
        lbReadOut.caption := s ;
        lbReadOut.Invalidate ;
        TLastReadout := TimeGetTime ;
        end ;

     end ;


procedure TRecordFrm.StartADC ;
{ ---------------------
  Start A/D conversions
  ---------------------}
const
    IdealWriteBufDuration = 2.0 ;
    MinScansPerWriteBuf = 16 ;
var

   ExtTrigger : Boolean ;
   i,ch : Integer ;
   MaxScans : Integer ;     // Max. number of scans in A/D buffer
   Done : Boolean ;         // Loop termination flag
   TempChannel : TChannel ;
begin

     // Get amplifier current/voltage gain settings

     for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin

        RecChannel[ch].ADCMaxValue :=  Main.SESLabIO.ADCMaxValue ;

        if RecChannel[ch].ADCCalibrationFactor = 0.0 then
           RecChannel[ch].ADCCalibrationFactor := 0.001 ;
        if RecChannel[ch].ADCAmplifierGain = 0.0 then
           RecChannel[ch].ADCAmplifierGain := 1.0 ;

         // Get amplifier #1 gain (if manual setting required)
         if (ch = 0) and (not Amplifier.GainTelegraphAvailable[1]) then begin
            if ReadUserEnteredGain and (CDRFH.NumSamplesInFile = 0) then begin
               RecChannel[ch].ADCCalibrationFactor := edAmplifierGain.Value ;
               end ;
           end ;

         // Get amplifier #2 gain (if manual setting required)
         if (ch = 2) and (not Amplifier.GainTelegraphAvailable[2]) and
            (Amplifier.AmplifierType[2] <> amNone) then begin
            if ReadUserEnteredGain and (CDRFH.NumSamplesInFile = 0) then begin
               RecChannel[ch].ADCCalibrationFactor := edAmplifier2Gain.Value ;
               end ;
           end ;

        // Get amplifier channel settings (using telegraphs)
        Amplifier.GetChannelSettings( ch, RecChannel[ch].ADCName,
                                          RecChannel[ch].ADCUnits,
                                          RecChannel[ch].ADCCalibrationFactor,
                                          RecChannel[ch].ADCAmplifierGain ) ;

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

        // Update display
        scDisplay.ChanScale[ch] := Abs(RecChannel[ch].ADCScale) ;
        scDisplay.ChanUnits[ch] := RecChannel[ch].ADCUnits ;
        scDisplay.ChanName[ch] := RecChannel[ch].ADCName ;

        // Update amplifier #1 gain setting boxes
        if ch = 0 then begin
           edAmplifierGain.Units := 'V/' + RecChannel[ch].ADCUnits ;
           edAmplifierGain.Value := Abs(Main.SESLabIO.ADCVoltageRange) /
                                    (RecChannel[ch].ADCScale*(Main.SESLabIO.ADCMaxValue+1) ) ;
           end ;

        // Update amplifier #2 gain setting boxes
        if (ch = 2) then begin
           edAmplifier2Gain.Units := 'V/' + RecChannel[ch].ADCUnits ;
           edAmplifier2Gain.Value := Abs(Main.SESLabIO.ADCVoltageRange) /
                                    (RecChannel[ch].ADCScale*(Main.SESLabIO.ADCMaxValue+1) ) ;
           end ;

        end ;

     // Clear request to read gain
     ReadUserEnteredGain := False ;

     // Ensure timer interval is long enough to capture some samples
     Timer.Interval := Max(Round(Main.SESLabIO.ADCNumChannels*Main.SESLabIO.ADCSamplingInterval*4000.0),55) ;

     // Set number of scans in write block
     MaxScans := Main.SESLabIO.ADCBufferLimit div Main.SESLabIO.ADCNumChannels ;

     WriteBufs.NumBufs := 1 ;
     Done := False ;
     while not Done do begin
          Inc(WriteBufs.NumBufs) ;
          WriteBufs.NumScansPerBuf := MaxScans div WriteBufs.NumBufs ;
          WriteBufs.NumScansPerBuf := 4*(WriteBufs.NumScansPerBuf div 4) ;
          if ((WriteBufs.NumScansPerBuf*Main.SESLabIO.ADCSamplingInterval) <= IdealWriteBufDuration) or
             (WriteBufs.NumScansPerBuf <= MinScansPerWriteBuf) then Done := True ;
          end ;
     WriteBufs.NumSamplesPerBuf := WriteBufs.NumScansPerBuf*Main.SESLabIO.ADCNumChannels ;
     WriteBufs.NumBytesPerBuf := WriteBufs.NumSamplesPerBuf*2 ;

     // Set up write Buf pointers
     for i := 0 to WriteBufs.NumBufs-1 do begin
         WriteBufs.BufStart[i] := i*WriteBufs.NumSamplesPerBuf ;
         WriteBufs.BufEnd[i] := WriteBufs.BufStart[i] + WriteBufs.NumSamplesPerBuf - 1 ;
         end ;

     // Total number of scans in circular buffer
     WriteBufs.NumScans := WriteBufs.NumBufs*WriteBufs.NumScansPerBuf ;
     // Total number of A/D samples
     WriteBufs.NumSamples := WriteBufs.NumScans*Main.SESLabIO.ADCNumChannels ;
     // Initialse sub-buffer index
     WriteBufs.WriteBuf := 0 ;
     WriteBufs.EmptyPointer := Main.SESLabIO.ADCNumChannels*4 ;
     WriteBufs.LatestScan := 0 ;
     WriteBufs.OldestScan := 0 ;

     { Fix zero level at true A/D converter zero }
     if ckFixedZeroLevels.Checked then begin
        for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin
            RecChannel[ch].ADCZero := 0 ;
            scDisplay.HorizontalCursors[ch] := 0 ;
            end ;

        end ;
     Settings.FixedZeroLevels := ckFixedZeroLevels.Checked ;
     scDisplay.CursorsEnabled := not Settings.FixedZeroLevels ;

     { If in external trigger mode when recording ...
       Request A/D converter to wait for a trigger pulse }
     if rbExtTrigger.checked and bStop.Enabled  then begin
        ExtTrigger := True ;
        Main.SESLabIO.ADCExternalTriggerActiveHigh := rbExtTriggerHigh.checked ;
        end
     else ExtTrigger := False ;

     { Enable on-line fluorescence ratio computation }
     if Main.SESLabIO.ADCNumChannels < Settings.Fluorescence.MinChannels then
        Settings.Fluorescence.InUse := False ;

     if Settings.Fluorescence.InUse then begin
        RecChannel[Settings.Fluorescence.RatioChan].ADCZero := 0 ;
        RecChannel[Settings.Fluorescence.RatioChan].yMin :=
           Max( RecChannel[Settings.Fluorescence.RatioChan].yMin,
                ((-RecChannel[Settings.Fluorescence.RatioChan].ADCmaxValue -1) div 10)) ;
        end ;

     if Main.SESLabIO.ADCNumChannels < Settings.Capacity.MinChannels then
        Settings.Capacity.InUse := False ;

     scDisplay.Invalidate ;

     { Start A/D converter sampling }
     Main.SESLabIO.ADCStop ;
     Main.SESLabIO.ADCNumChannels := Settings.NumChannels ;
     Main.SESLabIO.ADCNumSamples := WriteBufs.NumScans ;
     Main.SESLabIO.ADCSamplingInterval := Settings.ADCSamplingInterval ;
     Main.SESLabIO.ADCVoltageRangeIndex := Settings.ADCVoltageRangeIndex ;

     // Set A/D sweep triggering
     if not ExtTrigger then Main.SESLabIO.ADCTriggerMode := tmFreeRun
                       else Main.SESLabIO.ADCTriggerMode := tmExtTrigger ;

     Main.SESLabIO.ADCCircularBuffer := True ;
     Main.SESLabIO.ADCStart ;

     SumProcTime := 0 ;
     nProcTime := 0 ;
     TLastReadout := TimeGetTime ;

//     if Settings.Capacity.InUse then StartSineWave ;

     end ;


procedure TRecordFrm.StartStimulator ;
{ -----------------------
  Start stimulator output
  -----------------------}
var
   DACdt : single ;
   MaxDACPoints : Integer ;
begin

     if not StimulatorAvailable then Exit ;

     // Disable changes to stimulus & holding voltages
     bStopStimulus.Enabled := True ;
     bStartStimulus.Enabled := False ;
     cbStimProgram.Enabled := False ;

     // Stop D/A output
     Main.SESLabIO.DACStop ;

     // Load stimulus program from file
     Stimulator.LoadProgram( ActiveStimFileName ) ;

     { D/A update interval }
     Main.SESLabIO.DigitalStimulusEnabled := Stimulator.Prog.DigitalInUse ;

     MaxDACPoints := Main.SESLabIO.DACBufferLimit div (NumDACChannels) - 10 ;

     if Stimulator.Prog.ExtNumPoints > 0 then DACdt := Stimulator.Prog.ExtDACdt
                                         else DACdt := Stimulator.ProtocolDuration/MaxDACPoints ;
     DACdt := Max( DACdt, Main.SESLabIO.DACMinUpdateInterval ) ;
     Main.SESLabIO.DACUpdateInterval := DACdt ;
     DACdt := Main.SESLabIO.DACUpdateInterval ;

     Main.SESLabIO.DACNumChannels := NumDACChannels ;
     Main.SESLabIO.DACNumSamples := Min( MaxDACPoints,
                                         Round(Stimulator.ProtocolDuration/DACdt)) ;
     // Create stimulus
     Stimulator.CreateWaveform( DAC,DigBuf,Main.SESLabIO.DACNumSamples ) ;

     Main.SESLabIO.DACRepeatWaveform := Stimulator.Prog.RepeatedStimulus ;

     if Main.SESLabIO.DigitalStimulusEnabled then Main.SESLabIO.DACDigStart(0)
                                             else Main.SESLabIO.DACStart ;

     // Save start time of stimulus
     StimulusStartTime := TimeGetTime ;

     // Add marker and log entry if recording
     if bStop.Enabled then begin
        Inc(StimulusNumber) ;
        edMarker.Text := format('Stim%d',[StimulusNumber]) ;
        WriteToLogFile( format('Stim:%d %s started',[StimulusNumber,ActiveStimFileName])) ;
        bMark.Click ;
        end ;

     StimulusStatus := 'Stim: '
                       + ChangeFileExt(ExtractFileName(ActiveStimFileName),'') ;

     Main.StatusBar.SimpleText := SweepStatus + RecordingStatus + StimulusStatus ;

     end ;


procedure TRecordFrm.StopStimulator ;
{ ------------------------
  Stop stimulator pulses
  ------------------------ }
begin

   if not StimulatorAvailable then Exit ;

   // Stop any D/A output
   Main.SESLabIO.DACStop ;

   // Update outputs
   UpdateOutputs ;

   bStopStimulus.Enabled := False ;
   bStartStimulus.Enabled := True ;
   cbStimProgram.Enabled := True ;
   StimulusStatus := '' ;

   Main.StatusBar.SimpleText := SweepStatus + RecordingStatus + StimulusStatus ;

   end ;


procedure TRecordFrm.StopRecording ;
{ ----------------------
  Stop recording to disk
  ----------------------}
begin
     bRecord.Enabled := True ;
     bStop.Enabled := False ;
     edTDisplay.Enabled := True ;
     TDisplayPanel.Enabled := True ;
     ckFixedZeroLevels.Enabled := True ;
     lbRecording.Visible := False ;
     bMark.Enabled := False ;
     bUpdateGain.Enabled := True ;

     { Save data to file header }
     if CDRFH.FileHandle >= 0 then SaveCDRHeader( CdrFH ) ;

     { Update signal display window (if it is open) }
     Main.UpdateViewSig ;

     WriteBufs.Recording := False ;

     { Shut down lab. interface and re-start
       (This turns off any stimulus pattern that is in progress) }
     Main.SESLabIO.ADCStop ;

     // Stop any running stimulus program
     StopStimulator ;

     { Indicate when recording stopped on log file }
     WriteToLogFile( SweepStatus + format(
                    ' Stopped at %.4g s',[(CdrFH.NumSamplesInFile*CdrFH.dt)/
                                           CdrFH.NumChannels])  ) ;

     RecordingStatus := '' ;
     SweepStatus := '' ;
     Main.StatusBar.SimpleText := SweepStatus + RecordingStatus + StimulusStatus ;

     Main.SetMenus ;
     end ;


procedure TRecordFrm.StartStimulus ;
// -----------------------
// Start stimulus protocol
// -----------------------
begin
     bStartStimulus.Click ;
     end ;


procedure TRecordFrm.StopStimulus ;
// -----------------------
// Stop stimulus protocol
// -----------------------
begin
     bStopStimulus.Click ;
     end ;


procedure TRecordFrm.CalcComputedChannels ;
{ -------------------------------------------------------
  Calculate computed channels from analogue input channels
  ------------------------------------------------------- }
const
     mVToVolt = 1E-3 ;
     pFToFarad = 1E-12 ;
     nSToSiemen = 1E-9 ;
     pAToAmp = 1E-12 ;
     TwoPi = 2*pi ;
var
   Ratio,Numerator,Denominator,Threshold : single ;
   A,B,Gm,Gs,Vm,Im,Cm : single ;
   iScan : Integer ;
   OK : Boolean ;
   ch : Integer ;
   yDiff : Integer ;
begin

     if Settings.Fluorescence.InUse then begin

        if Settings.NewCalculation then begin

           Fluor.NumerZero := RecChannel[Settings.Fluorescence.NumerChan].ADCZero ;
           Fluor.NumerOffset :=  RecChannel[Settings.Fluorescence.NumerChan].ChannelOffset ;
           Fluor.DenomZero := RecChannel[Settings.Fluorescence.DenomChan].ADCZero ;
           Fluor.DenomOffset :=  RecChannel[Settings.Fluorescence.DenomChan].ChannelOffset ;

           { Ratio O/P channel settings }
           if Settings.Fluorescence.RatioChan >= 0 then begin
              RecChannel[Settings.Fluorescence.RatioChan].ADCScale :=
                             Settings.Fluorescence.RatioDisplayMax/Main.SESLabIO.ADCMaxValue ;
              RecChannel[Settings.Fluorescence.RatioChan].ADCCalibrationFactor :=
                   ADCScaleToCalibFactor( Main.SESLabIO.ADCVoltageRange,
                                          RecChannel[Settings.Fluorescence.RatioChan] ) ;
              Fluor.RatioScale := 1.0 / RecChannel[Settings.Fluorescence.RatioChan].ADCScale ;
              Fluor.RatioOffset :=  RecChannel[Settings.Fluorescence.RatioChan].ChannelOffset ;
              end
           else Fluor.RatioOffset := -1 ;

           { Concentration O/P channel settings }
           if Settings.Fluorescence.ConcChan >= 0 then begin
              RecChannel[Settings.Fluorescence.ConcChan].ADCScale :=
                             Settings.Fluorescence.ConcDisplayMax/Main.SESLabIO.ADCMaxValue ;
              RecChannel[Settings.Fluorescence.ConcChan].ADCCalibrationFactor :=
                  ADCScaleToCalibFactor( Main.SESLabIO.ADCVoltageRange,
                                         RecChannel[Settings.Fluorescence.ConcChan] ) ;
              Fluor.ConcScale := Settings.Fluorescence.Keff /
                     RecChannel[Settings.Fluorescence.RatioChan].ADCScale ;
              Fluor.ConcOffset := RecChannel[Settings.Fluorescence.ConcChan].ChannelOffset ;
              end
           else Fluor.ConcOffset := -1 ;

           { Set channel information }
           for ch := 0 to Main.SESLabIO.ADCNumChannels -1 do begin
               scDisplay.ChanOffsets[ch] := RecChannel[ch].ChannelOffset ;
               scDisplay.ChanUnits[ch] := RecChannel[Ch].ADCUnits ;
               scDisplay.ChanName[ch] := RecChannel[Ch].ADCName ;
               scDisplay.ChanScale[ch] := Abs(RecChannel[Ch].ADCScale) ;
               scDisplay.HorizontalCursors[ch] := RecChannel[ch].ADCZero ;
               scDisplay.yMin[ch] := RecChannel[ch].yMin ;
               scDisplay.yMax[ch] := RecChannel[ch].yMax ;
               scDisplay.ChanVisible[ch] := RecChannel[ch].InUse ;
               end ;
           scDisplay.Invalidate ;

           if CDRFH.FileHandle >= 0 then SaveCDRHeader ( CDRFH ) ;
           Settings.NewCalculation := False ;
           end ;

        { Acceptance threshold for ratio calculation }
        Threshold := Settings.Fluorescence.FThreshold /
                     RecChannel[Settings.Fluorescence.NumerChan].ADCScale ;

        iScan := WriteBufs.OldestScan ;
        while iScan <> WriteBufs.LatestScan do begin

              { Calculate ratio of fluorescence channels }
              Numerator := (ADC^[iScan+Fluor.NumerOffset] - Fluor.NumerZero) ;
              Denominator := (ADC^[iScan+Fluor.DenomOffset] - Fluor.DenomZero) ;
              if (Numerator >= Threshold) and (Denominator >= Threshold) then
                 Ratio := Numerator/Denominator
              else Ratio := 0.0 ;

              if Fluor.RatioOffset >= 0 then
                 ADC^[iScan+Fluor.RatioOffset] := Round(Ratio*Fluor.RatioScale) ;

              { Calculate ion concentration using binding eqn. }
              if Fluor.ConcOffset >= 0 then begin
                 if Ratio <> Settings.Fluorescence.RMax then
                    ADC^[iScan+Fluor.ConcOffset] := Round( Fluor.ConcScale *
                                              (Ratio - Settings.Fluorescence.RMin) /
                                              (Settings.Fluorescence.RMax - Ratio) )
                 else ADC^[iScan+Fluor.ConcOffset] := 0 ;
                 end ;
              { Next block of channels }
              iScan := iScan + Main.SESLabIO.ADCNumChannels ;
              if iScan >= WriteBufs.NumSamples then
                 iScan := iScan - WriteBufs.NumSamples ;

              end ;
        end ;

     if Settings.Capacity.InUse then CalculateCapacity ;


     // Event frequency display
     if Settings.RTEventAnalysis.InUse then begin

        // Initialise event detector
        if Settings.NewCalculation then begin

           // Clear scans acquired counter
           EventAnalysis.ScanCounter := 0 ;

           // Channel offset
           EventAnalysis.ChannelOffset := RecChannel[Settings.RTEventAnalysis.Channel].ChannelOffset ;

           // Clear dead time counter
           EventAnalysis.DeadScanCounter := 0 ;

           // Clear event counter
           EventAnalysis.NumEvents := 0 ;

           EventAnalysis.StartOfCountPeriod := 0 ;
           EventAnalysis.EndOfCountPeriod := 0 ;

           // Set detection threshold and polarity
           EventAnalysis.Threshold := Round( Settings.RTEventAnalysis.DetectionThreshold /
                                             RecChannel[Settings.RTEventAnalysis.Channel].ADCScale ) ;
           if EventAnalysis.Threshold < 0 then begin
              EventAnalysis.Threshold := Abs(EventAnalysis.Threshold) ;
              EventAnalysis.Polarity := -1 ;
              end
           else EventAnalysis.Polarity := 1 ;

           // Set running mean baseline and smoothing factor
           EventAnalysis.Baseline := ADC^[WriteBufs.OldestScan+EventAnalysis.ChannelOffset] ;
           EventAnalysis.Smooth := Main.SESLabIO.ADCSamplingInterval /
                                   Max(Settings.RTEventAnalysis.RunningMeanTime,
                                       Main.SESLabIO.ADCSamplingInterval);

           end ;

        // Open plotting window (if not already open)
        if not Main.FormExists( 'RecPlotFrm' ) then begin
           RecPlotFrm := TRecPlotFrm.Create(Self) ;
           RecPlotFrm.Width :=  Main.ClientWidth - RecordFrm.Left - RecordFrm.Width - 20 ;
           RecPlotFrm.Left := RecordFrm.Left + RecordFrm.Width + 2 ;
           RecPlotFrm.Top := 10 ;
           end ;

        iScan := WriteBufs.OldestScan ;
        while iScan <> WriteBufs.LatestScan do begin

              //
              yDiff := ADC^[iScan+EventAnalysis.ChannelOffset] - Round(EventAnalysis.Baseline) ;

              //outputdebugString(PChar(format('%d %d ',[yDiff,Round(EventAnalysis.Baseline)]))) ;

              // Detect event
              if ((EventAnalysis.Polarity*yDiff) > EventAnalysis.Threshold) and
                 (EventAnalysis.DeadScanCounter <= 0) then begin
                 EventAnalysis.EventAtScan := EventAnalysis.ScanCounter ;

                 // Update plot
                 RecPlotFrm.PlotFrequency( EventAnalysis.EventAtScan,
                                           EventAnalysis.StartOfCountPeriod,
                                           EventAnalysis.EndOfCountPeriod ) ;

                 EventAnalysis.NumEvents := EventAnalysis.NumEvents + 1 ;
                 EventAnalysis.DeadScanCounter := Round( Settings.RTEventAnalysis.DeadTime/
                                                         Main.SESLabIO.ADCSamplingInterval ) ;
                 end ;

              // Decrement dead time counter
              EventAnalysis.DeadScanCounter := Max(EventAnalysis.DeadScanCounter-1,0) ;

              // Update running mean baseline
              EventAnalysis.Baseline := (EventAnalysis.Smooth*yDiff)
                                        - (1.0-EventAnalysis.Smooth)*EventAnalysis.Baseline ;

              Inc(EventAnalysis.ScanCounter) ;

              { Next block of channels }
              iScan := iScan + Main.SESLabIO.ADCNumChannels ;
              if iScan >= WriteBufs.NumSamples then
                 iScan := iScan - WriteBufs.NumSamples ;

              end ;


        end ;

     if Settings.RTResistance.InUse then CalculateResistance ;

     Settings.NewCalculation := False ;
     end ;


procedure TRecordFrm.CalculateCapacity ;
// ----------------------------
// On-line capacity calculation
// ----------------------------
const
     mVToVolt = 1E-3 ;
     pFToFarad = 1E-12 ;
     nSToSiemen = 1E-9 ;
     pAToAmp = 1E-12 ;
     TwoPi = 2*pi ;
var
   Rm,A,X,GImag,GReal,Gm,Gs,Vm,Im,Cm : single ;
   iScan : Integer ;
   OK : Boolean ;
   ch : Integer ;
   yDiff : Integer ;
   Gdc,cm2 : single ;
   Omega,Denom : Single ;
begin

     if Settings.NewCalculation then begin
        // Set channel names and units }

        { Cell membrane capacity }
        if Settings.Capacity.CmChan >= 0 then begin
            RecChannel[Settings.Capacity.CmChan].ADCName := 'Cm' ;
            RecChannel[Settings.Capacity.CmChan].ADCUnits := 'pF' ;
            RecChannel[Settings.Capacity.CmChan].ADCScale :=
                               Settings.Capacity.CmDisplayMax/Main.SESLabIO.ADCMaxValue ;
            RecChannel[Settings.Capacity.CmChan].ADCCalibrationFactor :=
            ADCScaleToCalibFactor( Main.SESLabIO.ADCVoltageRange,
                                   RecChannel[Settings.Capacity.CmChan] ) ;
            RecChannel[Settings.Capacity.CmChan].yMin := Main.SESLabIO.ADCMinValue div 10 ;
            Cap.CmScale := RecChannel[Settings.Capacity.CmChan].ADCScale*pFToFarad ;
            Cap.CmOffset :=  RecChannel[Settings.Capacity.CmChan].ChannelOffset ;
            end
        else Cap.CmOffset := -1 ;

        { Cell membrane conductance }
        if Settings.Capacity.GmChan >= 0 then begin
           RecChannel[Settings.Capacity.GmChan].ADCName := 'Gm' ;
           RecChannel[Settings.Capacity.GmChan].ADCUnits := 'nS' ;
           RecChannel[Settings.Capacity.GmChan].ADCScale :=
                                        Settings.Capacity.GmDisplayMax/Main.SESLabIO.ADCMaxValue ;
           RecChannel[Settings.Capacity.GmChan].ADCCalibrationFactor :=
                                 ADCScaleToCalibFactor( Main.SESLabIO.ADCVoltageRange,
                                                        RecChannel[Settings.Capacity.GmChan] ) ;
           RecChannel[Settings.Capacity.GmChan].yMin :=
                 (-RecChannel[Settings.Capacity.GmChan].ADCMaxValue-1) div 10 ;
           Cap.GmScale := RecChannel[Settings.Capacity.GmChan].ADCScale*nSToSiemen ;
           Cap.GmOffset :=  RecChannel[Settings.Capacity.GmChan].ChannelOffset ;
           end
        else Cap.GmOffset := -1 ;

        { Cell access conductance }
        if Settings.Capacity.GsChan >= 0 then begin
           RecChannel[Settings.Capacity.GsChan].ADCName := 'Gs' ;
           RecChannel[Settings.Capacity.GsChan].ADCUnits := 'nS' ;
           RecChannel[Settings.Capacity.GsChan].ADCScale :=
                                     Settings.Capacity.GsDisplayMax/Main.SESLabIO.ADCMaxValue ;
           RecChannel[Settings.Capacity.GsChan].ADCCalibrationFactor :=
                  ADCScaleToCalibFactor( Main.SESLabIO.ADCVoltageRange,
                                         RecChannel[Settings.Capacity.GsChan] ) ;
           RecChannel[Settings.Capacity.GsChan].yMin :=
                 (-RecChannel[Settings.Capacity.GsChan].ADCMaxValue-1) div 10 ;
           Cap.GsScale := RecChannel[Settings.Capacity.GsChan].ADCScale*nSToSiemen ;
           Cap.GsOffset :=  RecChannel[Settings.Capacity.GsChan].ChannelOffset ;
           end
        else Cap.GsOffset := -1 ;

        { Cell membrane current }
        RecChannel[Settings.Capacity.ImChan].ADCName := 'Im' ;
        RecChannel[Settings.Capacity.ImChan].ADCUnits := 'pA' ;
        Cap.ImZero := RecChannel[Settings.Capacity.ImChan].ADCZero ;
        Cap.ImOffset :=  RecChannel[Settings.Capacity.ImChan].ChannelOffset ;
        Cap.ImScale := RecChannel[Settings.Capacity.ImChan].ADCScale*pAToAmp ;

        { Cell membrane potential }
        RecChannel[Settings.Capacity.VmChan].ADCName := 'Vm' ;
        RecChannel[Settings.Capacity.VmChan].ADCUnits := 'mV' ;
        Cap.VmZero := RecChannel[Settings.Capacity.VmChan].ADCZero ;
        Cap.VmOffset :=  RecChannel[Settings.Capacity.VmChan].ChannelOffset ;
        Cap.VmScale := RecChannel[Settings.Capacity.VmChan].ADCScale*mVToVolt ;

        { Real component of cell membrane impedance }
        RecChannel[Settings.Capacity.GrealChan].ADCName := 'Greal' ;
        RecChannel[Settings.Capacity.GrealChan].ADCUnits := 'nS' ;
        Cap.GrealZero := RecChannel[Settings.Capacity.GrealChan].ADCZero ;
        Cap.GrealOffset :=  RecChannel[Settings.Capacity.GrealChan].ChannelOffset ;
        // Calculate scaling factor
        RecChannel[Settings.Capacity.GrealChan].ADCScale :=
            Abs(Main.SESLabIO.ADCVoltageRange) /
            (RecChannel[Settings.Capacity.GrealChan].ADCCalibrationFactor
             *(Main.SESLabIO.ADCMaxValue+1) ) ;
        if Settings.Capacity.GChannelsUseGainTelegraph then begin
           // Apply current gain factor from telegraph (if in use)
           RecChannel[Settings.Capacity.GrealChan].ADCScale :=
               RecChannel[Settings.Capacity.GrealChan].ADCScale /
               RecChannel[Settings.Capacity.ImChan].ADCAmplifierGain ;
           end ;
        Cap.GrealScale := RecChannel[Settings.Capacity.GrealChan].ADCScale*nSToSiemen ;
        //if Settings.Capacity.InvertGReal then Cap.GrealScale := -Cap.GrealScale ;

        { Imaginary component of cell membrane impedance }
        RecChannel[Settings.Capacity.GimagChan].ADCName := 'Gimag' ;
        RecChannel[Settings.Capacity.GimagChan].ADCUnits := 'nS' ;
        Cap.GimagZero := RecChannel[Settings.Capacity.GimagChan].ADCZero ;
        Cap.GimagOffset :=  RecChannel[Settings.Capacity.GimagChan].ChannelOffset ;
        // Calculate scaling factor (note uses telegraph gain from current channel)
        RecChannel[Settings.Capacity.GimagChan].ADCScale :=
            Abs(Main.SESLabIO.ADCVoltageRange) /
            (RecChannel[Settings.Capacity.GimagChan].ADCCalibrationFactor
             *(Main.SESLabIO.ADCMaxValue+1) ) ;
        if Settings.Capacity.GChannelsUseGainTelegraph then begin
           // Apply current gain factor from telegraph (if in use)
           RecChannel[Settings.Capacity.GimagChan].ADCScale :=
               RecChannel[Settings.Capacity.GimagChan].ADCScale /
               RecChannel[Settings.Capacity.ImChan].ADCAmplifierGain ;
           end ;
        Cap.GimagScale := RecChannel[Settings.Capacity.GimagChan].ADCScale*nSToSiemen ;
        //if Settings.Capacity.InvertGImag then Cap.GimagScale := -Cap.GimagScale ;

        { Set channel information }
        for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin
               scDisplay.ChanOffsets[ch] := RecChannel[ch].ChannelOffset ;
               scDisplay.ChanUnits[ch] := RecChannel[Ch].ADCUnits ;
               scDisplay.ChanName[ch] := RecChannel[Ch].ADCName ;
               scDisplay.ChanScale[ch] := Abs(RecChannel[Ch].ADCScale) ;
               scDisplay.HorizontalCursors[ch] := RecChannel[ch].ADCZero ;
               scDisplay.yMin[ch] := RecChannel[ch].yMin ;
               scDisplay.yMax[ch] := RecChannel[ch].yMax ;
               scDisplay.ChanVisible[ch] := RecChannel[ch].InUse ;
               end ;
        scDisplay.Invalidate ;
        if CDRFH.FileHandle >= 0 then SaveCDRHeader ( CDRFH ) ;
        Settings.NewCalculation := False ;

        end ;

     iScan := WriteBufs.OldestScan ;

     while iScan <> WriteBufs.LatestScan do begin

            {- Convert the input signals from binary to SI units - }
            Im := (ADC^[iScan+Cap.ImOffset] - Cap.ImZero)*Cap.ImScale ;
            if Settings.NewCalculation then Cap.ImSmoothed := Im ;
            Cap.ImSmoothed := 0.02*Im + (1.0 - 0.02)*Cap.ImSmoothed ;
            Cap.ImSmoothed := Im ;

            // Real conductance
            if Settings.Capacity.InvertGReal then
               ADC^[iScan+Cap.GRealOffset] := -(ADC^[iScan+Cap.GRealOffset] - Cap.GRealZero)
                                               + Cap.GRealZero ;
            GReal := (ADC^[iScan+Cap.GrealOffset] - Cap.GrealZero)*Cap.GrealScale ;

            // Imaginary conductance
            if Settings.Capacity.InvertGImag then
               ADC^[iScan+Cap.GimagOffset] := -(ADC^[iScan+Cap.GimagOffset] - Cap.GimagZero)
                                               + Cap.GimagZero ;
            GImag := (ADC^[iScan+Cap.GimagOffset] - Cap.GimagZero)*Cap.GimagScale ;

            if Settings.Capacity.CapacityCompensationInUse then begin
               if Gdc = Settings.Capacity.RSeriesComp then Break ;
               Rm := 1.0 /(Gdc - Settings.Capacity.RSeriesComp) ;
               A := Gdc*Settings.Capacity.RSeriesComp ;
               Omega := TwoPi*Settings.Capacity.Frequency ;
//                    *Settings.Capacity.CellCapacityComp*Rm ;
               //GReal := GReal + (Gdc*(1.0 + X*X*A))/(1.0 + X*X*A*A) ;
               //GImag := GImag + (Gdc*X*(1.0 - A))/(1.0 + X*X*A*A) ;

               A := Omega*Settings.Capacity.CellCapacityComp*Settings.Capacity.RSeriesComp ;
               Denom := 1. + A*A ;
               GReal := GReal +
                        ((Omega*Omega*Settings.Capacity.CellCapacityComp*
                        Settings.Capacity.CellCapacityComp*
                         Settings.Capacity.RSeriesComp ) / Denom ) ;

               GImag := GImag +
                        ((Omega*Settings.Capacity.CellCapacityComp) / Denom ) ;

               end ;


            Vm := (ADC^[iScan+Cap.VmOffset] - Cap.VmZero)*Cap.VmScale ;
            Cap.VmSmoothed := 0.02*Vm + (1.0 - 0.02)*Cap.VmSmoothed ;
            if Settings.NewCalculation then Cap.VmSmoothed := Vm ;
            //Cap.VmSmoothed := -0.05 ;

            Gm := 0.0 ;
            Gs := 0.0 ;
            Cm := 0.0 ;

            OK := True ;
	          if Cap.VmSmoothed <> Settings.Capacity.Vrev then begin
               Gdc := ABS(Cap.ImSmoothed/(Cap.VmSmoothed - Settings.Capacity.Vrev)) ;
               if GDC = 0.0 then OK := False ;
               end
            else OK := False ;

            if GReal <> Gdc then Gs := GReal + ((GImag*GImag)/(GReal-Gdc))
                        else OK := False ;

            if Gs <> Gdc then Gm := (Gdc*Gs)/(Gs - Gdc)
                         else OK := False ;

            if (GImag <> 0.0) and OK then
               Cm := (Gs*Gs*(GReal - Gdc))/((Gs - Gdc)*GImag*TwoPi*Settings.Capacity.Frequency) ;

            if Cap.GsOffset >= 0 then
               ADC^[iScan+Cap.GsOffset] := Round(Min(Max(Gs/Cap.GsScale,
                                           0.0),Main.SESLabIO.ADCMaxValue-1)) ;
            if Cap.GmOffset >= 0 then
               ADC^[iScan+Cap.GmOffset] := Round(Min(Max(Gm/Cap.GmScale,
                                           0.0),Main.SESLabIO.ADCMaxValue-1)) ;
            if Cap.CmOffset >= 0 then
               ADC^[iScan+Cap.CmOffset] := Round(Min(Max(Cm/Cap.CmScale,
                                           -Main.SESLabIO.ADCMaxValue),Main.SESLabIO.ADCMaxValue-1)) ;
            { Next block of channels }
            iScan := iScan + Main.SESLabIO.ADCNumChannels ;
            if iScan >= WriteBufs.NumSamples then
               iScan := iScan - WriteBufs.NumSamples ;

            Settings.NewCalculation := False ;

            Main.StatusBar.SimpleText := format('Vm(smoothed)=%.4g mV, G(dc)=%.4g nS',
            [Cap.VmSmoothed*1E3,Gdc*1E9])  ;

            end ;
     end ;


procedure TRecordFrm.StartSineWave ;
{ -----------------------
  Start sine wave output
  -----------------------}
const
    NumDACChannels = 2 ;
var
   DACdt : single ;
   MaxDACPoints : Integer ;
   i,nDACValues : Integer ;
   x,xStep : single ;
   sVPeak : Single ;
   sVHold : Array[0..1] of Single ;
   DACScale : Single ;

begin

     if not StimulatorAvailable then Exit ;

     // Stop D/A output
     Main.SESLabIO.DACStop ;

     { D/A update interval }
     Main.SESLabIO.DigitalStimulusEnabled := False ;

     MaxDACPoints := Main.SESLabIO.DACBufferLimit div (NumDACChannels) - 10 ;

     DacDT := 1.0/(100.0*Settings.Capacity.Frequency) ;
     DACdt := Max( DACdt, Main.SESLabIO.DACMinUpdateInterval ) ;
     Main.SESLabIO.DACUpdateInterval := DACdt ;
     DACdt := Main.SESLabIO.DACUpdateInterval ;

     nDacValues := Min( Round( (10.0/(DacDT*Settings.Capacity.Frequency))), MaxDACPoints) ;
     sVPeak := (Settings.Capacity.SineWaveRMS/0.707)*Settings.Vcommand[0].DivideFactor ;
     sVHold[0] := Settings.Vcommand[0].HoldingVoltage*Settings.Vcommand[0].DivideFactor ;
     sVHold[1] := Settings.Vcommand[1].HoldingVoltage*Settings.Vcommand[1].DivideFactor ;

     { D/A channel voltage -> bits scaling factors }
     DACScale := Main.SESLabIO.DACMaxValue/ Main.SESLabIO.DACVoltageRange ;

     x := 0.0 ;
     xStep := 2.0*Pi*Settings.Capacity.Frequency*DacDT ;
     for i := 0 to nDacValues-1 do begin
         // DAC channel 0
         DAC^[i*NumDACChannels] := Round((sVPeak*Sin(x) + sVHold[0])*DACScale) ;
         // Other channel held at holding potential
         DAC^[i*NumDACChannels+1] := Round(sVHold[1]*DACScale) ;
         x := x + xStep ;
         end ;

     Main.SESLabIO.DACNumChannels := NumDACChannels ;
     Main.SESLabIO.DACNumSamples := nDacValues ;

     Main.SESLabIO.DACRepeatWaveform := True ;
     Main.SESLabIO.DACStart ;

     end ;



procedure TRecordFrm.CalculateResistance ;
// --------------------------------
// Real-time resistance calculation
// --------------------------------
const
    EndMargin = 1E-3 ; //ms
    ResistanceStimFileName = 'RTest.sti' ;
var
    i,iScan : Integer ;
    Vm, Im, VStep, IStep, R, G : Single ;
begin

    // Initialise event detector
    if Settings.NewCalculation then begin

       ResistanceAnalysis.NumPulseSamples := 0 ;
       ResistanceAnalysis.NumHoldSamples := 0 ;
       ResistanceAnalysis.NumPulseStart := Round( (0.5*Settings.RTResistance.Duration)/
                                                      Main.SESLabIO.ADCSamplingInterval) ;
       ResistanceAnalysis.NumPulseEnd := Round( (Settings.RTResistance.Duration-EndMargin)/
                                                    Main.SESLabIO.ADCSamplingInterval) ;
       ResistanceAnalysis.NumHoldStart := Round( (0.5*Settings.RTResistance.Interval)/
                                                      Main.SESLabIO.ADCSamplingInterval) ;
       ResistanceAnalysis.NumHoldEnd := Round( (Settings.RTResistance.Interval-EndMargin)/
                                                    Main.SESLabIO.ADCSamplingInterval) ;

       if Settings.RTResistance.Amplitude >= 0.0 then ResistanceAnalysis.Polarity := 1
                                                 else ResistanceAnalysis.Polarity := -1 ;

       ResistanceAnalysis.Threshold := Abs(Settings.RTResistance.Amplitude*0.5) ;

       // Set voltage channel and scaling
       ResistanceAnalysis.VmChan := Settings.RTResistance.VmChannel ;
       if RecChannel[ResistanceAnalysis.VmChan].ADCUnits = 'mV' then
          ResistanceAnalysis.ScaleToVolts := 1E-3
       else ResistanceAnalysis.ScaleToVolts := 1.0 ;

       // Set current channel and scaling
       ResistanceAnalysis.ImChan := Settings.RTResistance.ImChannel ;
       if AnsiContainsText(RecChannel[ResistanceAnalysis.ImChan].ADCUnits,'pa') then
          ResistanceAnalysis.ScaleToAmps := 1E-12
       else if AnsiContainsText(RecChannel[ResistanceAnalysis.ImChan].ADCUnits,'na') then
          ResistanceAnalysis.ScaleToAmps := 1E-9
       else if AnsiContainsText(RecChannel[ResistanceAnalysis.ImChan].ADCUnits,'ua') then
          ResistanceAnalysis.ScaleToAmps := 1E-6
       else if AnsiContainsText(RecChannel[ResistanceAnalysis.ImChan].ADCUnits,'ma') then
          ResistanceAnalysis.ScaleToAmps := 1E-3
       else ResistanceAnalysis.ScaleToAmps := 1.0 ;

       ResistanceAnalysis.VPulseSum := 0.0 ;
       ResistanceAnalysis.IPulseSum := 0.0 ;
       ResistanceAnalysis.VHoldSum := 0.0 ;
       ResistanceAnalysis.IHoldSum := 0.0 ;

       // Create resistance stimulus pulse
       for i := 0 to High(Stimulator.Prog.Shape) do Stimulator.Prog.Shape[i] := wvNone ;
       Stimulator.Prog.Shape[0] := wvStep0 ;
       Stimulator.Prog.Amplitude[0] := Settings.RTResistance.Amplitude ;
       Stimulator.Prog.Delay[0] := Settings.RTResistance.Interval*0.1 ;
       Stimulator.Prog.Duration[0] := Settings.RTResistance.Duration ;
       Stimulator.Prog.RecordInterval := Settings.RTResistance.Interval ;
       Stimulator.Prog.NumSteps := 1 ;
       Stimulator.Prog.RepeatedStimulus := True ;
       Stimulator.Prog.NextProtocolFileName := '' ;

       // Save file to stim folder
       ActiveStimFileName := Settings.StimDirectory + ResistanceStimFileName ;
       if FileExists(ActiveStimFileName) then DeleteFile(PChar(ActiveStimFileName)) ;
       Stimulator.SaveProgram(ActiveStimFileName);

       // Request stimulus pulse to be started
       StimulatorStartRequired := True ;

       end ;

    // Open plotting window (if not already open)
    if not Main.FormExists( 'RecPlotFrm' ) then begin
       RecPlotFrm := TRecPlotFrm.Create(Self) ;
       RecPlotFrm.Width :=  Main.ClientWidth - RecordFrm.Left - RecordFrm.Width - 20 ;
       RecPlotFrm.Left := RecordFrm.Left + RecordFrm.Width + 2 ;
       RecPlotFrm.Top := 10 ;
       end ;

    iScan := WriteBufs.OldestScan ;
    while iScan <> WriteBufs.LatestScan do begin

          // Get current and voltage

          Vm := (ADC^[iScan+RecChannel[ResistanceAnalysis.VmChan].ChannelOffset] -
                RecChannel[ResistanceAnalysis.VmChan].ADCZero)*
                RecChannel[ResistanceAnalysis.VmChan].ADCScale*ResistanceAnalysis.ScaleToVolts ;
          Im := (ADC^[iScan+RecChannel[ResistanceAnalysis.ImChan].ChannelOffset] -
                RecChannel[ResistanceAnalysis.ImChan].ADCZero)*
                RecChannel[ResistanceAnalysis.ImChan].ADCScale*ResistanceAnalysis.ScaleToAmps ;

          // Add to holding or pulse averages

          if ((Vm-Settings.VCommand[0].HoldingVoltage)*ResistanceAnalysis.Polarity) >
             ResistanceAnalysis.Threshold then begin
             // Add to test pulse samples
             Inc(ResistanceAnalysis.NumPulseSamples) ;
             if (ResistanceAnalysis.NumPulseSamples >= ResistanceAnalysis.NumPulseStart) and
                (ResistanceAnalysis.NumPulseSamples <= ResistanceAnalysis.NumPulseEnd) then begin
                ResistanceAnalysis.VPulseSum := ResistanceAnalysis.VPulseSum + Vm ;
                ResistanceAnalysis.IPulseSum := ResistanceAnalysis.IPulseSum + Im ;
                end ;
             end
          else begin
             // Add to holding samples
             Inc(ResistanceAnalysis.NumHoldSamples) ;
             if (ResistanceAnalysis.NumHoldSamples >= ResistanceAnalysis.NumHoldStart) and
                (ResistanceAnalysis.NumHoldSamples <= ResistanceAnalysis.NumHoldEnd) then begin
                ResistanceAnalysis.VHoldSum := ResistanceAnalysis.VHoldSum + Vm ;
                ResistanceAnalysis.IHoldSum := ResistanceAnalysis.IHoldSum + Im ;
                end ;
             end ;

              //outputdebugString(PChar(format('%d %d ',[yDiff,Round(EventAnalysis.Baseline)]))) ;

          // Plot resistance (if available)
          if (ResistanceAnalysis.NumHoldSamples >= ResistanceAnalysis.NumHoldEnd) and
             (ResistanceAnalysis.NumPulseSamples >= ResistanceAnalysis.NumPulseEnd) then begin

             // Calculate current & voltage steps
             VStep := (ResistanceAnalysis.VPulseSum/
                       (ResistanceAnalysis.NumPulseEnd - ResistanceAnalysis.NumPulseStart + 1))
                       -
                      (ResistanceAnalysis.VHoldSum/
                       (ResistanceAnalysis.NumHoldEnd - ResistanceAnalysis.NumHoldStart + 1)) ;

             IStep := (ResistanceAnalysis.IPulseSum/
                       (ResistanceAnalysis.NumPulseEnd - ResistanceAnalysis.NumPulseStart + 1))
                       -
                      (ResistanceAnalysis.IHoldSum/
                       (ResistanceAnalysis.NumHoldEnd - ResistanceAnalysis.NumHoldStart + 1)) ;

             // Calculate conductance & resistance
             if Abs(VStep) > 1E-4 then G := IStep / VStep
                                  else G := 0.0 ;
             if G <> 0.0 then R := 1/G
                         else R := 0.0 ;

              // Update plot
              case Settings.RTResistance.Plot of
                   0 : RecPlotFrm.PlotCurrent( IStep ) ;
                   1 : RecPlotFrm.PlotVoltage( VStep ) ;
                   2 : RecPlotFrm.PlotResistance( R ) ;
                   3 : RecPlotFrm.PlotConductance( G ) ;
                   end ;

//Main.StatusBar.SimpleText := format('R=%.4g %.4g %d %d',[R, Vstep,ResistanceAnalysis.NumHoldSamples,
//               ResistanceAnalysis.NumPulseSamples]) ;
                              Inc(ii) ;

              ResistanceAnalysis.NumHoldSamples := 0 ;
              ResistanceAnalysis.NumPulseSamples := 0 ;
              ResistanceAnalysis.VPulseSum := 0.0 ;
              ResistanceAnalysis.VHoldSum := 0.0 ;
              ResistanceAnalysis.IPulseSum := 0.0 ;
              ResistanceAnalysis.IHoldSum := 0.0 ;
              end ;

          { Next block of channels }
          iScan := iScan + Main.SESLabIO.ADCNumChannels ;
          if iScan >= WriteBufs.NumSamples then
             iScan := iScan - WriteBufs.NumSamples ;

          end ;

     Settings.NewCalculation := False ;


     end ;



procedure TRecordFrm.DisplaySignalLevels(
          var Buf : Array of SmallInt ;    { A/D  data buffer }
          StartAt : Integer                { Start at sample }
          ) ;
{ -------------------------------------------------------
  Display signal levels for a block of input channels
  ------------------------------------------------------- }
var
   ch : Integer ;
   Value : single ;
begin

     for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin
         Value := (Buf[StartAt+RecChannel[ch].ChannelOffset] - RecChannel[ch].ADCZero)
                  * RecChannel[ch].ADCScale ;
   {      lbReadOut.Add( format('%s=%7.4g %s ',
                        [RecChannel[ch].ADCName,
                         Value,
                         RecChannel[ch].ADCUnits] )) ;}
         end ;
     {lbReadOut.Invalidate ;}
     end ;



procedure TRecordFrm.StopADCandDAC ;
{ ---------------------------------------------
  Shut down A/D, D/A and digital O/P subsystems
  ---------------------------------------------}
begin

     { Disable A/D }
     Main.SESLabIO.ADCStop ;

     { Disable D/A waveform generation }
     Main.SESLabIO.DACStop ;

     UpdateOutputs ;

     bStartStimulus.Enabled := True ;
     bStopStimulus.Enabled := False ;

     Timer.Enabled := False ;

     end ;


procedure TRecordFrm.bRecordClick(Sender: TObject);
{ -----------------------
  Start recording to file
  ----------------------- }
var
  TempChannel : TChannel ;
  ch : Integer ;
begin

     //
     if CDRFH.FileHandle < 0 then begin
        ShowMessage('RECORD: No data file! Select File/New to create a data file') ;
        Exit ;
        end ;

     // Copy data into file header
     if CDRFH.NumSamplesInFile > 0 then begin
        if (CDRFH.NumChannels <> Main.SESLabIO.ADCNumChannels) then begin
           ShowMessage('RECORD: No. of channels changed. Open a new data file to record!') ;
           Exit ;
           end ;
        if Abs(CDRFH.dt - Main.SESLabIO.ADCSamplingInterval) > 1E-6 then begin
           ShowMessage('RECORD: Sampling interval changed. Open a new data file to record!') ;
           Exit ;
           end ;
        if CDRFH.ADCVoltageRange <> Main.SESLabIO.ADCVoltageRange then begin
           ShowMessage('RECORD: A/D voltage range changed. Open a new data file to record!') ;
           Exit ;
           end ;
        end ;

     // Copy data into file header
     CDRFH.NumChannels := Main.SESLabIO.ADCNumChannels ;
     CDRFH.dt := Main.SESLabIO.ADCSamplingInterval ;
     CDRFH.ADCVoltageRange := Main.SESLabIO.ADCVoltageRange ;
     CdrFH.NumBytesInHeader := NumBytesInHeader ;

     bRecord.Enabled := False ;
     bStop.Enabled := True ;
     TDisplayPanel.Enabled := False ;
     bMark.Enabled := True ;
     bUpdateGain.Enabled := True ;

     ckFixedZeroLevels.Enabled := False ;

     { Update number of recording sweeps to be done }
     // Set no. of records to acquire if this first sweep of ext. triggered sweeps
     if (WriteBufs.NumRecordsToAcquire = 0) and rbExtTrigger.Checked then begin
        Settings.NumTriggerSweeps := Round(edNumTriggerSweeps.Value) ;
        WriteBufs.NumRecordsToAcquire := Settings.NumTriggerSweeps ;
        end ;

     { Number of bytes to be acquired from A/D converter }
     Settings.RecordDuration := edRecordDuration.Value ;
     WriteBufs.NumSamplesInFileRequired := CDRFH.NumSamplesInFile +
                                           Round((Settings.RecordDuration*Main.SESLabIO.ADCNumChannels)
                                                 /Settings.ADCSamplingInterval ) ;

     { Sweep count if in Free Run or Ext. Trigger mode }
     if rbExtTrigger.Checked then begin
        SweepStatus := format( 'Sweep %d/%d : ',
                                  [Settings.NumTriggerSweeps-WriteBufs.NumRecordsToAcquire+1,
                                   Settings.NumTriggerSweeps]
                                    ) ;
        end
     else SweepStatus := '' ;

     WriteBufs.TAcquired := 0.0 ;
     Main.StatusBar.SimpleText := SweepStatus + RecordingStatus + StimulusStatus ;

     WriteToLogFile( SweepStatus + format(
                    ' Started at %.4g s',[(CdrFH.NumSamplesInFile*CdrFH.dt)/
                                           CdrFH.NumChannels])  ) ;

//     { Position data file pointer at expected end of A/D data }
//     FileSeek( CdrFH.FileHandle,
//               (WriteBufs.NumSamplesInFileRequired*2) + CdrFH.NumBytesInHeader,
//               0 ) ;
//     if FileWrite(CdrFH.FileHandle,ADC^,1) <> 1 then
//       ShowMessage( ' Error : Cannot write to data file' ) ;
//     { Return to current end of data }

     FileSeek(CdrFH.FileHandle,0,2) ;

     // Update channel scaling factors in case amplifier gain has changed
     for ch := 0 to Min(Main.SESLabIO.ADCNumChannels,2)-1 do begin
         TempChannel := RecChannel[ch] ;
         Amplifier.GetChannelSettings( ch, TempChannel.ADCName,
                                           TempChannel.ADCUnits,
                                           TempChannel.ADCCalibrationFactor,
                                           TempChannel.ADCAmplifierGain ) ;
         if ((RecChannel[ch].ADCAmplifierGain <> TempChannel.ADCAmplifierGain) or
            (RecChannel[ch].ADCUnits <> TempChannel.ADCUnits)) and
            (CDRFH.NumSamplesInFile > 0) then begin
            // Exit if data in file and channel gain/units changed
            ShowMessage( 'Amplifier gain/mode changed! Open a new file before recording.') ;
            Exit ;
           end
         else RecChannel[ch] := TempChannel ;
         end ;

     // Copy recording channel settings to file channel settings
     for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do Channel[ch] := RecChannel[ch] ;
     
     // Update scaling factors
     Main.UpdateChannelScalingFactors ;

     DisplayLive( True ) ;

     WriteBufs.Recording := True ;

     // Stop A/D recording (to force a re-start)
     RestartADC ;

     RecordingStatus := format( 'Recording: 0/%.1f s (0 Kb) ',
                                [Settings.RecordDuration]) ;


     end;


procedure TRecordFrm.FormResize(Sender: TObject);
{ ------------------------------------------
  Adjust components when display is re-sized
  ------------------------------------------ }
begin

     ReadOutGrp.Height := Max( ClientHeight - ReadoutGrp.Top - 5, 2) ;
     ckFixedZeroLevels.Top := ReadOutGrp.ClientHeight - ckFixedZeroLevels.Height - 2 ;

     StatusGrp.Top := ClientHeight - StatusGrp.Height - 5 ;
     StatusGrp.Width := Max( ClientWidth - StatusGrp.Left - 5, 2) ;
     TDisplayPanel.Left := StatusGrp.Width - TDisplayPanel.Width - 2 ;

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

     // Adjust width of ident boxes to match display area
     edIdent.Width := scDisplay.Left + scDisplay.Width - edIdent.Left ;

     if not Initialised then Timer.Enabled := False
                        else Timer.Enabled := True ;

     end;


procedure TRecordFrm.bStopClick(Sender: TObject);
{ ---------------
  Stop recording
  --------------- }
begin
     { Set number of records to be acquired to zero }
     WriteBufs.NumRecordsToAcquire := 0 ;

     // Restore origin holding voltage
     UpdateOutputs ;

     { Stop recording to disk and shutdown A/D & D/A conversion }
     StopRecording ;

     // Stop A/D recording (to force a re-start)
     Main.SESLabIO.ADCStop ;

     end;


procedure TRecordFrm.FormClose(Sender: TObject; var Action: TCloseAction);
{ -----------------------------
  Save settings and close form
  ----------------------------- }
begin

     { Save data to file header }
     if CDRFH.FileHandle >= 0 then SaveCDRHeader( CdrFH ) ;

     // Close frequency plotting window if it is open
     if Main.FormExists( 'RecPlotFrm' ) then begin
        RecPlotFrm.Close  ;
        end ;

     { Shut down Lab. interface hardware ... essential to avoid program crash }
     StopADCandDAC ;

     Main.SetMenus ;



     Action := caFree ;

     Main.mnRecord.Enabled := True ;
     Timer.Enabled := False ;
     Initialised := False ;

     end;


procedure TRecordFrm.rbFreeRunClick(Sender: TObject);
{ ----------------------------
  Set trigger mode to free run
  ----------------------------}
begin
     Settings.TriggerMode := 'F' ;
     end;


procedure TRecordFrm.rbExtTriggerClick(Sender: TObject);
{ ------------------------------------
  Set trigger mode to External trigger
  ------------------------------------}
begin
     Settings.TriggerMode := 'E' ;
     end;


procedure TRecordFrm.FormDeactivate(Sender: TObject);
{ ---------------------------------------
  Actions to be taken if form loses focus
  ---------------------------------------}
begin
    
     { If we're recording give up (in disgust!!) }
    { bStop.Click ;}
     //Timer.Enabled := False ;
     Main.SetMenus ;
     end;


procedure TRecordFrm.edIdentKeyPress(Sender: TObject; var Key: Char);
{ -------------------------------
  Update identification text line
  -------------------------------}
begin
     { Only save to log file if recording is not in progress }
     if (bRecord.Enabled) and (key = chr(13)) then WriteToLogFile( CdrFH.IdentLine ) ;
     end;


procedure TRecordFrm.FormActivate(Sender: TObject);
{ --------------------------------------------
  Activities required when form becomes active
  --------------------------------------------}
begin


     // Stop seal test activity
     if Main.FormExists('SealTestFrm') then SealTestFrm.StopADCandDAC ;

     if Initialised then begin
        if not Amplifier.GainTelegraphAvailable[1] then
           edAmplifierGain.Value := RecChannel[0].ADCCalibrationFactor ;
        if not Amplifier.GainTelegraphAvailable[2] then
           edAmplifier2Gain.Value := RecChannel[3].ADCCalibrationFactor ;
        //if not WriteBufs.Recording then Main.SESLabIO.ADCStop ;
        end ;

     // Update holding voltage
     edVHold0.Value := Settings.VCommand[0].HoldingVoltage ;
     edVHold1.Value := Settings.VCommand[1].HoldingVoltage ;

     if not bStopStimulus.Enabled then Settings.UpdateOutputs := True ;

     Timer.Enabled := True ;

     end;


procedure TRecordFrm.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
{ --------------
  Function keys
  -------------}
begin
     case Key of
          { F1 starts recording }
          VK_F1 : if bRecord.enabled then bRecord.Click ;
          { F2 stops recording }
          VK_F2 : if bStop.enabled then bStop.click ;
          end ;
     end;


procedure TRecordFrm.edTDisplayKeyPress(Sender: TObject;
  var Key: Char);
{ -------------------------
  Change display duration
  ------------------------- }
begin
     if key = #13 then begin
        Settings.DisplayDuration := edTDisplay.Value ;
        // Stop A/D recording (to force a re-start)
        if not WriteBufs.Recording then RestartADC ;
        end ;
     end;


procedure TRecordFrm.edRecordDurationKeyPress(Sender: TObject;
  var Key: Char);
{ -------------------------
  Change recording duration
  ------------------------- }
begin
     if key = chr(13) then Settings.RecordDuration := edRecordDuration.Value ;
     end;


procedure TRecordFrm.bStartStimulusClick(Sender: TObject);
// -----------------------------------
// Request start of a stimulus program
// -----------------------------------
begin
     if cbStimProgram.ItemIndex > 0 then begin
        if not ckResistance.Checked then ActiveStimFileName := Settings.StimFileName ;
        StimulatorStartRequired := True ;
        end ;
     end;


procedure TRecordFrm.bSpecialClick(Sender: TObject);
{ -------------------------------------------------------
  Set channels/parameters for real-time computed channels
  ------------------------------------------------------- }
begin
     if Main.FormExists('SetCompFrm') then begin
        if SetCompFrm.WindowState = wsMinimized then SetCompFrm.WindowState := wsNormal ;
        SetCompFrm.BringToFront ;
        //SetCompFrm.SetFocus ;
        end
     else begin
        SetCompFrm := TSetCompFrm.Create(Self) ;
        SetCompFrm.Left := 15 ;
        SetCompFrm.Top := 15 ;
        end ;

     if ckFluorescence.Checked then SetCompFrm.SelectPage(0)
     else if ckCapacity.Checked then SetCompFrm.SelectPage(1)
     else if ckEventFrequency.Checked then SetCompFrm.SelectPage(2)
     else if ckResistance.Checked then SetCompFrm.SelectPage(3) ;

     end ;


procedure TRecordFrm.ckFluorescenceClick(Sender: TObject);
{ -------------------------------------------------------
  Enable/disable real-time fluorescence ratio computation
  ------------------------------------------------------- }
begin
     Settings.Fluorescence.InUse := ckFluorescence.Checked ;
     Main.SESLabIO.ADCStop ;
     end;


procedure TRecordFrm.scDisplayCursorChange(Sender: TObject);
var
   ch : Integer ;
begin
    if Main.SESLabIO.ADCActive then begin
       { Update zero level }
       ch := scDisplay.ActiveHorizontalCursor ;
       if ch >= 0 then RecChannel[ch].ADCZero := scDisplay.HorizontalCursors[ch] ;
       { Update vertical display magnification }
       for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin
           RecChannel[ch].yMin := scDisplay.YMin[ch] ;
           RecChannel[ch].yMax := scDisplay.YMax[ch] ;
           RecChannel[ch].InUse := scDisplay.ChanVisible[ch] ;
           end ;
       end ;

    end;


procedure TRecordFrm.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 TRecordFrm.ZoomOut(
          ChanNum : Integer ) ;
// ------------------------------------
// Magnify selected A/D channel display
// ------------------------------------
begin

     scDisplay.YZoom( ChanNum, 50.0 ) ;

     end ;


procedure TRecordFrm.ZoomIn( ChanNum : Integer ) ;
// ------------------------------------
// Reduce selected A/D channel display
// ------------------------------------
begin

     scDisplay.YZoom( ChanNum, -50.0 ) ;

     end ;


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


procedure TRecordFrm.ckCapacityClick(Sender: TObject);
{ -------------------------------------------------------
  Enable/disable real-time capacity computation
  ------------------------------------------------------- }
begin
     Settings.Capacity.InUse := ckCapacity.Checked ;
     if ckCapacity.Checked then ckFixedZeroLevels.Checked := True ;
     Main.SESLabIO.ADCStop ;
     end;


procedure TRecordFrm.ckFixedZeroLevelsClick(Sender: TObject);
// ---------------------------------------------------
// Force a re-start when fixed zero level mode changed
// ---------------------------------------------------
begin
     Main.SESLabIO.ADCStop ;
     
     end;


procedure TRecordFrm.UpdateOutputs ;
// ------------------------------
// Update D/A and digital outputs
// ------------------------------
var
   VDACS : Array[0..1] of Single ;
   i : Integer ;
begin

     // Update DACs
     for i := 0 to High(Settings.VCommand) do
         VDACS[i] := Settings.VCommand[i].HoldingVoltage *
                     Settings.VCommand[i].DivideFactor ;
     Main.SESLabIO.WriteDACS( VDACS, 2 ) ;

     // Ensure holding voltage boxes up to date
     edVHold0.Value := Settings.VCommand[0].HoldingVoltage ;
     edVHold1.Value := Settings.VCommand[1].HoldingVoltage ;

     // Update digital ports
     Main.SESLabIO.WriteDig( Settings.DigitalOutputs ) ;

     Settings.UpdateOutputs := False ;

     //outputdebugString(PChar(format('Write DAC0 %.4g ',[VDACS[0]]))) ;

     end ;


procedure TRecordFrm.edIdentChange(Sender: TObject);
 {-------------------------------------------
  Update identification string in file header
  -------------------------------------------}
begin
     { Update ident line if it is changed }
     CdrFH.IdentLine := edIdent.text ;
     if bRecord.Enabled then SaveCDRHeader(CdrFH) ;
     end;


procedure TRecordFrm.bMarkClick(Sender: TObject);
// ------------------------------
//  Add a text marker to the chart
// ------------------------------
var
     MarkerTime : Single ;
     TimeScale : Single ;
begin

     if MarkerList.Count < MaxMarkers then begin

          TimeScale := scDisplay.TScale/Settings.TScale ;

          MarkerTime := (scDisplay.NumPoints + scDisplay.XOffset)*TimeScale ;
          // Plot marker on chart
          MarkerList.AddObject( EdMarker.text, TObject(MarkerTime) ) ;
          scDisplay.AddMarker( Round(MarkerTime/TimeScale) - scDisplay.XOffset,
                               EdMarker.text );

          end ;
     edNumMarkers.text := format('%d',[MaxMarkers-MarkerList.Count]);
     end;


procedure TRecordFrm.edMarkerKeyPress(Sender: TObject; var Key: Char);
// -----------------------------------------
// Add marker to chart when <CR> key pressed
// -----------------------------------------
begin
     if (Key = #13) and WriteBufs.Recording  then bMark.Click ;
     end;

procedure TRecordFrm.rbExtTriggerHighClick(Sender: TObject);
// ----------------------------------------
// Ext. Trigger Active High option selected
// ----------------------------------------
begin

     Settings.ExternalTriggerActiveHigh := rbExtTriggerHigh.Checked ;
     Main.SESLabIO.ADCExternalTriggerActiveHigh := Settings.ExternalTriggerActiveHigh ;
     rbExtTriggerHigh.Checked := Main.SESLabIO.ADCExternalTriggerActiveHigh ;

     end;


procedure TRecordFrm.rbExttriggerLowClick(Sender: TObject);
// ----------------------------------------
// Ext. Trigger Active Low option selected
// ----------------------------------------
begin
     Settings.ExternalTriggerActiveHigh := rbExtTriggerHigh.Checked ;
     Main.SESLabIO.ADCExternalTriggerActiveHigh := Settings.ExternalTriggerActiveHigh ;
     rbExtTriggerHigh.Checked := Main.SESLabIO.ADCExternalTriggerActiveHigh ;
     end;


procedure TRecordFrm.bUpdateGainClick(Sender: TObject);
//  ----------------------------------
// Force re-start to update Ch.0 gain
// ----------------------------------
begin
     if bRecord.Enabled and (CDRFH.NumSamplesInFile = 0) then begin
        ReadUserEnteredGain := True ;
        Settings.NewCalculation := True ;
        RestartADC ;
        end ;
     end;


procedure TRecordFrm.bStopStimulusClick(Sender: TObject);
{ ------------------------
  Toggle stimulator Off
  ------------------------ }
begin
     StopStimulator ;
     end;


procedure TRecordFrm.edAmplifierGainKeyPress(Sender: TObject;
  var Key: Char);
// -------------------------------------------
// Patch clamp amplifier gain changed by user
// -------------------------------------------
begin
     if Key = #13 then begin
        if bRecord.Enabled and (CDRFH.NumSamplesInFile = 0) then begin
           ReadUserEnteredGain := True ;
           RestartADC ;
           end ;
        end ;
     end;


procedure TRecordFrm.bTDisplayHalfClick(Sender: TObject);
// -------------------------------
// Halve display window duration
// -------------------------------
begin
     edTDisplay.Value := edTDisplay.Value*0.5 ;
     Settings.DisplayDuration := edTDisplay.Value ;
     if not WriteBufs.Recording then RestartADC ;
     end;


procedure TRecordFrm.bTDisplayDoubleClick(Sender: TObject);
// -------------------------------
// Double display window duration
// -------------------------------
begin
     edTDisplay.Value := edTDisplay.Value*2.0 ;
     Settings.DisplayDuration := edTDisplay.Value ;
     if not WriteBufs.Recording then RestartADC ;
     end;


procedure TRecordFrm.rbTDisplayUnitsMSecsClick(Sender: TObject);
// --------------------------------------
// Set time readout units to milliseconds
// --------------------------------------
begin
    Settings.TUnits := 'ms' ;
    Settings.TSCale := 1000.0 ;
    edTDisplay.Scale := Settings.TSCale ;
    edTDisplay.Units := Settings.TUnits ;
    Main.SESLabIO.ADCStop ;
    end;


procedure TRecordFrm.rbTDisplayUnitsSecsClick(Sender: TObject);
// --------------------------------------
// Set time readout units to milliseconds
// --------------------------------------
begin
    Settings.TUnits := 's' ;
    Settings.TSCale := 1.0 ;
    edTDisplay.Scale := Settings.TSCale ;
    edTDisplay.Units := Settings.TUnits ;
    Main.SESLabIO.ADCStop ;
    end;

procedure TRecordFrm.rbTDisplayUnitsMinsClick(Sender: TObject);
// --------------------------------------
// Set time readout units to minutes
// --------------------------------------
begin
    Settings.TUnits := 'min' ;
    Settings.TSCale := 1./60.0 ;
    edTDisplay.Scale := Settings.TSCale ;
    edTDisplay.Units := Settings.TUnits ;
    Main.SESLabIO.ADCStop ;
    end;

procedure TRecordFrm.edVHold0KeyPress(Sender: TObject; var Key: Char);
{ -------------------------------------
  Update voltage clamp holding voltage
  ------------------------------------- }
begin
     if (Key = #13) and bRecord.Enabled then begin
        Settings.VCommand[0].HoldingVoltage := edVHold0.Value ;
        if bStopStimulus.Enabled then begin
           // Re-start stimulator
           StopStimulator ;
           Stimulator.LoadProgram( ActiveStimFileName ) ;
           StartStimulator ;
           end
        else UpdateOutputs ;
        end ;
     end;

procedure TRecordFrm.edVHold1KeyPress(Sender: TObject; var Key: Char);
{ -------------------------------------
  Update voltage clamp holding voltage
  ------------------------------------- }
begin
     if (Key = #13) and bRecord.Enabled then begin
        Settings.VCommand[1].HoldingVoltage := edVHold1.Value ;
        if bStopStimulus.Enabled then begin
           // Re-start stimulator
           StopStimulator ;
           Stimulator.LoadProgram( ActiveStimFileName ) ;
           StartStimulator ;
           end
        else UpdateOutputs ;
        end ;
     end;


procedure TRecordFrm.UpdateStimProgramList ;
// --------------------------------
// Update list of stimulus programs
// --------------------------------
var
    NamePart : String ;
begin

     // Load list of stimulus programs
     Stimulator.CreateProgramList( cbStimProgram ) ;
     if Settings.StimFileName <> '' then begin
        // Find name within list
        NamePart := AnsiReplaceText(ExtractFileName( Settings.StimFileName),'.sti', '' ) ;
        cbStimProgram.ItemIndex := cbStimProgram.Items.IndexOf(NamePart) ;
        end
     else begin
        cbStimProgram.ItemIndex := 0 ;
        end ;

     if cbStimProgram.ItemIndex <= 0 then begin
        bStartStimulus.Enabled := False ;
        bStopStimulus.Enabled := False ;
        end
     else begin
        bStartStimulus.Enabled := StimulatorAvailable ;
        bStopStimulus.Enabled := False ;
        end ;

     end ;


procedure TRecordFrm.ReStartADC ;
//
// Stop ADC and DAC to force re-start
begin

     Main.SESLabIO.ADCStop ;
     if Main.SESLabIO.DACActive then begin
        Main.SESLabIO.DACStop ;
        StimulatorStartRequired := True ;
        end ;

    end ;

procedure TRecordFrm.cbStimProgramChange(Sender: TObject);
// ------------------------
// Stimulus program changed
// ------------------------
begin
     if cbStimProgram.ItemIndex > 0 then begin
        bStartStimulus.Enabled := True ;
        Settings.StimFileName := Settings.StimDirectory + cbStimProgram.Text + '.sti' ;
        end
     else bStartStimulus.Enabled := False ;

     end;

procedure TRecordFrm.StartRecording ;
// -------------------------------
// Start recording (external call)
// -------------------------------
begin
     bRecord.Click ;
     end ;

function TRecordFrm.GetRecordDuration : Single ;
begin
    Result := edRecordDuration.Value ;
    end ;

procedure TRecordFrm.SetRecordDuration( Value : Single ) ;
begin
    Settings.RecordDuration := Value ;
    edRecordDuration.Value := Value ;
    end ;

function TRecordFrm.GetStimulusProtocol : String ;
// -------------------------------------------
// Return currently selected stimulus protocol
// -------------------------------------------
begin
    Result := cbStimProgram.Text ;
    end ;


procedure TRecordFrm.SetStimulusProtocol( Value : String ) ;
// ----------------------
// Set stimulus protocol
// ----------------------
var
    idx : Integer ;
begin

    idx := cbStimProgram.Items.IndexOf(Value) ;
    if (idx > 0) and (idx < cbStimProgram.Items.Count) then begin
       cbStimProgram.ItemIndex := idx ;
       Settings.StimFileName := Settings.StimDirectory + cbStimProgram.Text + '.sti' ;
       end
    else begin
       cbStimProgram.ItemIndex := 0 ;
       Settings.StimFileName := '' ;
       end ;

    end ;

function TRecordFrm.GetTriggerMode : Integer ;
// --------------------------------------------
// Return trigger mode (0=free run, 1=external)
// --------------------------------------------
begin
    if Settings.TriggerMode = 'F' then Result := 0
                                  else Result := 1 ;
    end ;


procedure TRecordFrm.SetTriggerMode( Value : Integer ) ;
// --------------------------------------------
// Set trigger mode (0=free run, 1=external)
// --------------------------------------------
begin
    if Value = 0 then begin
       Settings.TriggerMode := 'F' ;
       rbFreeRun.Checked := True ;
       end
    else begin
       Settings.TriggerMode := 'E' ;
       rbExtTrigger.Checked := True ;
       end ;
    end ;


function TRecordFrm.GetNumTriggerSweeps : Integer ;
// --------------------------------------------------
// Return no. of externally triggered sweeps required
// --------------------------------------------------
begin
    Result := Settings.NumTriggerSweeps ;
    end ;


procedure TRecordFrm.SetNumTriggerSweeps( Value : Integer ) ;
// --------------------------------------------
// Set no. of externally triggered sweeps required
// --------------------------------------------
begin
    Settings.NumTriggerSweeps :=  Round(Value) ;
    edNumTriggerSweeps.Value := Settings.NumTriggerSweeps ;
    end ;


function TRecordFrm.GetRunning : Boolean ;
// -----------------------------------------
// Return TRUE if digitising analogue signal
// -----------------------------------------
begin
    Result := Timer.Enabled ;
    end ;


function TRecordFrm.GetRecording : Boolean ;
// -----------------------------------------
// Return TRUE if recording analogue signal
// -----------------------------------------
begin
    Result := bStop.Enabled ;
    end ;



procedure TRecordFrm.edNumTriggerSweepsKeyPress(Sender: TObject;
  var Key: Char);
// --------------------------------------
// No. of external trigger sweeps changed
// --------------------------------------
begin
     if Key = #13 then Settings.NumTriggerSweeps := Round(edNumTriggerSweeps.Value)  ;
     end;

procedure TRecordFrm.ckEventFrequencyClick(Sender: TObject);
{ -------------------------------------------------------
  Enable/disable real-time fluorescence ratio computation
  ------------------------------------------------------- }
begin
     Settings.RTEventAnalysis.InUse := ckEventFrequency.Checked ;
     Settings.NewCalculation := True ;
     end;

procedure TRecordFrm.ckResistanceClick(Sender: TObject);
// -------------------------------------------------------
//  Enable/disable real-time resistance computation
//  ------------------------------------------------------- }
begin
     Settings.RTResistance.InUse := ckResistance.Checked ;
     Settings.NewCalculation := True ;
     if not ckResistance.Checked then StopStimulator ;
     end;

Initialization

end.
