unit Rec;
{ ==============================================================================
  WinWCP - Analogue Recording module (c) J. Dempster 1996, All Rights Reserved.
  8/9/97 ... Event detector Pretrigger % can now be set by user V1.7b
  31/5/98 ... GetInterfaceInfo put into Create method to force initialisation
              of lab. interface before timed events start
              DAC buffer now locked into memory to fix intermittent 0V glitches
              between cycles of D/A waveforms
  14/8/99 ... 32 bit version
  4/9/99 ... Display grid added
  11/12/99 ... TimeStart changed Settings.TimeRecordingStarted
  20/12/01 ... D/A and digital default values now set using Main.WriteOutputPorts (V3.2.1)
  17/3/02 .... Empty buffers now filled with pairs of positive/negative empty flags
               to avoid hang ups when buffer was filled with A/D values equal
               to empty flag.
               Channel Units/Div scaling information added to display
  15.7.02 .... Voltage pulse timing procedures moved to SESLABIO
  13.2.03 .... Display time calibration now correctly updated when sweep duration
               changes occur while in storage mode
  16.4.03 .... Sweep now terminates correctly when A/D input = emptyflag
  24.6.03 ... No. of display grid lines can be changed
  6.9.03 .... Erase screen button enabled
  18.03.04 .. A/D resolution now checked for compatibility with data in file
              before form is opened
  30.07.04 .. StartRecording & StopRecording states now replaced with
              StartSweep & StopSweep procedures. Recording sweeps can now
              be acquired at 10 sweeps/second
  02.08.04 .. ADCDisplay buffer added to ensure trace remains displayed correctly
              between sweeps
  04.08.04 .. Channel calibration factor and units now updated automatically
              from Amplifier module as well as gain. Switches between current & voltage
              clamp mode can be handled (UpdateSettings)
  04.05.06 .. Storage display mode now working again
  25.07.06 Recording channel settings now stored in RecChannel (global.pas)
  03.07.06 .. NumChannels, NumSamples, ADCVoltageIndex & RecordDuration now stored in settings (not RawFH)
              EndOfSweepCount now used to detect completion of sweep
  19.08.06 .. Storage display mode now working again
  21.08.06 .. Record time stamp now derived from master Windows tick count
              (rather than stimulus tick counter)
  17.09.06 .. .ZoomOut now sets RecChannel YMin/YMax properties
  29.10.07 .. Stimulus waveforms now always recreated before each sweep
              (Previously waveform was not recreated when pulse were repeated.
               which caused duration errors with NIDAQ-MX and repeated waveforms)
  28.11.07 .. Stimulus program selected when recording started now restored at stop of recording
              Stimulus program name now included in status bar
  08.02.08 .. Bug in event detector which produced incorrect sampling intervals
              and number of samples / record fixed
  05.08.08 .. Now possible to magnify display on X axis as well as Y
  16.09.08 .. Leak waveform now re-created in leak mode to avoid different
              leak pulse duration
  11.02.09 .. Stimulus protocol folder can now be set by user.
  13.05.09 .. Empty flags now replaced by last recorded samplewhen recording sweep
              is prematurely terminated bu user
  ==============================================================================}
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, ExtCtrls, Shared, FileIO,
  maths, math, ValEdit, ScopeDisplay, Global, SESLabIO, mmsystem, ValidatedEdit ;

type
  TState = ( Idle, SweepInProgress ) ;
  TRecordFrm = class(TForm)
    ControlGrp: TGroupBox;
    bRecord: TButton;
    bStop: TButton;
    TriggerGrp: TGroupBox;
    rbFreeRun: TRadioButton;
    rbExtTrigger: TRadioButton;
    rbEventDetector: TRadioButton;
    rbPulseProgram: TRadioButton;
    cbPulseProgram: TComboBox;
    ckSaveRecords: TCheckBox;
    Timer: TTimer;
    DisplayGrp: TGroupBox;
    edStatus: TEdit;
    edIdent: TEdit;
    Label4: TLabel;
    TimerGrp: TGroupBox;
    edTimeOfDay: TEdit;
    bResetTimer: TButton;
    Bevel1: TBevel;
    Label3: TLabel;
    scDisplay: TScopeDisplay;
    edRecordsRequired: TValidatedEdit;
    edAmplifierGain: TValidatedEdit;
    bErase: TButton;
    ExtTriggerGrp: TGroupBox;
    rbExtTriggerHigh: TRadioButton;
    rbExttriggerLow: TRadioButton;
    Bevel2: TBevel;
    Bevel3: TBevel;
    GroupBox1: TGroupBox;
    lbAmplifier: TLabel;
    Amplifier2Panel: TPanel;
    lbAmplifier2: TLabel;
    edAmplifier2Gain: TValidatedEdit;
    GroupBox2: TGroupBox;
    Label1: TLabel;
    Label2: TLabel;
    Label5: TLabel;
    cbDetectChannel: TComboBox;
    edDetectionThreshold: TValidatedEdit;
    edPreTrigger: TValidatedEdit;
    ckFixZeroLevels: TCheckBox;
    bOpenOLAWindow: TButton;
    bCloseOLAWindow: TButton;
    edMarker: TEdit;
    bAddMarker: TButton;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Bevel4: TBevel;
    bSetStimFolder: TButton;
    procedure TimerTimer(Sender: TObject);
    procedure bRecordClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure bStopClick(Sender: TObject);
    procedure cbPulseProgramChange(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure bResetTimerClick(Sender: TObject);
    procedure rbFreeRunClick(Sender: TObject);
    procedure rbExtTriggerClick(Sender: TObject);
    procedure rbPulseProgramClick(Sender: TObject);
    procedure rbEventDetectorClick(Sender: TObject);
    procedure EdDetectionThresholdKeyPress(Sender: TObject; var Key: Char);
    procedure FormDeactivate(Sender: TObject);
    procedure edIdentKeyPress(Sender: TObject; var Key: Char);
    procedure FormShow(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure bEraseClick(Sender: TObject);
    procedure edPreTriggerKeyPress(Sender: TObject; var Key: Char);
    procedure cbDetectChannelChange(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure scDisplayCursorChange(Sender: TObject);
    procedure rbExtTriggerHighClick(Sender: TObject);
    procedure rbExttriggerLowClick(Sender: TObject);
    procedure ckFixZeroLevelsClick(Sender: TObject);
    procedure edAmplifierGainKeyPress(Sender: TObject; var Key: Char);
    procedure FormActivate(Sender: TObject);
    procedure bOpenOLAWindowClick(Sender: TObject);
    procedure bCloseOLAWindowClick(Sender: TObject);
    procedure bAddMarkerClick(Sender: TObject);
    procedure bSetStimFolderClick(Sender: TObject);
  private
    { Private declarations }

    NewAmplifierGainValue : Boolean ;
    SetAmplifierGainReadout : Boolean ;

    C0Cursor : Integer ;
    C1Cursor : Integer ;
    C2Cursor : Integer ;

    StartingPulseProgramIndex : Integer ; // Pulse program index at start of recording

    procedure SaveSweep ;
    procedure InitialiseEventDetector ;
    procedure DetectEvent ;
    procedure StartVoltageProgram ;
    procedure RestoreIdleState ;

    procedure UpdateStopwatch ;
    procedure ShutDownLabInterface ;
    procedure MainMenus( Enabled : Boolean ) ;
    procedure HeapBuffers( Operation : THeapBufferOp ) ;
    procedure UpdateChannelSettings ;
    procedure UpdateChannelScalingFactors( var RH : TRecHeader ) ;
    procedure UpdateAmplifierGain ;
    procedure SetTriggerMode( Value : String ) ;
    function GetTriggerMode : String ;
    function GetStimulusProtocol : String ;
    procedure SetStimulusProtocol( Value : String ) ;
    function GetRunning : Boolean ;
    function GetRecording : Boolean ;

  public
    { Public declarations }
    TimerBusy : boolean ;
    State : TState ;   // Current operational state of module
    procedure NewFile ;
    procedure SetStoreMode( StorageMode : Boolean ) ;
    procedure ChangeDisplayGrid ;
    procedure ZoomOutAll ;
    procedure ZoomIn( ChanNum : Integer ) ;
    procedure ZoomOut( ChanNum : Integer ) ;

    procedure StartSweep ;
    procedure StopSweep ;


    procedure UpdateStimulusProtocolList ;

    procedure StartRecording ;
    procedure StopRecording ;

    property TriggerMode : String read GetTriggerMode write SetTriggerMode ;
    property StimulusProtocol : String
             read GetStimulusProtocol write SetStimulusProtocol ;
    property Running : Boolean read GetRunning ;
    property Recording : Boolean read GetRecording ;


  end;



var
  RecordFrm: TRecordFrm;


implementation

{$R *.DFM}

uses MDiform, AmpModule, StimModule , RecPlotUnit, Sealtest;
type
    TDet = record
         StartAt : Integer ;
         EndAt : Integer ;
         PreTriggerPoints : Integer ;
         Baseline : Integer ;
         Threshold : Integer ;
         Polarity : Integer ;
         Pointer : Integer ;
         EndOfBuf : Integer ;
         Chan : Integer ;
         LastChan : Integer ;
         ChanSelected : Integer ;
         EventDetected : boolean ;
         TimeDetected : single ;
         BufferCycles : Integer ;
         end ;


    TStopwatch = record
               ElapsedTime : TDateTime ;
               Hour : Word ;
               Minute : Word ;
               Second : Word ;
               MSec : Word ;
               TickStart : single ;
               OldSeconds : single ;
               end ;
TCursors = record
         Base : Array[0..WCPChannelLimit] of Integer ;
         end ;

const

     StimDACChannel = 0 ;
     SyncDACChannel = 1 ;
     NumDACChannels = 2 ;

var
  ADC : PSmallIntArray ;
  DAC : PSmallIntArray ;
  ADCCopy : PSmallIntArray ;
  ADCDetected : PSmallIntArray ;
  ADCCircular : PSmallIntArray ;
  ADCDisplay : PSmallIntArray ;
  Sum : ^TSmallIntArray ; { Summation buffer used to average LEAK records }
  DigBuf : PSmallIntArray ; { Digital port output array }

  Det : TDet ;

  EndOfBuf : Integer ;
  RH : TRecHeader ;
  NumRecordsDone,EndAtRecord : Integer ;
  NextBlock : Integer ;
  DIGActive : Boolean ; { Digital port sub-system active }
  NewSweep : Boolean ;
  InLeakMode : boolean ;    { Indicates that LEAK records are currently
                              being collected }
  LeakCollected : boolean ; { Indicates the LEAK records have been collected
                              for the current group of records }
  GroupNumber : Integer ; { Group of TEST and LEAK records produced by same
                            voltage program waveform }
  OldStepCounter : Integer ; { Previous voltage program step number }
  Stopwatch : TStopwatch ;
  EndOfSweep : boolean ;
  EndOfSweepCount : Integer ;
  AbortRecording : boolean ;
  EraseScreen : boolean ;
  BuffersAllocated : boolean ;{ Indicates if memory buffers have been allocated }
  NumBlocksDone : Integer ;
  Cursors : TCursors ;
  EmptyFlag : Integer ;

procedure TRecordFrm.HeapBuffers( Operation : THeapBufferOp ) ;
{ -----------------------------------------------
  Allocate/deallocation dynamic buffers from heap
  -----------------------------------------------}
begin
     case Operation of
          Allocate : begin
             if not BuffersAllocated then begin
                { Create A/D and D/A data buffers }
                New(ADCCopy) ;
                New(ADCDetected) ;
                New(ADCDisplay) ;
                New(Sum) ;
                BuffersAllocated := True ;
                end ;
             end ;
          Deallocate : begin
             if BuffersAllocated then begin
                Dispose(ADCCopy) ;
                Dispose(ADCDetected) ;
                Dispose(ADCDisplay) ;
                Dispose(Sum) ;
                { Create buffers for event detection procedures }
                BuffersAllocated := False ;
                end ;
             end ;
          end ;
     end ;


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

     { Make sure event timer is off }
     Timer.Enabled := False ;

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



     { Allocate memory buffers }

     HeapBuffers( Allocate ) ;

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

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

     { Set size of window }
     ClientHeight := TimerGrp.Top + TimerGrp.Height + 5 ;
     ClientWidth := scDisplay.Width  + 5 ;
     edIdent.text := RawFH.IdentLine ;
     RH.dt := Main.SESLabIO.ADCSamplingInterval ;
     DigActive := False ;
     EraseScreen := False ;

     { Set trigger mode }
     if Main.SESLabIO.LabInterfaceType = Triton then begin
        Settings.TriggerMode := 'P' ;
        rbExtTrigger.Enabled := False ;
        rbEventDetector.Enabled := False ;
        rbFreeRun.Enabled := False ;
        end ;

     if Settings.TriggerMode = 'P' then rbPulseProgram.checked := True
     else if Settings.TriggerMode = 'E' then rbExtTrigger.checked := True
     else if Settings.TriggerMode = 'D' then rbEventDetector.checked := True
     else rbFreeRun.checked := True ;
     // Set external trigger polarity
     Main.SESLabIO.ADCExternalTriggerActiveHigh := Settings.ExternalTriggerActiveHigh ;
     rbExtTriggerHigh.Checked := Main.SESLabIO.ADCExternalTriggerActiveHigh ;

     { Set event detector }
     edDetectionThreshold.Value := Settings.EventDetector.Threshold ;
     edPreTrigger.Value := Settings.EventDetector.PreTrigger ;

     { Fill combo box with list of available command voltage programs }
     Stimulator.CreateProgramList( cbPulseProgram ) ;
     if Settings.VProgramFileName <> '' then
        cbPulseProgram.ItemIndex := cbPulseProgram.Items.IndexOf(
                                 ExtractFileNameOnly(Settings.VProgramFileName))
     else cbPulseProgram.ItemIndex := 0 ;
     OldStepCounter := -1 ;

     { Force an update to command voltage and digital port settings }
     Settings.UpdateOutputs := True ;

     { Initialise stopwatch }
     Stopwatch.TickStart := GetTickCount ;
     Stopwatch.OldSeconds := 0. ;

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

     edRecordsRequired.Value := Settings.NumRecordsRequired ;
     edStatus.font.color := clBlack ;
     TimerBusy := False ;
     State := Idle ;
     EndofSweep := False ;
     NewAmplifierGainValue := False ;
     SetAmplifierGainReadout := True ;
     StartingPulseProgramIndex := 0 ;

     NewFile ;

     Timer.Enabled := True ;

     end ;

procedure TRecordFrm.UpdateStimulusProtocolList ;
// ----------------------------------
// Update list of stimulus protocols
// ----------------------------------
begin
     { Fill combo box with list of available command voltage programs }
     Stimulator.CreateProgramList( cbPulseProgram ) ;
     if Settings.VProgramFileName <> '' then
        cbPulseProgram.ItemIndex := cbPulseProgram.Items.IndexOf(
                                 ExtractFileNameOnly(Settings.VProgramFileName))
     else cbPulseProgram.ItemIndex := 0 ;
     end ;


procedure TRecordFrm.NewFile ;
{ -----------------------------------------------
  Re-initialise display when data file is changed
  ----------------------------------------------- }
var
   i,ch : Integer ;
begin

     // Update number of channels in event detector channel selector
     cbDetectChannel.items := ChannelNames ;
     cbDetectChannel.ItemIndex := IntLimitTo(Settings.EventDetector.Channel,
                                  0,Settings.NumChannels-1) ;

     { Fill combo box with list of available command voltage programs }
     Stimulator.CreateProgramList( cbPulseProgram ) ;
     if Settings.VProgramFileName <> '' then
        cbPulseProgram.ItemIndex := cbPulseProgram.Items.IndexOf(
                                    ExtractFileNameOnly(Settings.VProgramFileName))
     else cbPulseProgram.ItemIndex := 0 ;

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

     { Set up the number of channels/points in the 'scope display }
     scDisplay.MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     scDisplay.MinADCValue := Main.SESLabIO.ADCMinValue ;
     scDisplay.DisplayGrid := Settings.DisplayGrid ;
     scDisplay.NumHorizontalGridLines := Settings.NumHorizontalGridLines ;
     scDisplay.NumVerticalGridLines := Settings.NumVerticalGridLines ;

     scDisplay.MaxPoints := Settings.NumSamples ;
     scDisplay.xMin := 0 ;
     scDisplay.xMax := Settings.NumSamples - 1 ;
     scDisplay.NumPoints := 0 ;
     scDisplay.NumChannels := Settings.NumChannels ;

     {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] := RecChannel[ch].ADCScale ;
         scDisplay.yMin[ch] := RecChannel[Ch].yMin ;
         scDisplay.yMax[ch] := RecChannel[Ch].yMax ;
         scDisplay.ChanVisible[ch] := True ;
         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,
                             Settings.Colors.Cursors,
                             True,
                             'z' ) ;
         scDisplay.HorizontalCursors[ch] := RecChannel[ch].ADCZero ;
         end ;

     scDisplay.TScale := Main.SESLabIO.ADCSamplingInterval*Settings.TScale ;
     scDisplay.TFormat := '%.5g ' + Settings.TUnits ;

     SetStoreMode(Main.mnStoreTraces.Checked) ;

     { Set A/D buffer to zero }
     EndofBuf := (Settings.NumChannels*Settings.NumSamples) - 1;
     for i := 0 to EndOfBuf do ADC^[i] := 0 ;

     scDisplay.SetDataBuf( ADCDisplay ) ;

     { Make sure event detector channel is within valid limits }
     cbDetectChannel.items := ChannelNames ;
     cbDetectChannel.ItemIndex := IntLimitTo(Settings.EventDetector.Channel,
                                  0,Settings.NumChannels-1) ;

     end ;


procedure TRecordFrm.TimerTimer(Sender: TObject);
{ ---------------------
  Timed Event scheduler
  ---------------------}
var
   i,i0,i1 : Integer ;
   Done : Boolean ;
begin

     if not TimerBusy then begin

        TimerBusy := True ;

        Case State of

          { **** Update display during recording sweep **** }
          SweepInProgress : Begin

               { Get new A/D samples }
               Main.SESLabIO.ADCBufferRefresh ;

               { Check event detection buffer for signal }
               if rbEventDetector.checked then DetectEvent ;

               { Determine how many samples have been acquired }
               Done := False ;
               if NextBlock > EndOfBuf then Done := True ;
               While not Done do begin
                   if Odd(Nextblock) then begin
                      // End of multi-channel sample block is odd
                      if (ADC^[NextBlock] = -EmptyFlag) and
                         (ADC^[NextBlock-1] = EmptyFlag) then Done := True
                      else begin
                         i0 := NumBlocksDone*Settings.NumChannels ;
                         i1 :=  i0 + Settings.NumChannels - 1 ;
                         for i := i0 to i1 do ADCDisplay^[i] := ADC^[i] ;
                         Inc(NumBlocksDone) ;
                         NextBlock := NextBlock + Settings.NumChannels ;
                         end ;
                      end
                   else begin
                      // End of multi-channel sample block is even
                      if (ADC^[NextBlock] = EmptyFlag) and
                         (ADC^[NextBlock-1] = -EmptyFlag) then Done := True
                      else begin
                         i0 := NumBlocksDone*Settings.NumChannels ;
                         i1 :=  i0 + Settings.NumChannels - 1 ;
                         for i := i0 to i1 do ADCDisplay^[i] := ADC^[i] ;
                         Inc(NumBlocksDone) ;
                         NextBlock := NextBlock + Settings.NumChannels ;
                         end ;
                      end ;
                   // Exit loop if at end of buffer
                   if NextBlock > EndOfBuf then Done := True ;
                   end ;

               // Determine if all samples have been acquired
               if ADC[EndOfBuf] <> -EmptyFlag then Inc(EndOfSweepCount) ;
               if ADC[EndOfBuf-1] <> EmptyFlag then Inc(EndOfSweepCount) ;
               if EndOfSweepCount >= 2 then EndOfSweep := True ;

               { Erase old display now that new sweep has started }
               if (NextBlock >= (2*Settings.NumChannels)) and NewSweep then begin
                   { Erase display once sweep has started (if required) }
                   scDisplay.SetDataBuf( ADCDisplay ) ;
                   scDisplay.NumPoints := 0 ;
                   scDisplay.TScale := Main.SESLabIO.ADCSamplingInterval*Settings.TScale ;
                   if (not scDisplay.StorageMode) or
                      (Abs(Main.SESLabIO.ADCSamplingInterval - rH.dt) > 1E-5) or
                      EraseScreen then scDisplay.Invalidate ;
                   EraseScreen := False ;
                   NewSweep := False ;
                   end ;

               { Display sample record on screen}
               if NumBlocksDone > (2*Settings.NumChannels) then
                  scDisplay.DisplayNewPoints( NumBlocksDone-1 ) ;

               { End of sweep processing }
               if EndOfSweep then begin

                  { Stop A/D conversion ... unless in detect event mode }
                  if not rbEventDetector.checked then begin
                     Main.SESLabIO.ADCStop ;
                     end ;

                  { Save sweep data to file }
                  SaveSweep ;

                  { If in repeated sweep modes request another sweep
                    (unless recording aborted by user) }

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

                  if AbortRecording then begin
                     StopSweep ;
                     end
                  else if rbPulseProgram.checked then begin
                     { Voltage pulse mode }
                     if Stimulator.Prog.StepCounter
                        >= Stimulator.Prog.NumSteps then begin
                        { If at the end of the current set of voltage pulses
                          for the current protocol either load and begin
                          the next protocol in a linked sequence OR
                          stop recording if no valid link exists }

                        if FileExists(Stimulator.Prog.NextProtocolFileName) then begin
                            cbPulseProgram.ItemIndex := cbPulseProgram.Items.IndexOf(
                                 ExtractFileNameOnly(Stimulator.Prog.NextProtocolFileName)) ;
                            Stimulator.LoadProgram(Stimulator.Prog.NextProtocolFileName) ;
                            WriteToLogFile(
                            'Next Program = '+ Stimulator.Prog.NextProtocolFileName ) ;
                            { Stop D/A output }
                            if Main.SESLabIO.DACActive then begin
                               Main.SESLabIO.DACStop ;

                               end ;
                            StartSweep ;
                            end
                        else StopSweep  ;
                        end
                     else StartSweep ;
                     end
                  else begin
                     { Other modes }
                     Inc(NumRecordsDone) ;
                     if (NumRecordsDone >= Settings.NumRecordsRequired) then
                          StopSweep
                     else StartSweep ; ;
                     end ;
                  end ;

               End ;


          Idle : begin

              { ** Procedures when recording is in idle mode ** }

              if (edRecordsRequired.text = '')
                 and (not rbPulseProgram.checked) then
                 edRecordsRequired.text := IntToStr( Settings.NumRecordsRequired ) ;

              // Update channel scaling factors in case amplifier gain has changed
              UpdateChannelSettings ;

              if not Main.FormExists( 'RecPlotFrm' ) and bCloseOLAWindow.Enabled then begin
                 bCloseOLAWindow.Click ;
                 end ;

              end ;

          end ;

        UpdateStopwatch ;
        TimerBusy := False ;

        if (Main.SESLabIO.StimulusStartFlag = True) and
           (not Main.SESLabIO.DACActive) and
           (not bRecord.enabled) then begin
           Main.SESLabIO.StartStimulus ;
           rH.Time := GetTickCount*mStoSecs ;
           end ;

        if not bOpenOLAWindow.Enabled then begin
           if not Main.FormExists( 'RecPlotFrm' ) then bCloseOLAWindow.Click ;
           end ;

        end ;
     end ;


procedure TRecordFrm.StartSweep ;
// ----------------------------
// Start A/D acquisition sweep
// ----------------------------
var
    ch : Integer ;
begin

     { Initialise recording sweep pointers/flags }
     NextBlock := (2*Settings.NumChannels)-1 ;
     NumBlocksDone := 0 ;
     EndOfSweepCount := 0 ;
     NewSweep := True ;
     EndOfSweep := False ;

     // Update file channel settings with recording settings
     for ch := 0 to Settings.NumChannels-1 do Channel[ch] := RecChannel[ch] ;
     // Update sample value range
     MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     MinADCValue := -MaxADCValue -1 ;

     // Set sampling interval
     if rbPulseProgram.checked then begin
        Main.SESLabIO.ADCSamplingInterval := Stimulator.Prog.RecordDuration/Settings.NumSamples ;
        end
     else begin
        Main.SESLabIO.ADCSamplingInterval := Settings.RecordDuration / Settings.NumSamples ;
        end ;

     { Start recording sweep }
     if rbEventDetector.checked then begin
        InitialiseEventDetector ;
        end
     else begin
        { Other modes ... set up a single A/D recording sweep }
        Main.SESLabIO.GetADCBuffer( ADC ) ;

        if Main.SESLabIO.ADCActive then Main.SESLabIO.ADCStop ;
        Main.SESLabIO.ADCNumChannels := Settings.NumChannels ;
        Main.SESLabIO.ADCNumSamples := Settings.NumSamples ;
        Main.SESLabIO.ADCVoltageRangeIndex := Settings.ADCVoltageRangeIndex ;

        // Set A/D acquisition trigger mode
        if Settings.TriggerMode = 'P' then begin
           // Pulse generation mode
           Main.SESLabIO.ADCTriggerMode := tmWaveGen ;
           Main.SESLabIO.ADCExternalTriggerActiveHigh := False ;
           // Set A/D and D/A update intervals
           Stimulator.SetADCDACUpdateIntervals( Stimulator.Prog.RecordDuration ) ;
           //outputdebugString(PChar(format('B Prog.DACdt :=  %.8g ',[Stimulator.Prog.DACdt]))) ;
           end
        else if Settings.TriggerMode = 'E' then begin
           // Wait for external trigger
           Main.SESLabIO.ADCTriggerMode := tmExtTrigger ;
           Main.SESLabIO.ADCExternalTriggerActiveHigh := rbExtTriggerHigh.checked ;
           end
        else begin
           // Free run and event detection mode
           Main.SESLabIO.ADCTriggerMode := tmFreeRun ;
           end ;

        Main.SESLabIO.ADCCircularBuffer := False ;
        rH.Time := GetTickCount*mStoSecs ;
        Main.SESLabIO.ADCStart ;

        end ;

     // Update file header information
     RawFH.NumSamples := Settings.NumSamples ;
     RawFH.NumChannels := Settings.NumChannels ;
     RawFH.NumSamplesPerRecord := Settings.NumSamples*Settings.NumChannels ;
     RawFH.NumDataBytesPerRecord := RawFH.NumSamplesPerRecord*2 ;
     RawFH.NumBytesPerRecord := RawFH.NumDataBytesPerRecord + NumAnalysisBytesPerRecord ;
     RawFH.NumBytesInHeader := NumBytesInFileHeader ;
     RawFH.ADCVoltageRange := Main.SESLabIO.ADCVoltageRange ;
     RawfH.dt := Main.SESLabIO.ADCSamplingInterval ;
     scDisplay.TScale := Main.SESLabIO.ADCSamplingInterval*Settings.TScale ;

     { Start voltage/digital output program (if needed) }
     if rbPulseProgram.checked then StartVoltageProgram
     else begin
         edStatus.text := format( 'Rec %d ( %d/%d ) ',
                          [RawFH.NumRecords,
                           NumRecordsDone,Settings.NumRecordsRequired] ) ;
         Main.StatusBar.Panels[0].Text :=  edStatus.Text ;
         end ;

     State := SweepInProgress ;
     EndofSweep := False ;

     end ;


procedure TRecordFrm.StopSweep ;
// --------------------
// Stop recording sweep
// --------------------
begin
    { Stop recording and return to idle state }

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

    Main.WriteOutputPorts( Settings.DAC[0].HoldingVoltage*Settings.DAC[0].DivideFactor,
                           Settings.DAC[1].HoldingVoltage*Settings.DAC[1].DivideFactor,
                           Settings.DigitalPort.Value ) ;

    RestoreIdleState ;

    end ;


procedure TRecordFrm.RestoreIdleState ;
{ --------------------------------------
  Restore recording module to idle state
  -------------------------------------- }
begin

     { Disable A/D and D/A sub-systems and restore default outputs }
     ShutDownLabInterface ;

     if State <> Idle then begin
        edStatus.font.color := clBlack ;
        edStatus.text := format( 'Stopped %d records in file',
                                [RawFH.NumRecords] ) ;
        Main.StatusBar.Panels[0].Text :=  edStatus.Text ;

        WriteToLogFile( format('Stopped (%d records)',[RawFH.NumRecords])) ;

        State := Idle ;
        bRecord.enabled := True ;
        bStop.enabled := False ;
        Main.SetMenus ;

        if RawFH.NumRecords > 0 then Main.mnShowRaw.enabled := True ;
        RawFH.IdentLine := edIdent.text ;

        { Save header block }
        SaveHeader( RawfH ) ;
        { Set file header in use to raw file header }
        FH := RawFH ;

        // Restore pulse program selected when recording started
        if cbPulseProgram.ItemIndex < cbPulseProgram.Items.Count then
           cbPulseProgram.ItemIndex := StartingPulseProgramIndex ;

        end ;

     end ;


procedure TRecordFrm.InitialiseEventDetector ;
{ -------------------------
  Initialise event detector
  ------------------------- }
var
   i,j : Integer ;
begin
     if not Main.SESLabIO.ADCActive then begin
        { Get pointer to A/D input buffer }
        Main.SESLabIO.GetADCBuffer( ADCCircular ) ;
        { Assign ADC data buffer to detected buffer }
        ADC := ADCDetected ;

        { Set up continuous sampling into a circulating buffer, ensuring
          that it is a multiple of the no. of channels
          Note that buffer size may vary with different lab. interfaces }

        Det.EndofBuf := ((Main.SESLabIO.ADCBufferLimit div Settings.NumChannels)
                           *Settings.NumChannels) - 1 ;

        { Set channel counter }
        Det.LastChan := Settings.NumChannels - 1 ;
        Det.Chan := 0 ;
        Det.ChanSelected := RecChannel[cbDetectChannel.ItemIndex].ChannelOffset ;

        { Set event detection threshold and polarity }
        Settings.EventDetector.Threshold := edDetectionThreshold.Value ;
        Det.Threshold := Round( 2.0*Main.SESLabIO.ADCMaxValue
                                *Settings.EventDetector.Threshold ) ;
        if Det.Threshold >= 0 then begin
           Det.Polarity := 1 ;
           { Note ... baseline set to maximum value to prevent
           event triggering when the recording starts }
           Det.Baseline := Main.SESLabIO.ADCMaxValue ;
           end
        else begin
           Det.Polarity := -1 ;
           Det.Baseline := -(Main.SESLabIO.ADCMaxValue + 1) ;
           end ;
        Det.Threshold := Abs(Det.Threshold) ;

        { Initialise sample pointer }
        Det.Pointer := 0 ;
        { Number of pre-trigger points }
        Settings.EventDetector.PreTrigger := edPreTrigger.Value ;
        Det.PreTriggerPoints :=  Round ( Settings.NumSamples*Settings.EventDetector.PreTrigger
                                         *Settings.NumChannels ) ;

        { Start continous sampling into circular buffer }

        //if  Main.SESLabIO.ADCActive then Main.SESLabIO.ADCStop ;
        Main.SESLabIO.ADCNumChannels := Settings.NumChannels ;
        Main.SESLabIO.ADCNumSamples := (Det.EndofBuf+1) div Settings.NumChannels ;
        Main.SESLabIO.ADCSamplingInterval := Settings.RecordDuration / Settings.NumSamples ;
        Main.SESLabIO.ADCVoltageRangeIndex := Settings.ADCVoltageRangeIndex ;
        Main.SESLabIO.ADCTriggerMode := tmFreeRun ;
        Main.SESLabIO.ADCCircularBuffer := True ;
        Main.SESLabIO.ADCStart ;
        end ;

     { Initialise O/P buffer with empty flag }
     j := 0 ;
     for i := 1 to (Settings.NumChannels*Settings.NumSamples) div 2 do begin
         ADCDetected^[j] := EmptyFlag ;
         Inc(j) ;
         ADCDetected^[j] := -EmptyFlag ;
         Inc(j) ;
         end ;

     Det.EventDetected := False ;
     end ;


procedure TRecordFrm.DetectEvent ;
{ ---------------------------------------
  Monitor circular A/D buffer for signals
  --------------------------------------- }
var
   PointerPlus1,iY,i,j,EFlag : Integer ;
   Done : Boolean ;
begin

     Main.SESLabIO.ADCBufferRefresh ;

     { Scan circular A/D buffer for an event }

     { Initialise pointers }
     PointerPlus1 := Det.Pointer + 1 ;
     if PointerPlus1 > Det.EndOfBuf then PointerPlus1 := 0 ;

     if Odd(PointerPlus1) then EFlag := -EmptyFlag
                          else EFlag := EmptyFlag ;
     if ADCCircular^[PointerPlus1] = EFlag then Done := True
                                           else Done := False ;

        //outputdebugString(PChar(format('%d %d',[Det.Pointer,Det.EndofBuf])));
     { Check new A/D samples in buffer }
     while not Done do begin
        { Extract sample from A/D data buffer }
        iY := ADCCircular^[Det.Pointer] ;
        ADCCopy^[Det.Pointer] := iY ;
        if Odd(Det.Pointer) then ADCCircular^[Det.Pointer] := -EmptyFlag
                            else ADCCircular^[Det.Pointer] := EmptyFlag ;

        if not Det.EventDetected then begin

           { No event detected yet ... check sample against threshold }
           if Det.Chan = Det.ChanSelected then begin
              if Det.Polarity*(iY - Det.Baseline) > Det.Threshold then begin
                 { Event detected ... Store location/time of detected event }
                 Det.EventDetected := True ;
                 { Get segment of buffer to be extracted }
                 Det.StartAt := Det.Pointer - Det.PreTriggerPoints ;
                 if Det.StartAt < 0 then Det.StartAt := Det.StartAt + (Det.EndofBuf+1) ;
                 Det.StartAt := (Det.StartAt div Settings.NumChannels)*Settings.NumChannels ;
                 Det.EndAt := Det.StartAt + Settings.NumSamples*Settings.NumChannels -1 ;
                 if Det.EndAt > Det.EndofBuf then Det.EndAt := Det.EndAt - (Det.EndofBuf+1) ;
                 { Time of detection }
                 Det.TimeDetected := Det.BufferCycles ;
                 Det.TimeDetected := Main.SESLabIO.ADCSamplingInterval *((Det.TimeDetected*(Det.EndOfBuf+1)
                                     + Det.Pointer ) / Settings.NumChannels) ;
                 end ;
              end ;
           end
        else begin
           { Event detected ... Wait till all samples have been collected,
                                then transfer the samples to ADC buffer }
           if Det.Pointer = Det.EndAt then begin
               j := Det.StartAt ;
               for i := 0 to (Settings.NumSamples*Settings.NumChannels)-1 do begin
                   ADCDetected^[i] := ADCCopy^[j] ;
                   Inc(j) ;
                   if j > Det.EndOfBuf then j := 0 ;
                   end ;
               Det.EventDetected := False ;
               Done := True ;
               end ;
           end ;

        { Update baseline level with 30 point running mean }
        if Det.Chan = Det.ChanSelected then begin
           Det.Baseline := (iY + 30*Det.Baseline) div 31 ;
           end ;

        { Increment sample pointers }
        Inc( Det.Pointer ) ;
        if Det.Pointer > Det.EndofBuf then begin
           Inc(Det.BufferCycles) ;
           Det.Pointer := 0 ;
           Done := True ;
           end ;

        Inc( PointerPlus1 ) ;
        if PointerPlus1 > Det.EndofBuf then PointerPlus1 := 0 ;

        { Increment channel pointer }
        Inc(Det.Chan) ;
        if Det.Chan > Det.LastChan then Det.Chan := 0 ;

        { Exit loop if no samples left }
        if Odd(PointerPlus1) then EFlag := -EmptyFlag
                             else EFlag := EmptyFlag ;
        if ADCCircular^[PointerPlus1] = EFlag then Done := True ;

        end ;

     end ;


procedure TRecordFrm.StartVoltageProgram ;
{ ------------------------------------------------------------
  Create and output a voltage/digital program to D/A converter
  ------------------------------------------------------------ }
begin

     { Show which step/repeat is being recorded }
     edStatus.text := format( 'Rec %d Stim: %s Step %d/%d Repeat %d/%d ',
                               [RawFH.NumRecords,
                                cbPulseProgram.Text,
                                Stimulator.Prog.StepCounter+1,Stimulator.Prog.NumSteps,
                                Stimulator.Prog.RepeatCounter+1,Stimulator.Prog.NumRepeats
                                ]) ;

     Main.StatusBar.Panels[0].Text := edStatus.Text ;

     if OldStepCounter <> Stimulator.Prog.StepCounter then Inc(GroupNumber) ;
     OldStepCounter := Stimulator.Prog.StepCounter ;

     { Create TEST voltage program waveform }
     if not InLeakMode then begin

        Main.SESLabIO.DACNumSamples := Stimulator.CreateWaveform( DAC^, DigBuf^ ) ;
        Main.SESLabIO.DACUpdateInterval := Stimulator.Prog.DACdt ;

        { If repeat count = 0 ... create waveform }
        if Stimulator.Prog.RepeatCounter = 0 then begin
           Stimulator.Prog.LeakCounter := 0 ;
           end ;

        { If LEAK records are needed ... re-scale test waveform and switch to LEAK mode }
        if (Abs(Stimulator.Prog.NumLeaks) > 0) and (not LeakCollected)
           {and (Stimulator.Prog.RepeatCounter = 0)} then begin
           { Create leak waveform }
           Main.SESLabIO.DACNumSamples := Stimulator.CreateLeakWaveform( DAC^ ) ;
           InLeakMode := True ;
           end ;
        end ;

     if not InLeakMode then begin

        Inc(Stimulator.Prog.RepeatCounter) ;
        if Stimulator.Prog.RepeatCounter >= Stimulator.Prog.NumRepeats then begin
           Stimulator.Prog.RepeatCounter := 0 ;
           Inc(Stimulator.Prog.StepCounter) ;
           LeakCollected := False ;
           end ;
        end
     else begin
        // In leak mode

        // Create leak waveform
        Main.SESLabIO.DACNumSamples := Stimulator.CreateWaveform( DAC^, DigBuf^ ) ;
        Main.SESLabIO.DACUpdateInterval := Stimulator.Prog.DACdt ;
        Main.SESLabIO.DACNumSamples := Stimulator.CreateLeakWaveform( DAC^ ) ;

        { Add leak information ... if this is a LEAK record }
        edStatus.text := edStatus.text
                         + format( ' ( Leak %d/%d)',
                         [Stimulator.Prog.LeakCounter+1,Abs(Stimulator.Prog.NumLeaks)
                         ]);
        Main.StatusBar.Panels[0].Text :=  edStatus.Text ;

        end ;

     { Set up D/A conversion  }
     Main.SESLabIO.DACNumChannels := NumDACChannels ;
     { Set voltage stimulus pulse interval }

     Main.SESLabIO.TimerPeriod := Stimulator.Prog.RecordInterval ;
     Main.StatusBar.Panels[1].Text := '' ;

     Main.SESLabIO.DigitalStimulusEnabled := Stimulator.Prog.DigitalInUse and (not InLeakMode) ;
     if not Main.SESLabIO.TimerActive then Main.SESLabIO.StartTimer ;

     end ;


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

     { Stop periodic stimulus pulse timer }
     Main.SESLabIO.StopTimer ;

     { Disable A/D }
     if  Main.SESLabIO.ADCActive then Main.SESLabIO.ADCStop ;

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

     { Return voltage command to holding voltage and Sync. O/P to 5V }
     Main.WriteOutputPorts( Settings.DAC[0].HoldingVoltage*Settings.DAC[0].DivideFactor,
                            Settings.DAC[1].HoldingVoltage*Settings.DAC[1].DivideFactor,
                               Settings.DigitalPort.Value ) ;

     Main.SESLabIO.ADCNumSamples := Settings.NumSamples ;
     MainMenus( Enable ) ;
     end ;


procedure TRecordFrm.SaveSweep ;
{ ---------------------------
  Save recording sweep to file
  --------------------------- }
var
   ch : Integer ;
   i,NumAvg,iStart : Integer ;
   Done : Boolean ;
begin

     // Update channel scaling factors in case amplifier gain has changed
     UpdateChannelSettings ;

     { Replace any Empty flags in the buffer with the most recently
       recorded sample for that channel }
     Done := False ;
     i := EndOfBuf ;
     while (not Done) do begin
        if (ADC^[i] = -EmptyFlag) and (ADC^[i-1] = EmptyFlag) then i := i - 2
                                                              else Done := True ;
        if i < 1 then Done := True ;
        end ;
     if i < EndofBuf then begin
        iStart := ((i - Settings.NumChannels) div Settings.NumChannels)*Settings.NumChannels ;
        iStart := Max(iStart,0) ;
        for i := iStart to EndOfBuf do
            ADC^[i] := ADC^[iStart + (i mod Settings.NumChannels)] ;
        end ;

     { Special processing if this is a leak current record }
     if InLeakMode then begin
        {Add leak record to average }
        if Stimulator.Prog.LeakCounter = 0 then for i := 0 to EndOfBuf do Sum^[i] := 0 ;
        for i := 0 to EndOfBuf do Sum^[i] := Sum^[i] + ADC^[i] ;
        Inc(Stimulator.Prog.LeakCounter) ;
        if Stimulator.Prog.LeakCounter >= Abs(Stimulator.Prog.NumLeaks) then begin
           NumAvg := MaxInt( [Stimulator.Prog.NumLeaks,1] ) ;
           for i := 0 to EndOfBuf do ADC^[i] := Sum^[i] div NumAvg ;
           rH.RecType := 'LEAK' ;
           InLeakMode := False ;
           LeakCollected := True ;
           end
        end
     else begin
           rH.RecType := 'TEST' ;
           LeakCollected := False ;
           end ;

     { *** Save data to file *** (unless in leak mode) }
     if not InLeakMode then begin

        RawfH.NumRecords := RawfH.NumRecords + 1 ;
        RawFH.CurrentRecord := RawFH.NumRecords ;
        rH.Status := 'ACCEPTED' ;
        if rbPulseProgram.checked then rH.Number := GroupNumber
                                  else rH.Number := RawfH.NumRecords ;
        rH.dt := Main.SESLabIO.ADCSamplingInterval ;

        { Update time that record was collected }
        if rbEventDetector.checked then rH.Time := Det.TimeDetected  ;

        { Make time relative to the first record acquired }
        if RawFH.NumRecords = 1 then Settings.TimeRecordingStarted := rH.Time ;
        rH.Time := rH.Time - Settings.TimeRecordingStarted ;

        rH.Analysis.Available := false ;
        rH.Equation.Available := false ;
        PutRecord( RawfH, rH, RawfH.NumRecords, ADC^ ) ;
        rH.Ident := '' ;

        if (not ckSaveRecords.checked) then begin
              RawfH.NumRecords := RawfH.NumRecords - 1 ;
              edStatus.text := 'Records not saved!' ;
              Main.StatusBar.Panels[0].Text :=  edStatus.Text ;
              end ;

        if Main.FormExists( 'RecPlotFrm' ) and (not AbortRecording) then begin
           RecPlotFrm.UpdatePlot( ADC^,
                                  scDisplay.VerticalCursors[C0Cursor],
                                  scDisplay.VerticalCursors[C1Cursor],
                                  scDisplay.VerticalCursors[C2Cursor],
                                  cbPulseProgram.Text,
                                  RawfH.NumRecords ) ;
           end ;
        end ;

     End ;


procedure TRecordFrm.UpdateChannelSettings ;
// ----------------------------------------------------------------------
// Update channel scaling factors in case amplifier gain/mode has changed
// ----------------------------------------------------------------------
var
    ch : Integer ;
    Changed : Boolean ;
    OldValue : Single ;
    OldUnits : String ;
    OldName : String ;
begin

     Changed := False ;
     for ch := 0 to Settings.NumChannels-1 do begin

         OldValue := RH.ADCVoltageRange[ch] ;
         OldUnits := RecChannel[ch].ADCUnits ;
         OldName := RecChannel[ch].ADCName ;

         // Channels 0&1 associated with Amplifier #1, 2&3 with #2.
         Amplifier.GetChannelSettings( ch,
                                       RecChannel[ch].ADCName,
                                       RecChannel[ch].ADCUnits,
                                       RecChannel[ch].ADCCalibrationFactor,
                                       RecChannel[ch].ADCAmplifierGain ) ;

         RH.ADCVoltageRange[ch] := Main.SESLabIO.ADCVoltageRange /
                                   RecChannel[ch].ADCAmplifierGain ;

         if (OldValue <> RH.ADCVoltageRange[ch]) or
            (OldUnits <> RecChannel[ch].ADCUnits) or
            (OldName <> RecChannel[ch].ADCName) then Changed := True ;

         end ;

     // Re-calculate channel ADC unit -> units scaling factor
     UpdateChannelScalingFactors( RH ) ;

     UpdateAmplifierGain ;

     // Update display readout scaling factors
     for ch := 0 to Settings.NumChannels-1 do begin
         scDisplay.ChanScale[ch] := RecChannel[ch].ADCScale ;
         scDisplay.ChanUnits[ch] := RecChannel[Ch].ADCUnits ;
         scDisplay.ChanName[ch] := RecChannel[ch].ADCName ;
         end ;

     // Force display display update if factors changed
     if Changed then scDisplay.Invalidate ;


     end ;

procedure TRecordFrm.UpdateAmplifierGain ;
// ------------------------------
// Update amplifier gain readouts
// ------------------------------
begin

     // Update patch clamp gain readout
     if Amplifier.GainTelegraphAvailable[1] then begin
        // Amplifier with gain telegraph
        edAmplifierGain.Units := 'V/' + RecChannel[0].ADCUnits ;
        edAmplifierGain.Value := Abs(Main.SESLabIO.ADCVoltageRange) /
                                 (RecChannel[0].ADCScale*(Main.SESLabIO.ADCMaxValue+1) ) ;
        end
     else begin
        // User-entered amplifier gain
        if SetAmplifierGainReadout then begin
           edAmplifierGain.Units := 'V/' + RecChannel[0].ADCUnits ;
           edAmplifierGain.Value := RecChannel[0].ADCCalibrationFactor ;
           end ;
        if NewAmplifierGainValue then begin
           // Gain factor cannot be manually changed if records have been collected
           // or zero or negative value entered
           if (RawFH.NumRecords > 0) or (edAmplifierGain.Value <= 0.0) then begin
              edAmplifierGain.Value := RecChannel[0].ADCCalibrationFactor ;
              end ;
           RecChannel[0].ADCCalibrationFactor := edAmplifierGain.Value ;
           end ;
        end ;

     // Update patch clamp amplifier #2 gain readout

     if (Amplifier.AmplifierType[2] = amNone) or (Settings.NumChannels < 3) then begin
        Amplifier2Panel.Visible := False ;
        end
     else if not Amplifier.GainTelegraphAvailable[2] then begin
        // User-entered amplifier gain
        Amplifier2Panel.Visible := True ;
        if SetAmplifierGainReadout then begin
           edAmplifier2Gain.Units := 'V/' + RecChannel[2].ADCUnits ;
           edAmplifier2Gain.Value := RecChannel[2].ADCCalibrationFactor ;
           end ;
        if NewAmplifierGainValue then begin
           // Gain factor cannot be manually changed if records have been collected
           // or zero or negative value entered
           if (RawFH.NumRecords > 0) or (edAmplifier2Gain.Value <= 0.0) then begin
              edAmplifier2Gain.Value := RecChannel[2].ADCCalibrationFactor ;
              end ;
           RecChannel[0].ADCCalibrationFactor := edAmplifierGain.Value ;
           end ;
        end
     else begin
        // Amplifier with gain telegraph
        Amplifier2Panel.Visible := True ;
        edAmplifier2Gain.Units := 'V/' + RecChannel[2].ADCUnits ;
        edAmplifier2Gain.Value := Abs(Main.SESLabIO.ADCVoltageRange) /
                                 (RecChannel[2].ADCScale*(Main.SESLabIO.ADCMaxValue+1) ) ;
        end ;

     // Re-calculate channel ADC unit -> units scaling factor
     UpdateChannelScalingFactors( RH ) ;

     NewAmplifierGainValue := False ;
     SetAmplifierGainReadout := False ;

     end ;



procedure TRecordFrm.bRecordClick(Sender: TObject);
{ -----------------------
  Start recording session
  ----------------------- }
var
    ch : Integer ;
begin

     // Prevent recording if channel settings have changed
     if RawFH.NumRecords > 0 then begin

        if (RawFH.NumChannels <> Settings.NumChannels) or
           (RawFH.NumSamples <> Settings.NumSamples) then begin
           ShowMessage('Record settings changed! Open a new file to continue recording.') ;
           exit ;
           end ;

        for ch := 0 to Settings.NumChannels-1 do begin
            if (Channel[ch].ADCName <> RecChannel[ch].ADCName) or
               (Channel[ch].ADCUnits <> RecChannel[ch].ADCUnits) or
               (Channel[ch].ADCCalibrationFactor <> RecChannel[ch].ADCCalibrationFactor) then begin
               ShowMessage('Channel settings changed! Open a new file to continue recording.') ;
               exit ;
               end ;
            end ;
        end ;

     AbortRecording := False ;
     bRecord.Enabled := False ;
     bStop.Enabled := True ;
     edStatus.font.color := clRed ;

     // Keep current setting of pulse program list
     StartingPulseProgramIndex := cbPulseProgram.ItemIndex ;

     MainMenus( Disable ) ;

     WriteToLogFile( 'Recording Started ' ) ;

     // Stop timer
     // (Forces a call to Main.SESLabIO.StartTimer in PulseProgram
     //  trigger mode which ensures first pulse is generated immediately)
     if Main.SESLabIO.TimerActive then Main.SESLabIO.StopTimer ;

     { Set number of recording sweepsm to be collected }
     Settings.NumRecordsRequired := Round(edRecordsRequired.Value) ;
     EndAtRecord := RawFH.NumRecords + Settings.NumRecordsRequired ;
     NumRecordsDone := 0 ;

     { Get last group number used }
     if RawFH.NumRecords > 0 then begin
        GetRecordHeaderOnly( RawfH, rH, RawfH.NumRecords ) ;
        GroupNumber := Round(rH.Number) + 1
        end
     else begin
        { Reset group number for a new file }
        GroupNumber := 1 ;
        {Set event detector buffer cycle count to zero
         when recording starts in a new file }
        Det.BufferCycles := 0 ;
        end ;

     { Load voltage program (if in Stimulus Protocol mode) }
     if rbPulseProgram.checked then begin
        Settings.VProgramFileName := Settings.VProtDirectory
                                     + cbPulseProgram.text + '.vpr' ;
        if FileExists( Settings.VProgramFileName ) then begin
           Stimulator.LoadProgram(Settings.VProgramFileName) ;
           WriteToLogFile( 'Voltage Program = ' + Settings.VProgramFileName ) ;
           Stimulator.Prog.StepCounter := 0 ;
           end
        else begin
           ShowMessage( ' No Voltage Program - Recording Aborted! ' ) ;
           State := Idle ;
           end ;
        OldStepCounter := 0 ;
        end ;

     StartSweep ;

     if State = Idle then begin
        bRecord.Enabled := True ;
        bStop.Enabled := False ;
        InLeakMode := False ;
        LeakCollected := False ;
        end ;

     end;


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

     ckFixZeroLevels.Top := ClientHeight - ckFixZeroLevels.Height - 5 ;
     edStatus.Top := ckFixZeroLevels.Top - EdStatus.Height - 2 ;
     TimerGrp.Top := ClientHeight - TimerGrp.Height - 5 ;

     scDisplay.Height := Max( edStatus.Top - scDisplay.Top  - 5, 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 ;
     edStatus.Width := scDisplay.Left + scDisplay.Width - edStatus.Left ;

     end;


procedure TRecordFrm.bStopClick(Sender: TObject);
{ ---------------
  Stop recording
  --------------- }
begin
     if (State = SweepInProgress) then EndofSweep := True
                                  else StopSweep ;

     bRecord.enabled := True ;
     bStop.enabled := False ;
     AbortRecording := True ;
     end;


procedure TRecordFrm.cbPulseProgramChange(Sender: TObject);
{ ---------------------------
  Load a new voltage program
  --------------------------}
begin
     { Update voltage program file name stored in 'settings' when
       program combo box is changed }
     Settings.VProgramFileName := Settings.VProtDirectory + cbPulseProgram.text + '.vpr' ;
     if FileExists( Settings.VProgramFileName ) then begin
         Stimulator.LoadProgram(Settings.VProgramFileName) ;
         Settings.DAC[0].HoldingVoltage := Stimulator.Prog.HoldingVoltage[0] ;
         Settings.DAC[1].HoldingVoltage := Stimulator.Prog.HoldingVoltage[1] ;
         Settings.DigitalPort.Value := Stimulator.Prog.DigitalPortValue ;
         end ;
     end;


procedure TRecordFrm.FormClose(Sender: TObject; var Action: TCloseAction);
// ---------------------------
// Tidy up when form is closed
// ---------------------------
begin

     { Shut down Lab. interface hardware ... essential to avoid program crash }
     RestoreIdleState ;
     HeapBuffers( Deallocate ) ;
     Action := caFree ;
     end;


procedure TRecordFrm.UpdateStopwatch ;
var
   Seconds : single ;
begin

        Seconds := (GetTickCount - Stopwatch.TickStart) / 1000. ;
        if (Seconds - Stopwatch.OldSeconds) > 1. then begin
           Stopwatch.OldSeconds := Seconds ;

           Stopwatch.Minute := Trunc(Seconds / 60. ) ;
           Stopwatch.Second := Trunc( Seconds - Stopwatch.Minute*60. ) ;
           Stopwatch.Hour := Stopwatch.Minute div 60 ;
           Stopwatch.Minute := Stopwatch.Minute mod 60 ;
           Stopwatch.MSec := 0 ;
           edTimeofDay.text := format(' %2dh:%2dm:%2ds ',[Stopwatch.Hour,
                                                          Stopwatch.Minute,
                                                          Stopwatch.Second]) ;

           end ;
        end ;


procedure TRecordFrm.bResetTimerClick(Sender: TObject);
{ Reset elapsed time counter on stopwatch
  ---------------------------------------}
begin
     Stopwatch.TickStart := (GetTickCount*1. ) ;
     Stopwatch.OldSeconds := 0. ;

     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.rbPulseProgramClick(Sender: TObject);
{ Set trigger mode to voltage pulse program }
begin
     Settings.TriggerMode := 'P' ;
     end;

procedure TRecordFrm.rbEventDetectorClick(Sender: TObject);
{ Set trigger mode to event detection }
begin
     Settings.TriggerMode := 'D' ;
     end;


procedure TRecordFrm.FormDeactivate(Sender: TObject);
{ ---------------------------------------
  Actions to be taken if form loses focus
  ---------------------------------------}
begin
     { Shut down lab. interface and return to idle state }
     //RestoreIdleState ;
     end;


procedure TRecordFrm.edIdentKeyPress(Sender: TObject; var Key: Char);
begin
     { Update ident line if it is changed }
     RawFH.IdentLine := edIdent.text ;
     { Only save to file if recording is not in progress }
     if bRecord.Enabled then begin
        SaveHeader(RawFH) ;
        if key = chr(13) then WriteToLogFile( RawFH.IdentLine ) ;
        end ;
     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.bEraseClick(Sender: TObject);
{ -------------------------
  Erase the display screen
  ------------------------}
begin
     if bRecord.Enabled then begin
        { Erase screen immediately if not recording }
        scDisplay.NumPoints := 0 ;
        scDisplay.Invalidate ;
        end
     else begin
        { Request that the screen be erased at the beginning of the next sweep }
        EraseScreen := True ;
        end ;
     end;


procedure TRecordFrm.edPreTriggerKeyPress(Sender: TObject; var Key: Char);
{ ----------------------------------
  Get event detection pre-trigger %
  ---------------------------------- }
begin
     if key = chr(13) then Settings.EventDetector.PreTrigger := edPreTrigger.Value ;
     end ;


procedure TRecordFrm.EdDetectionThresholdKeyPress(Sender: TObject;
  var Key: Char);
{ -------------------------------------
  Get event detection threshold level
  -----------------------------------}
begin
     if key = chr(13) then Settings.EventDetector.Threshold := edDetectionThreshold.Value ;
     end ;


procedure TRecordFrm.SetStoreMode(
          StorageMode : Boolean
          ) ;
{ --------------------------------------------
  Set oscilloscope display storage mode on/off
  -------------------------------------------- }
begin
     scDisplay.StorageMode := StorageMode ;
     end ;


procedure TRecordFrm.cbDetectChannelChange(Sender: TObject);
{ --------------------------------------
  Store change to event detector channel
  -------------------------------------- }
begin
     Settings.EventDetector.Channel := IntLimitTo(cbDetectChannel.ItemIndex,
                                       0,Settings.NumChannels-1) ;
     end;


procedure TRecordFrm.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
begin
     CanClose := not (Main.SESLabIO.ADCActive or Main.SESLabIO.DACActive) ;
     end;


procedure TRecordFrm.MainMenus( Enabled : Boolean ) ;
begin
     Main.File1.Enabled := Enabled ;
     Main.View.Enabled := Enabled ;
     Main.Analysis.Enabled := Enabled ;
     Main.Edit.Enabled := Enabled ;
     Main.Setup.Enabled := True ;
     Main.Simulations1.Enabled := Enabled ;
     end ;


procedure TRecordFrm.scDisplayCursorChange(Sender: TObject);
var
   ch : Integer ;
begin

     // Keep zero levels fixed at signal 0 mV level
     if ckFixZeroLevels.Checked then begin
        for ch := 0 to scDisplay.NumChannels-1 do if Channel[ch].InUse and
            (scDisplay.HorizontalCursors[Cursors.Base[ch]] <> 0) then
                scDisplay.HorizontalCursors[Cursors.Base[ch]] := 0 ;
        end ;

     for ch := 0 to scDisplay.NumChannels-1 do
         RecChannel[ch].ADCZero := scDisplay.HorizontalCursors[Cursors.Base[ch]] ;

     { Update channel descriptors with any changes to display }
     for ch := 0 to scDisplay.NumChannels-1 do if Channel[ch].InUse then begin
         RecChannel[Ch].yMin := scDisplay.YMin[Ch] ;
         RecChannel[Ch].yMax := scDisplay.YMax[Ch] ;
         end ;

     end;


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

     scDisplay.Invalidate ;
     end ;


procedure  TRecordFrm.ZoomOutAll ;
{ ---------------------------------
  Set minimum display magnification
  --------------------------------- }
var
    i : Integer ;
begin
     scDisplay.MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     scDisplay.MinADCValue := Main.SESLabIO.ADCMinValue ;
     scDisplay.ZoomOut ;
     for i := 0 to High(RecChannel) do begin
         RecChannel[i].yMin := Main.SESLabIO.ADCMinValue ;
         RecChannel[i].yMax := Main.SESLabIO.ADCMaxValue ;
         end ;
     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.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.ckFixZeroLevelsClick(Sender: TObject);
begin
     scDisplay.Invalidate ;
     end;


procedure TRecordFrm.edAmplifierGainKeyPress(Sender: TObject;
  var Key: Char);
// ---------------------
// Update amplifier gain
// ---------------------
begin
     if Key = #13 then begin
        NewAmplifierGainValue := True ;
        UpdateChannelSettings ;
        end ;
     end;

     
procedure TRecordFrm.UpdateChannelScalingFactors(
          var RH : TRecHeader ) ;
// ------------------------------
// Update channel scaling factors
// ------------------------------
var
   ch : Integer ;
begin
     for ch := 0 to Settings.NumChannels-1 do begin

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

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

procedure TRecordFrm.SetTriggerMode( Value : String ) ;
// ----------------
// Set trigger mode
// ----------------
begin
     Settings.TriggerMode := Value ;
     if Settings.TriggerMode = 'P' then rbPulseProgram.checked := True
     else if Settings.TriggerMode = 'E' then rbExtTrigger.checked := True
     else if Settings.TriggerMode = 'D' then rbEventDetector.checked := True
     else rbFreeRun.Checked := True ;
     end ;


function TRecordFrm.GetTriggerMode : String ;
// ----------------
// Get trigger mode
// ----------------
begin
     Result := Settings.TriggerMode ;
     end ;


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


procedure TRecordFrm.StopRecording ;
// -------------------------------
// Stop recording (external call)
// -------------------------------
begin
     bStop.Click ;
     end ;


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


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

    idx := cbPulseProgram.Items.IndexOf(Value) ;
    if (idx > 0) and (idx < cbPulseProgram.Items.Count) then begin
       cbPulseProgram.ItemIndex := idx ;
       Settings.VProgramFileName := Settings.VProtDirectory + cbPulseProgram.Text + '.vpr' ;
       end
    else begin
       cbPulseProgram.ItemIndex := 0 ;
       Settings.VProgramFileName := '' ;
       end ;

    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.FormActivate(Sender: TObject);
begin

     // Stop seal test recording form if it is open
     if Main.FormExists( 'SealTestFrm') then SealTestFrm.StopADCandDAC ;

     SetAmplifierGainReadout := True ;


     end;

procedure TRecordFrm.bOpenOLAWindowClick(Sender: TObject);
// ----------------------------
// Open on-line analysis window
// ----------------------------
begin

     scDisplay.ClearVerticalCursors ;
     C0Cursor := scDisplay.AddVerticalCursor(-1,clOlive,'c0') ;
     scDisplay.VerticalCursors[C0Cursor] := 1 ;
     C1Cursor := scDisplay.AddVerticalCursor(-1,clGreen,'c1') ;
     scDisplay.VerticalCursors[C1Cursor] := 10 ;
     C2Cursor := scDisplay.AddVerticalCursor(-1,clGreen,'c2') ;
     scDisplay.VerticalCursors[C2Cursor] := Settings.NumSamples - 10 ;
     scDisplay.LinkVerticalCursors(C1Cursor,C2Cursor) ;
     scDisplay.Invalidate ;

     if not Main.FormExists( 'RecPlotFrm' ) then begin
        RecPlotFrm := TRecPlotFrm.Create(Self) ;
        RecPlotFrm.Width :=  Main.ClientWidth - RecordFrm.Left - RecordFrm.Width - 5 ;
        RecPlotFrm.Left := RecordFrm.Left + RecordFrm.Width + 2 ;
        RecPlotFrm.Top := 10 ;
        end ;

     bCloseOLAWindow.Enabled := True ;
     bOpenOLAWindow.Enabled := False ;

     end;

procedure TRecordFrm.bCloseOLAWindowClick(Sender: TObject);
// -----------------------------
// Close on-line analysis window
// -----------------------------
begin
     scDisplay.ClearVerticalCursors ;
     scDisplay.Invalidate ;
     
     if Main.FormExists( 'RecPlotFrm' ) then begin
        RecPlotFrm.Close  ;
        end ;

     bCloseOLAWindow.Enabled := False ;
     bOpenOLAWindow.Enabled := True ;

     end ;

procedure TRecordFrm.bAddMarkerClick(Sender: TObject);
// --------------------
// Add marker to record
// --------------------
begin
    scDisplay.ClearMarkers ;
    scDisplay.AddMarker( 0, EdMarker.text );
    rh.ident := edMarker.Text ;
    end;

procedure TRecordFrm.bSetStimFolderClick(Sender: TObject);
// -----------------------------
//  Set voltage protocol file folder
// -----------------------------
begin
     Main.OpenDialog.FileName := '_' ;
     Main.OpenDialog.InitialDir := Settings.VProtDirectory ;
     Main.OpenDialog.Title := 'Select protocol file folder' ;
     if Main.OpenDialog.execute then begin
        Settings.VProtDirectory := ExtractFilePath( Main.OpenDialog.FileName ) ;
        // Load list of stimulus programs
        Stimulator.CreateProgramList( cbPulseProgram ) ;
        if Settings.VProgramFileName <> '' then
           cbPulseProgram.ItemIndex := cbPulseProgram.Items.IndexOf(
                                 ExtractFileNameOnly(Settings.VProgramFileName))
        else cbPulseProgram.ItemIndex := 0 ;
        end ;

     end ;

Initialization
    BuffersAllocated := False ;
end.
