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 wavceforms 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
  13.05.10 .. Channel display visibility setting now preserved in RecChannel[].InUse
  22.07.10 .. Time of record acquisition (RH.Time) now set when record is saved
              to provide consistent time for all trigger modes. Fixes bug with
              external trigger mode where first record had T=0 and 2nd record
              record had incorrectly long time.
  06.01.11 .. TritonPanelFrm opened automatically when record form is displayed
              when a Tecella amplifier is in use.
  09.02.11 .. Second pair of cursors added for on-line analysis
  27.05.11 .. Recording mode now selected from drop-down menu
              No. records and no. samples/record now set in record window (not setup)
  20.07.11 .. Protocol execution list added
  17.08.11 .. Protocol file names now local to selected protocol folder so linked
              protocols continue to work when folders renamed
  12.09.11 .. Stimulator.Prot.NumAOChannels now used to set number of AO channels in use
              All 4 AO channels of CED Power 1401 now work. DO0 and DO1 also checked and OK
  14.09.11 .. Display Y ranges now updated on exit
  20.09.11 .. Main.SESLabIO.ADCVoltageRangeIndex now set from Settings.ADCVoltageRangeIndex
              every time .StartADC is called
  13.03.12 .. VCLAMP/ICLAMP mode of amplifiers now indicated/set in recording window
  16.04.12 .. Stimulus output channels now limited to number of available DAC channels
              Global display channel vertical limits and enabled/disabled settings
              now updated whenever changed.
              Changes to recording parameters now disabled when recording in progress
              Record display window (if open) now updated with number of records as they are collected
  ==============================================================================}
interface

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

const
     rmProtocol = 0 ;
     rmFreeRun = 1 ;
     rmExtTrig = 2 ;
     rmDetect = 3 ;

type
  TState = ( Idle, SweepInProgress ) ;
  TRecordFrm = class(TForm)
    ControlGrp: TGroupBox;
    bRecord: TButton;
    bStop: TButton;
    ckSaveRecords: TCheckBox;
    Timer: TTimer;
    DisplayGrp: TGroupBox;
    edStatus: TEdit;
    edIdent: TEdit;
    Label4: TLabel;
    TimerGrp: TGroupBox;
    edTimeOfDay: TEdit;
    bResetTimer: TButton;
    bErase: TButton;
    OnLineAnalysisGrp: TGroupBox;
    ckFixZeroLevels: TCheckBox;
    bOpenOLAWindow: TButton;
    bCloseOLAWindow: TButton;
    edMarker: TEdit;
    bAddMarker: TButton;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Bevel4: TBevel;
    RecordModeGrp: TGroupBox;
    cbRecordingMode: TComboBox;
    RecordParametersPanel: TPanel;
    edNumRecords: TValidatedEdit;
    Label3: TLabel;
    Label10: TLabel;
    edNumSamples: TValidatedEdit;
    Label11: TLabel;
    edNumChannels: TValidatedEdit;
    Label12: TLabel;
    edSamplingInterval: TValidatedEdit;
    Label13: TLabel;
    edRecordDuration: TValidatedEdit;
    panAmplifierGain0: TPanel;
    lbAmplifier: TLabel;
    edAmplifierGain0: TValidatedEdit;
    panAmplifierGain1: TPanel;
    Label14: TLabel;
    edAmplifierGain1: TValidatedEdit;
    panAmplifierGain2: TPanel;
    Label15: TLabel;
    edAmplifierGain2: TValidatedEdit;
    panAmplifierGain3: TPanel;
    Label16: TLabel;
    edAmplifierGain3: TValidatedEdit;
    panProtocol: TPanel;
    rbSingleProtocol: TRadioButton;
    rbProtocolList: TRadioButton;
    panProtocolList: TPanel;
    Label17: TLabel;
    cbProtocolList: TComboBox;
    meProtocolList: TMemo;
    bAddProtocolToList: TButton;
    bNewProtocolList: TButton;
    edNewProtocolListName: TEdit;
    bDeleteProtocolList: TButton;
    panSingleProtocol: TPanel;
    Label18: TLabel;
    cbPulseProgram: TComboBox;
    bSetStimFolder: TButton;
    ExtTriggerGrp: TGroupBox;
    rbExtTriggerHigh: TRadioButton;
    rbExttriggerLow: TRadioButton;
    DetectGrp: TGroupBox;
    Label1: TLabel;
    Label2: TLabel;
    Label5: TLabel;
    cbDetectChannel: TComboBox;
    edDetectionThreshold: TValidatedEdit;
    edPreTrigger: TValidatedEdit;
    OpenDialog: TOpenDialog;
    scDisplay: TScopeDisplay;
    rbVCLAMP0: TRadioButton;
    rbICLAMP0: TRadioButton;
    rbVCLAMP1: TRadioButton;
    RBICLAMP1: TRadioButton;
    rbVCLAMP2: TRadioButton;
    RBICLAMP2: TRadioButton;
    rbVCLAMP3: TRadioButton;
    rbICLAMP3: TRadioButton;
    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 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 edAmplifierGain0KeyPress(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);
    procedure cbRecordingModeChange(Sender: TObject);
    procedure edNumChannelsKeyPress(Sender: TObject; var Key: Char);
    procedure edNumSamplesKeyPress(Sender: TObject; var Key: Char);
    procedure edRecordDurationKeyPress(Sender: TObject; var Key: Char);
    procedure edNumRecordsKeyPress(Sender: TObject; var Key: Char);
    procedure edSamplingIntervalKeyPress(Sender: TObject; var Key: Char);
    procedure rbSingleProtocolClick(Sender: TObject);
    procedure rbProtocolListClick(Sender: TObject);
    procedure cbProtocolListChange(Sender: TObject);
    procedure bAddProtocolToListClick(Sender: TObject);
    procedure bDeleteProtocolListClick(Sender: TObject);
    procedure bNewProtocolListClick(Sender: TObject);
    procedure rbVCLAMP0Click(Sender: TObject);
    procedure rbICLAMP0Click(Sender: TObject);
  private
    { Private declarations }

    NewAmplifierGain : Boolean ;

    C0Cursor : Integer ;
    C1Cursor : Integer ;
    C2Cursor : Integer ;
    C3Cursor : Integer ;
    C4Cursor : Integer ;

    StartingPulseProgramIndex : Integer ; // Pulse program index at start of recording
    ProtocolCount : Integer ;           // Protocol execution list index counter
    ProtocolFileName : String ;         // Currently selected stimulus protocol file

    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 UpdateAmplifierGain ;
    procedure SetRecordingMode(iMode : Integer ) ;
    function GetRecordingMode : Integer ;
    function GetStimulusProtocol : String ;
    procedure SetStimulusProtocol( Value : String ) ;
    function GetRunning : Boolean ;
    function GetRecording : Boolean ;
    procedure UpdateNumSamples ;
    procedure UpdateSamplingInterval ;
    procedure UpdateAmplifierGains ;
    procedure ShowAmplifierGains ;
    function UpdateVProgramFileName( NewName : String ) : String ;
    function CheckNewDataFileNeeded : Boolean ;
    procedure CreateProtocolListList( var cbList : TComboBox ) ;
    function LoadProtocolList : Boolean ;
    function SaveProtocolList( FileName : String ) : Boolean ;
    function LoadStimulusProtocol(
             Initialise : Boolean              // True = Initialise protocol counter
             ) : Boolean ;
    procedure DisplayClampMode ;

  public
    { Public declarations }
    TimerBusy : boolean ;
    State : TState ;   // Current operational state of module


    procedure SetStoreMode( StorageMode : Boolean ) ;
    procedure ChangeDisplayGrid ;
    procedure ZoomOutAll ;
    procedure ZoomIn( ChanNum : Integer ) ;
    procedure ZoomOut( ChanNum : Integer ) ;

    procedure StartSweep ;
    procedure StopSweep ;


    procedure UpdateStimulusProtocolList ;
    procedure UpdateDisplay ;

    procedure StartRecording ;
    procedure StopRecording ;

    property RecordingMode : Integer read GetRecordingMode write SetRecordingMode ;
    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,
  TritonPanelUnit, DirectorySelectUnit, Replay;
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..WCPMaxChannels-1] of Integer ;
         end ;

var
  ADC : PSmallIntArray ;
  DAC : PSmallIntArray ;
  ADCCopy : PSmallIntArray ;
  ADCDetected : PSmallIntArray ;
  ADCCircular : PSmallIntArray ;
  ADCDisplay : PSmallIntArray ;
  Sum : PIntArray ; { 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 }
  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 ;

     // Display Triton control panel if it is not open
     if (Main.SESLabIO.LabInterfaceType = Triton)
        and (not Main.FormExists( 'TritonPanelFrm' )) then begin
        Main.mnTriton.Enabled := True ;
        Main.mnTriton.Click ;
        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 ;

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

     { Fill combo box with list of available command voltage programs }
     UpdateStimulusProtocolList ;

     // Recording mode selection box
     cbRecordingMode.Clear ;
     cbRecordingMode.Items.Add('Stimulus protocol') ;
     cbRecordingMode.Items.Add('Free run') ;
     cbRecordingMode.Items.Add('Ext. triggered') ;
     cbRecordingMode.Items.Add('Detect events') ;
     cbRecordingMode.ItemIndex := Settings.RecordingMode ;
     SetRecordingMode( cbRecordingMode.ItemIndex ) ;

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

     OldStepCounter := -1 ;

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

     ShowAmplifierGains ;

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

     edNumRecords.Value := Settings.NumRecordsRequired ;
     edRecordDuration.Value := Settings.RecordDuration ;
     edNumSamples.Value := Settings.NumSamples ;
     edNumChannels.Value := Settings.NumChannels ;
     UpdateSamplingInterval ;

     edStatus.font.color := clBlack ;
     TimerBusy := False ;
     State := Idle ;
     EndofSweep := False ;
     StartingPulseProgramIndex := 0 ;

     UpdateDisplay ;

     Timer.Enabled := True ;

     end ;


procedure TRecordFrm.ShowAmplifierGains ;
// --------------------------------------
// Show gain values for amplifiers in use
// --------------------------------------
begin
     if Amplifier.AmplifierType[0] = amNone then panAmplifierGain0.Visible := False
                                      else panAmplifierGain0.Visible := True ;
     if Amplifier.AmplifierType[1] = amNone then panAmplifierGain1.Visible := False
                                      else panAmplifierGain1.Visible := True ;
     if Amplifier.AmplifierType[2] = amNone then panAmplifierGain2.Visible := False
                                      else panAmplifierGain2.Visible := True ;
     if Amplifier.AmplifierType[3] = amNone then panAmplifierGain3.Visible := False
                                      else panAmplifierGain3.Visible := True ;

     if PanAmplifierGain3.Visible then begin
        DisplayGrp.ClientHeight := PanAmplifierGain3.Top
                                   + PanAmplifierGain3.Height + 5 ;
        end
     else if PanAmplifierGain2.Visible then begin
        DisplayGrp.ClientHeight := PanAmplifierGain2.Top
                                   + PanAmplifierGain2.Height + 5 ;
        end
     else if PanAmplifierGain1.Visible then begin
        DisplayGrp.ClientHeight := PanAmplifierGain1.Top
                                   + PanAmplifierGain1.Height + 5 ;
        end
     else begin
        DisplayGrp.ClientHeight := PanAmplifierGain0.Top
                                   + PanAmplifierGain0.Height + 5 ;
        end ;

     end ;


procedure TRecordFrm.UpdateStimulusProtocolList ;
// ----------------------------------
// Update list of stimulus protocols
// ----------------------------------
begin

     { Fill combo box with list of available command voltage programs }
     Stimulator.CreateProtocolList( cbPulseProgram ) ;

     if (Settings.VProgramFileName = '') or
        ANSIContainsText(Settings.VProgramFileName,'\ .') then begin
        cbPulseProgram.ItemIndex := 0 ;
        end
     else begin
        cbPulseProgram.ItemIndex := cbPulseProgram.Items.IndexOf(
                                    ExtractFileNameOnly(Settings.VProgramFileName))
        end ;

     // Update list of protocol lists
     CreateProtocolListList( cbProtocolList ) ;
     if (Settings.ProtocolListFileName = '') or
        ANSIContainsText(Settings.ProtocolListFileName,'\ .') then begin
        cbProtocolList.ItemIndex := 0 ;
        meProtocolList.Clear ;
        end
     else begin
        cbProtocolList.ItemIndex := cbProtocolList.Items.IndexOf(
                                    ExtractFileNameOnly(Settings.ProtocolListFileName)) ;
        LoadProtocolList ;
        end ;

     end ;


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

     if cbRecordingMode.ItemIndex = rmProtocol then begin
        // Stimulus protocol recording mode
        if (ProtocolFileName <> '') and
           FileExists(ProtocolFileName) then begin
           Stimulator.LoadProtocol(ProtocolFileName) ;
           end ;
        end
     else begin
        // All other recording modes
        Main.SESLabIO.ADCNumChannels := Settings.NumChannels ;
        Main.SESLabIO.ADCNumSamples := Settings.NumSamples ;
        Main.SESLabIO.ADCSamplingInterval := Settings.RecordDuration / Settings.NumSamples ;
        Settings.RecordDuration := Main.SESLabIO.ADCSamplingInterval*Main.SESLabIO.ADCNumSamples ;
        end ;

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

     { 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.MaxPoints := Main.SESLabIO.ADCNumSamples ;
     scDisplay.xMin := 0 ;
     scDisplay.xMax := Main.SESLabIO.ADCNumSamples - 1 ;
     scDisplay.NumPoints := 0 ;
     scDisplay.NumChannels := Main.SESLabIO.ADCNumChannels ;

     {Set channel information }
     for ch := 0 to scDisplay.NumChannels-1 do begin
         scDisplay.ChanOffsets[ch] := Main.SESLabIO.ADCChannelOffset[ch] ;
         scDisplay.ChanUnits[ch] := Main.SESLabIO.ADCChannelUnits[ch] ;
         scDisplay.ChanName[ch] := Main.SESLabIO.ADCChannelName[ch] ;
         scDisplay.ChanScale[ch] := Main.SESLabIO.ADCChannelUnitsPerBit[ch] ;
         scDisplay.yMin[ch] := Main.SESLabIO.ADCChannelYMin[ch] ;
         scDisplay.yMax[ch] := Main.SESLabIO.ADCChannelYMax[ch] ;
         scDisplay.ChanVisible[ch] := Main.SESLabIO.ADCChannelVisible[ch] ;
         end ;

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

     scDisplay.TScale := Main.SESLabIO.ADCSamplingInterval*Settings.TScale ;
     scDisplay.TUnits := Settings.TUnits ;

     SetStoreMode(Main.mnStoreTraces.Checked) ;

     { Set A/D buffer to zero }
     EndofBuf := (Main.SESLabIO.ADCNumSamples*Main.SESLabIO.ADCNumChannels) - 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,Main.SESLabIO.ADCNumSamples-1) ;

     end ;


function TRecordFrm.UpdateVProgramFileName( NewName : String ) : String ;
begin

    Result := Settings.VProtDirectory + NewName + '.xml' ;
    if ANSIContainsText(Result,'\.') or
       ANSIContainsText(Result,'\ .') then Result := '' ;
    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 cbRecordingMode.ItemIndex = rmDetect 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*Main.SESLabIO.ADCNumChannels ;
                         i1 :=  i0 + Main.SESLabIO.ADCNumChannels - 1 ;
                         for i := i0 to i1 do ADCDisplay^[i] := ADC^[i] ;
                         Inc(NumBlocksDone) ;
                         NextBlock := NextBlock + Main.SESLabIO.ADCNumChannels ;
                         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*Main.SESLabIO.ADCNumChannels ;
                         i1 :=  i0 + Main.SESLabIO.ADCNumChannels - 1 ;
                         for i := i0 to i1 do ADCDisplay^[i] := ADC^[i] ;
                         Inc(NumBlocksDone) ;
                         NextBlock := NextBlock + Main.SESLabIO.ADCNumChannels ;
                         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*Main.SESLabIO.ADCNumChannels)) 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*Main.SESLabIO.ADCNumChannels) then
                  scDisplay.DisplayNewPoints( NumBlocksDone-1 ) ;

               { End of sweep processing }
               if EndOfSweep then begin

                  { Stop A/D conversion ... unless in detect event mode }
                  if cbRecordingMode.ItemIndex <> rmDetect 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 cbRecordingMode.ItemIndex = rmProtocol then begin
                     { Voltage pulse mode }

                     // Increment to next waveform
                     Stimulator.NextWaveform ;

                     if Stimulator.EndOfProtocol 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 list OR
                          stop recording if no new protocol available }
                        if LoadStimulusProtocol(False) then begin
                           if CheckNewdataFileNeeded then StartSweep
                                                     else StopSweep ;
                           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 (edNumRecords.text = '')
                 and (cbRecordingMode.ItemIndex <> rmProtocol ) then
                 edNumRecords.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 ;
           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

     { Start recording sweep }

     // Set A/D acquisition mode

     case cbRecordingMode.ItemIndex of

        // Detect events mode
        rmDetect : begin
          InitialiseEventDetector ;
          RawFH.NumSamples := Settings.NumSamples ;
          end ;

        // Pulse generation mode
        rmProtocol : begin
          Main.SESLabIO.GetADCBuffer( ADC ) ;
          if Main.SESLabIO.ADCActive then Main.SESLabIO.ADCStop ;
          Main.SESLabIO.ADCTriggerMode := tmWaveGen ;
          Main.SESLabIO.ADCExternalTriggerActiveHigh := False ;
//          Main.SESLabIO.ADCVoltageRangeIndex := Settings.ADCVoltageRangeIndex ;
          // Set A/D and D/A update intervals
          //Stimulator.SetADCDACUpdateIntervals( Stimulator.Prot ) ;
          Main.SESLabIO.ADCNumChannels := Stimulator.Prot.NumADCChannels ;
          Main.SESLabIO.ADCNumSamples := Stimulator.Prot.NumADCSamplesPerChannel ;
          if Stimulator.Prot.ADCSamplingIntervalKeepFixed then begin
             Main.SESLabIO.ADCSamplingInterval := Stimulator.Prot.RecordDuration/Main.SESLabIO.ADCNumSamples ;
             end
          else begin
             Main.SESLabIO.ADCSamplingInterval := Stimulator.Prot.ADCSamplingInterval
             end ;
          Stimulator.SetADCDACUpdateIntervals( Stimulator.Prot ) ;
          Stimulator.Prot.RecordDuration := Main.SESLabIO.ADCNumSamples*Main.SESLabIO.ADCSamplingInterval ;
          Main.SESLabIO.ADCCircularBuffer := False ;
          EndofBuf := (Main.SESLabIO.ADCNumSamples*Main.SESLabIO.ADCNumChannels) - 1;
          RawFH.NumSamples := Main.SESLabIO.ADCNumSamples ;
          Main.SESLabIO.ADCStart ;
          //outputdebugString(PChar(format('B Prog.DACdt :=  %.8g ',[Stimulator.Prog.DACdt]))) ;
          end ;

        // Wait for external trigger
        rmExtTrig : begin
          Main.SESLabIO.GetADCBuffer( ADC ) ;
          if Main.SESLabIO.ADCActive then Main.SESLabIO.ADCStop ;
          Main.SESLabIO.ADCTriggerMode := tmExtTrigger ;
          Main.SESLabIO.ADCExternalTriggerActiveHigh := rbExtTriggerHigh.checked ;
//          Main.SESLabIO.ADCVoltageRangeIndex := Settings.ADCVoltageRangeIndex ;
          Main.SESLabIO.ADCNumChannels := Settings.NumChannels ;
          Main.SESLabIO.ADCNumSamples := Settings.NumSamples ;
          Main.SESLabIO.ADCSamplingInterval := Settings.RecordDuration / Settings.NumSamples ;
          Main.SESLabIO.ADCCircularBuffer := False ;
          EndofBuf := (Main.SESLabIO.ADCNumSamples*Main.SESLabIO.ADCNumChannels) - 1;
          RawFH.NumSamples := Main.SESLabIO.ADCNumSamples ;
          Main.SESLabIO.ADCStart ;
          end ;

        // Free run mode
        else begin
          Main.SESLabIO.GetADCBuffer( ADC ) ;
          if Main.SESLabIO.ADCActive then Main.SESLabIO.ADCStop ;
          Main.SESLabIO.ADCTriggerMode := tmFreeRun ;
//          Main.SESLabIO.ADCVoltageRangeIndex := Settings.ADCVoltageRangeIndex ;
          Main.SESLabIO.ADCNumChannels := Settings.NumChannels ;
          Main.SESLabIO.ADCNumSamples := Settings.NumSamples ;
          Main.SESLabIO.ADCSamplingInterval := Settings.RecordDuration / Settings.NumSamples ;
          RawFH.NumSamples := Main.SESLabIO.ADCNumSamples ;
          Main.SESLabIO.ADCCircularBuffer := False ;
          EndofBuf := (Main.SESLabIO.ADCNumSamples*Main.SESLabIO.ADCNumChannels) - 1;
          RawFH.NumSamples := Main.SESLabIO.ADCNumSamples ;
          Main.SESLabIO.ADCStart ;
          end ;

        end ;

     // Initialise recording sweep pointers/flags
     NextBlock := (2*Main.SESLabIO.ADCNumChannels)-1 ;
     NumBlocksDone := 0 ;
     EndOfSweepCount := 0 ;
     NewSweep := True ;
     EndOfSweep := False ;

     // Update file header information

     RawFH.MaxADCValue := Main.SESLabIO.ADCMaxValue ;
     RawFH.MinADCValue := -RawFH.MaxADCValue -1 ;

     RawFH.NumChannels := Main.SESLabIO.ADCNumChannels ;
     RawFH.NumSamplesPerRecord := RawFH.NumSamples*RawFH.NumChannels ;
     RawFH.NumDataBytesPerRecord := RawFH.NumSamplesPerRecord*2 ;
     RawFH.NumBytesPerRecord := RawFH.NumDataBytesPerRecord + NumAnalysisBytesPerRecord(RawFH.NumChannels) ;
     RawFH.NumBytesInHeader := NumBytesInFileHeader(RawFH.NumChannels) ;
     RawFH.ADCVoltageRange := Main.SESLabIO.ADCVoltageRange ;
     RawfH.dt := Main.SESLabIO.ADCSamplingInterval ;
     scDisplay.TScale := Main.SESLabIO.ADCSamplingInterval*Settings.TScale ;

     // Update file channel settings with recording settings
     for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin
        Channel[ch].ADCName := Main.SESLabIO.ADCChannelName[ch] ;
        Channel[ch].ChannelOffset := Main.SESLabIO.ADCChannelOffset[ch] ;
        Channel[ch].ADCUnits := Main.SESLabIO.ADCChannelUnits[ch] ;
        Channel[ch].ADCCalibrationFactor := Main.SESLabIO.ADCChannelVoltsPerUnit[ch] ;
        Channel[ch].ADCAmplifierGain := Main.SESLabIO.ADCChannelGain[ch] ;
        Channel[ch].ADCZero := Main.SESLabIO.ADCChannelZero[ch] ;
        Channel[ch].InUse := Main.SESLabIO.ADCChannelVisible[ch] ;
        Channel[ch].yMin := Main.SESLabIO.ADCChannelYMin[ch] ;
        Channel[ch].yMax := Main.SESLabIO.ADCChannelYMax[ch] ;
        end ;

     { Start voltage/digital output program (if needed) }
     if cbRecordingMode.ItemIndex = rmProtocol 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
// --------------------
var
    ch : Integer ;
begin
    { Stop recording and return to idle state }

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

    { Return voltage command to holding voltage and Sync. O/P to OFF }
    for ch := 0 to Main.SESLabIO.DACMaxChannels-1 do
        Main.SESLabIO.DACHoldingVoltage[ch] := Main.SESLabIO.DACHoldingVoltage[ch] ;
    Main.SESLabIO.DIGHoldingLevel := Main.SESLabIO.DIGHoldingLevel ;

    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 ;
        RecordParametersPanel.Enabled := True ;
        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 := Min( Settings.NumSamples*Settings.NumChannels*4,
                             Main.SESLabIO.ADCBufferLimit)  ;
        Det.EndofBuf := ((Main.SESLabIO.ADCBufferLimit div Settings.NumChannels)*Settings.NumChannels) - 1 ;

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

        { 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
  ------------------------------------------------------------ }
var
    NumAOChannels : Integer ;
begin

     { Show which step/repeat is being recorded }

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

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

     if Stimulator.LeakRecord then begin
        // Create leak waveform
        edStatus.text := format( 'Rec %d %s',
                                 [RawFH.NumRecords,
                                 Stimulator.ProtocolStatus ]) ;
        NumAOChannels :=  Min( Stimulator.Prot.NumAOChannels,
                               Main.SESLabIO.DACMaxChannels ) ;
        Main.SESLabIO.DACNumSamples := Stimulator.CreateWaveform( DAC^,
                                                                  DigBuf^,
                                                                  NumAOChannels ) ;
        Main.SESLabIO.DACUpdateInterval := Stimulator.DACUpdateInterval ;
        Main.SESLabIO.DACNumSamples := Stimulator.CreateLeakWaveform( DAC^, NumAOChannels ) ;
        Main.SESLabIO.DigitalStimulusEnabled := False ;
        end
      else begin
        // Create TEST voltage program waveform
        edStatus.text := format( 'Rec %d %s',
                                 [RawFH.NumRecords,
                                 Stimulator.ProtocolStatus ]) ;
        NumAOChannels :=  Min( Stimulator.Prot.NumAOChannels,
                               Main.SESLabIO.DACMaxChannels ) ;
        Main.SESLabIO.DACNumSamples := Stimulator.CreateWaveform( DAC^,
                                                                  DigBuf^,
                                                                  NumAOChannels ) ;
        Main.SESLabIO.DACUpdateInterval := Stimulator.DACUpdateInterval ;
        Main.SESLabIO.DigitalStimulusEnabled := Stimulator.DigitalOutputsInUse ;
        end ;

     { Set up D/A conversion  }
     Main.SESLabIO.DACNumChannels := NumAOChannels ;
     { Set voltage stimulus pulse interval }
     if Stimulator.Prot.ExtTrigger then begin
        // Externally triggered stimuli
        Main.SESLabIO.TimerPeriod := StimulusExtTriggerFlag ;
        end
     else begin
        // Periodic stimuli
        Main.SESLabIO.TimerPeriod := Stimulator.Prot.StimulusPeriod ;
        end ;
     Main.StatusBar.Panels[1].Text := '' ;

     if not Main.SESLabIO.TimerActive then begin
        Main.SESLabIO.StartTimer ;
        end ;

     end ;


procedure TRecordFrm.ShutDownLabInterface ;
{ ---------------------------------------------
  Shut down A/D, D/A and digital O/P subsystems
  ---------------------------------------------}
var
    ch : Integer ;
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 }
    { Return voltage command to holding voltage and Sync. O/P to OFF }
    for ch := 0 to Main.SESLabIO.DACMaxChannels-1 do
        Main.SESLabIO.DACHoldingVoltage[ch] := Main.SESLabIO.DACHoldingVoltage[ch] ;
    Main.SESLabIO.DIGHoldingLevel := Main.SESLabIO.DIGHoldingLevel ;

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


procedure TRecordFrm.SaveSweep ;
{ ---------------------------
  Save recording sweep to file
  --------------------------- }
var
   i,NumAvg,iStart : Integer ;
   Done,SaveRecord : 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 Main.SESLabIO.ADCNumChannels)*Main.SESLabIO.ADCNumChannels ;
        iStart := Max(iStart,0) ;
        for i := iStart to EndOfBuf do
            ADC^[i] := ADC^[iStart + (i mod Main.SESLabIO.ADCNumChannels)] ;
        end ;

     { Special processing if this is a leak current record }
     if Stimulator.LeakRecord then begin
        // Clear leak average if first leak record
        if Stimulator.RepeatCounter = 0 then for i := 0 to EndOfBuf do Sum^[i] := 0 ;
        // Add leak record to average
        for i := 0 to EndOfBuf do Sum^[i] := Sum^[i] + ADC^[i] ;
        // Calculate average leak record (and request to be saved)
        if Stimulator.RepeatCounter >= Abs(Stimulator.Prot.NumLeakSubtractionRecords)-1 then begin
           NumAvg := Max( Stimulator.Prot.NumLeakSubtractionRecords,1 ) ;
           for i := 0 to EndOfBuf do ADC^[i] := Sum^[i] div NumAvg ;
           rH.RecType := 'LEAK' ;
           SaveRecord := True ;
           end
        else SaveRecord := False ;
        end
     else begin
        rH.RecType := 'TEST' ;
        SaveRecord := True ;
        end ;

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

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

        { Update time that record was collected }
        if cbRecordingMode.itemIndex = rmDetect then rH.Time := Det.TimeDetected
                                                else rH.Time := GetTickCount*mStoSecs ;

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

        rH.AnalysisAvailable := false ;
        rH.EqnType := None ;
        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],
                                  scDisplay.VerticalCursors[C3Cursor],
                                  scDisplay.VerticalCursors[C4Cursor],
                                  cbPulseProgram.Text,
                                  RawfH.NumRecords ) ;
           end ;

        // Update file header
        SaveHeader( RawfH ) ;

        // Update replay form if it is open
        if Main.FormExists('ReplayFrm') then begin
           Main.mnShowRaw.Click ;
           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 ;
   Name,Units : string ;
   VPU,Gain : Single ;
begin

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

         OldValue := RH.ADCVoltageRange[ch] ;
         OldUnits := Main.SESLabIO.ADCChannelUnits[ch] ;
         OldName := Main.SESLabIO.ADCChannelName[ch] ;

         // Update channel scaling factors
         Amplifier.GetChannelSettings( ch,Name,Units,VPU,Gain ) ;
         Main.SESLabIO.ADCChannelName[ch] := Name ;
         Main.SESLabIO.ADCChannelUnits[ch] := Units ;
         Main.SESLabIO.ADCChannelVoltsPerUnit[ch] := VPU ;
         Main.SESLabIO.ADCChannelGain[ch] := Gain ;

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

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

         end ;

     UpdateAmplifierGain ;

     // Update current/voltage clamp state
     DisplayClampMode ;

     // Update display readout scaling factors
     for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin
         scDisplay.ChanScale[ch] := Main.SESLabIO.ADCChannelUnitsPerBit[ch] ;
         scDisplay.ChanUnits[ch] := Main.SESLabIO.ADCChannelUnits[ch] ;
         scDisplay.ChanName[ch] := Main.SESLabIO.ADCChannelName[ch] ;
         end ;

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


     end ;


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

    if panAmplifierGain0.Visible and
       (Amplifier.GainTelegraphAvailable[0] or NewAmplifierGain) then begin
       edAmplifierGain0.Units := 'V/' + Amplifier.PrimaryChannelUnits[0,Amplifier.ClampMode[0]] ;
       edAmplifierGain0.Value := Amplifier.PrimaryChannelScaleFactor[0] ;
       end ;

    if panAmplifierGain1.Visible and
       (Amplifier.GainTelegraphAvailable[1] or NewAmplifierGain) then begin
       edAmplifierGain1.Units := 'V/' + Amplifier.PrimaryChannelUnits[1,Amplifier.ClampMode[1]] ;
       edAmplifierGain1.Value := Amplifier.PrimaryChannelScaleFactor[1] ;
       end ;

    if panAmplifierGain2.Visible and
       (Amplifier.GainTelegraphAvailable[2] or NewAmplifierGain) then begin
       edAmplifierGain2.Units := 'V/' + Amplifier.PrimaryChannelUnits[2,Amplifier.ClampMode[2]] ;
       edAmplifierGain2.Value := Amplifier.PrimaryChannelScaleFactor[2] ;
       end ;

    if panAmplifierGain3.Visible and
       (Amplifier.GainTelegraphAvailable[3] or NewAmplifierGain) then begin
       edAmplifierGain3.Units := 'V/' + Amplifier.PrimaryChannelUnits[3,Amplifier.ClampMode[3]] ;
       edAmplifierGain3.Value := Amplifier.PrimaryChannelScaleFactor[3] ;
       end ;

     NewAmplifierGain := False ;

     end ;


procedure TRecordFrm.bRecordClick(Sender: TObject);
{ -----------------------
  Start recording sesson
  ----------------------- }
begin

     if Main.FormExists( 'SealTestFrm' ) then begin
        SealTestFrm.StopADCandDAC ;
        end ;

     // Update amplifier gains (in case they have been changed by user)
     UpdateAmplifierGains ;

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

     // 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 sweeps to be collected }
     UpdateNumSamples ;       // Ensure no. of samples is valid (multiple of 256)
     UpdateSamplingInterval ; // Ensure sampling interval is in supported range

     Settings.NumRecordsRequired := Round(edNumRecords.Value) ;
     Settings.NumChannels := Round(edNumChannels.Value) ;
     Settings.RecordDuration := edRecordDuration.Value ;
     Settings.NumSamples := Round(edNumSamples.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 cbRecordingMode.ItemIndex = rmProtocol then begin
        if not LoadStimulusProtocol( True ) then begin
           ShowMessage( ' No Stimulus Protocol - Recording Aborted! ' ) ;
           State := Idle ;
           bRecord.Enabled := True ;
           bStop.Enabled := False ;
           MainMenus( Enable ) ;
           Exit ;
           end ;
        OldStepCounter := 0 ;
        end ;

     // Update display with new settings
     UpdateDisplay ;

     // Check if a new data file is need due to changes in data file record size
     // or calibration factors etc. and create file if required

     if not CheckNewDataFileNeeded then begin
        // Stop if unable to create file
        State := Idle ;
        bRecord.Enabled := True ;
        bStop.Enabled := False ;
        MainMenus( Enable ) ;
        Exit ;
        end ;

     StartSweep ;

     if State = Idle then begin
        bRecord.Enabled := True ;
        bStop.Enabled := False ;
        MainMenus( Enable ) ;
        end ;

     end;

function TRecordFrm.LoadStimulusProtocol(
         Initialise : Boolean              // True = Initialise protocol counter
         ) : Boolean ;
// ----------------------
// Load stimulus protocol
// ----------------------
begin

    if Initialise then ProtocolCount := 0 ;

    Result := False ;

    if rbSingleProtocol.Checked then begin
       // Load a single protocol (or subsequent linked protocols)
       if ProtocolCount < 1 then begin
          // Load initial protocol from list
          ProtocolFileName := cbPulseProgram.Text ;
          Settings.VProgramFileName := UpdateVProgramFileName(ProtocolFileName) ;
          end
       else begin
          // Load linked protocol (if it exists)
          ProtocolCount := 0 ;
          ProtocolFileName := ExtractFileNameOnly(Stimulator.Prot.NextProtocolFileName) ;
          if FileExists(UpdateVProgramFileName(ProtocolFileName)) then begin
             if Main.SESLabIO.DACActive then Main.SESLabIO.DACStop ;
             end
          else ProtocolFileName := '' ;
          end ;
       end
    else begin
       // Get protocol from execution list
       if ProtocolCount < meProtocolList.Lines.Count then begin
          ProtocolFileName := meProtocolList.Lines[ProtocolCount] ;
          end
       else ProtocolFileName := ''
       end ;

    if (ProtocolFileName = '') or (ProtocolFileName = ' ') then begin
       Exit ;
       end ;

    ProtocolFileName := UpdateVProgramFileName(ProtocolFileName) ;

    if FileExists( ProtocolFileName ) then begin
       Stimulator.LoadProtocol(ProtocolFileName) ;
       WriteToLogFile( 'Stimulus Protocol: ' + ProtocolFileName ) ;
       Result := True ;
       end
    else Exit ;

    Inc( ProtocolCount ) ;

    end ;


function TRecordFrm.CheckNewDataFileNeeded : Boolean ;
// ------------------------------------------------------------
// Check record size and created a new file if size has changed
// ------------------------------------------------------------
var
    NewFileNeeded : Boolean ;
    ch : Integer ;
    NewFileName : String ;
begin

     NewFileNeeded := False ;
     Result := True ;

     // Not required if file is empty
     if RawFH.NumRecords <= 0 then Exit ;

     if RawFH.NumChannels <> Main.SESLabIO.ADCNumChannels then NewFileNeeded := True ;
     if RawFH.NumSamples <> Main.SESLabIO.ADCNumSamples then NewFileNeeded := True ;

     for ch := 0 to Main.SESLabIO.ADCNumChannels-1 do begin
         if Channel[ch].ADCName <> Main.SESLabIO.ADCChannelName[ch] then NewFileNeeded := True ;
         if Channel[ch].ADCUnits <> Main.SESLabIO.ADCChannelUnits[ch] then NewFileNeeded := True ;
         if Channel[ch].ADCCalibrationFactor <> Main.SESLabIO.ADCChannelVoltsPerUnit[ch] then NewFileNeeded := True ;
        end ;

     if NewFileNeeded then begin
        // New file with '+' added to end of file name
        NewFileName := ANSIReplaceText( RawFH.FileName,'.wcp','+.wcp') ;
        if not FileExists(NewFileName) then begin
           Main.CreateNewDataFile( NewFileName ) ;
           end
        else begin
           ShowMessage('Additional data file ' + NewFileName + ' already exists. New data file required!') ;
           Result := False ;
           end ;

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

     OnLineAnalysisGrp.Top := TimerGrp.Top - OnLineAnalysisGrp.Height - 2 ;

     DisplayGrp.Top := OnLineAnalysisGrp.Top - DisplayGrp.Height - 2 ;

     RecordModeGrp.Height := Max(DisplayGrp.Top - RecordModeGrp.Top - 2,2) ;

     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 ;
     RecordParametersPanel.Enabled := 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 }
     if cbPulseProgram.ItemIndex > 0 then begin
        Settings.VProgramFileName := UpdateVProgramFileName(cbPulseProgram.text) ;
        if FileExists( Settings.VProgramFileName ) then begin
           Stimulator.LoadProtocol(Settings.VProgramFileName) ;
           end ;
        end
     else Settings.VProgramFileName := '' ;

     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.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 := True ;//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
         Main.SESLabIO.ADCChannelZero[ch] := 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
         Main.SESLabIO.ADCChannelYMin[ch] := scDisplay.YMin[Ch] ;
         Main.SESLabIO.ADCChannelYMax[ch] := scDisplay.YMax[Ch] ;
         Main.SESLabIO.ADCChannelVisible[ch] := scDisplay.ChanVisible[ch]
         end ;

     end;


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

     for ch := 0 to scDisplay.NumChannels-1 do begin
         scDisplay.ChanVisible[ch] := Main.SESLabIO.ADCChannelVisible[ch] ; ;
         end ;

     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 Main.SESLabIO.ADCNumChannels-1 do begin
         Main.SESLabIO.ADCChannelYMin[i] := Main.SESLabIO.ADCMinValue ;
         Main.SESLabIO.ADCChannelYMax[i] := 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.edAmplifierGain0KeyPress(Sender: TObject;
  var Key: Char);
// ---------------------------------------
// Amplifier gain changed - update display
// ---------------------------------------
begin
     if Key = #13 then UpdateAmplifierGains ;
     end;


procedure TRecordFrm.UpdateAmplifierGains ;
// -----------------------------------------------
// Update amplifier gains with user-entered values
// -----------------------------------------------
begin
    if panAmplifierGain0.Visible then
       Amplifier.PrimaryChannelScaleFactor[0] := edAmplifierGain0.Value ;
    if panAmplifierGain1.Visible then
       Amplifier.PrimaryChannelScaleFactor[1] := edAmplifierGain1.Value ;
    if panAmplifierGain2.Visible then
       Amplifier.PrimaryChannelScaleFactor[2] := edAmplifierGain2.Value ;
    if panAmplifierGain3.Visible then
       Amplifier.PrimaryChannelScaleFactor[3] := edAmplifierGain3.Value ;
    NewAmplifierGain := True ;
    end ;


function TRecordFrm.GetRecordingMode : Integer ;
// ----------------
// Get recording mode
// ----------------
begin
     Result := cbRecordingMode.ItemIndex ;
     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 := UpdateVProgramFileName(cbPulseProgram.text) ;
       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

     // Ensure display channels visibility is updated
     ChangeDisplayGrid ;

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

     ShowAmplifierGains ;
     NewAmplifierGain := True ;

     end;

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

     scDisplay.ClearVerticalCursors ;
     C0Cursor := scDisplay.AddVerticalCursor(-1,clOlive,'0') ;
     scDisplay.VerticalCursors[C0Cursor] := 1 ;

     C1Cursor := scDisplay.AddVerticalCursor(-1,clGreen,'1') ;
     scDisplay.VerticalCursors[C1Cursor] := Main.SESLabIO.ADCNumSamples div 20 ;
     C2Cursor := scDisplay.AddVerticalCursor(-1,clGreen,'2') ;
     scDisplay.VerticalCursors[C2Cursor] := (Main.SESLabIO.ADCNumSamples*8) div 10 ;
     scDisplay.LinkVerticalCursors(C1Cursor,C2Cursor) ;

     C3Cursor := scDisplay.AddVerticalCursor(-1,clGreen,'3') ;
     scDisplay.VerticalCursors[C3Cursor] := (Main.SESLabIO.ADCNumSamples*9) div 10 ;
     C4Cursor := scDisplay.AddVerticalCursor(-1,clGreen,'4') ;
     scDisplay.VerticalCursors[C4Cursor] := (Main.SESLabIO.ADCNumSamples*19) div 20 ;
     scDisplay.LinkVerticalCursors(C3Cursor,C4Cursor) ;

     scDisplay.Invalidate ;

     if not Main.FormExists( 'RecPlotFrm' ) then begin
        RecPlotFrm := TRecPlotFrm.Create(Self) ;
        // Make space for on-line window
        RecordFrm.Width := Min( (Main.ClientWidth*2) div 3, RecordFrm.Width) ;
        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
    DirectorySelectFrm.Directory := Settings.VProtDirectory ;
    DirectorySelectFrm.Left := Main.Left +
                               RecordFrm.Left +
                               bSetStimFolder.Left +
                               bSetStimFolder.Width + 20 ;
    DirectorySelectFrm.Top := Main.Top +
                              RecordFrm.Top +
                              bSetStimFolder.Top + 10 ;

    DirectorySelectFrm.ShowModal ;
    if DirectorySelectFrm.ModalResult = mrOK then begin
       Settings.VProtDirectory := DirectorySelectFrm.Directory ;
       UpdateStimulusProtocolList ;
       end ;
     end ;

     
procedure TRecordFrm.cbRecordingModeChange(Sender: TObject);
// ----------------------
// Recording mode changed
// ----------------------
begin
    SetRecordingMode( cbRecordingMode.ItemIndex ) ;
    end;

procedure TRecordFrm.SetRecordingMode( iMode : Integer ) ;
// -------------------------
// Set recording mode panels
// -------------------------
begin

     cbRecordingMode.ItemIndex := iMode ;

     { Set trigger mode }

     // detect mode not available with Tecella
     if (Main.SESLabIO.LabInterfaceType = Triton) and
        (cbRecordingMode.ItemIndex = rmDetect) then cbRecordingMode.ItemIndex := rmProtocol ;

     Settings.RecordingMode := cbRecordingMode.ItemIndex ;

     case cbRecordingMode.ItemIndex of
        rmProtocol : Begin
            RecordParametersPanel.Visible := False ;
            panProtocol.Visible := True ;
            panSingleProtocol.Visible := rbSingleProtocol.Checked ;
            panProtocolList.Visible := rbProtocolList.Checked ;
            LoadProtocolList ;
            end ;
        rmFreeRun : Begin
            RecordParametersPanel.Visible := True ;
            panProtocol.Visible := False ;
            ExtTriggerGrp.Visible := False ;
            DetectGrp.Visible := False ;
            end ;
        rmExtTrig : Begin
            RecordParametersPanel.Visible := True ;
            panProtocol.Visible := False ;
            ExtTriggerGrp.Visible := True ;
            DetectGrp.Visible := False ;
            end ;
        rmDetect : Begin
            RecordParametersPanel.Visible := True ;
            panProtocol.Visible := False ;
            ExtTriggerGrp.Visible := False ;
            DetectGrp.Visible := True ;
            end ;
        end ;

     UpdateDisplay ;

     end ;

procedure TRecordFrm.UpdateNumSamples ;
{ --------------------------------------------
  Set number of samples per A/D input channels
  -------------------------------------------- }
const
     nMin = 256 ;
var
   n,nMax : Integer ;
begin

    Main.SESLabIO.ADCNumChannels := Round(edNumChannels.Value) ;
    edNumChannels.Value :=  Round(Main.SESLabIO.ADCNumChannels) ;

    nMax := Main.SESLabIO.ADCBufferLimit div Main.SESLabIO.ADCNumChannels ;
    n := Round(edNumSamples.Value) ;
    n := Min(Max(n,nMin),nMax) ;
    { Make a multiple of nMim }
    n := (n div nMin)*nMin ;
    edNumSamples.Value := n ;
    Main.SESLabIO.ADCNumSamples := n ;
    Settings.NumSamples := n ;
    end ;


procedure TRecordFrm.edNumChannelsKeyPress(Sender: TObject; var Key: Char);
// -----------------------
// No. of channels changed
// -----------------------
begin
     if Key = #13 then begin
          Settings.NumChannels := Round(edNumChannels.Value) ;
          Main.SESLabIO.ADCNumChannels := Round(edNumChannels.Value) ;
          edNumChannels.Value := Main.SESLabIO.ADCNumChannels ;
          UpdateSamplingInterval ;
          UpdateDisplay ;
          end ;
     end;

procedure TRecordFrm.edNumSamplesKeyPress(Sender: TObject; var Key: Char);
// ------------------------------
// No. of samples/channel changed
// ------------------------------
begin
     if Key = #13 then begin
        UpdateNumSamples ;
        UpdateSamplingInterval ;
        UpdateDisplay ;
        end ;
     end;

procedure TRecordFrm.UpdateSamplingInterval ;
{ ----------------------------
  Update A/D sampling interval
  ---------------------------- }
begin

     Main.SESLabIO.ADCNumChannels := Round(edNumChannels.Value) ;
     edNumChannels.Value := Main.SESLabIO.ADCNumChannels ;

     // Compute sampling interval
     Main.SESLabIO.ADCSamplingInterval := edRecordDuration.Value /
                                          Main.SESLabIO.ADCNumSamples ;

     // Get validated in interval back from interface
     edSamplingInterval.Value := Main.SESLabIO.ADCSamplingInterval ;

     edRecordDuration.Value := edSamplingInterval.Value*edNumSamples.Value ;

     end ;



procedure TRecordFrm.edRecordDurationKeyPress(Sender: TObject;
  var Key: Char);
begin
     if Key = #13 then begin
        Settings.RecordDuration := edRecordDuration.Value ;
        UpdateSamplingInterval ;
        UpdateDisplay ;
        end ;
     end;

procedure TRecordFrm.edNumRecordsKeyPress(Sender: TObject; var Key: Char);
begin
     if Key = #13 then begin
        Settings.NumRecordsRequired := Round(edNumRecords.Value) ;
        end ;
     end;

procedure TRecordFrm.edSamplingIntervalKeyPress(Sender: TObject;
  var Key: Char);
begin
     if Key = #13 then begin
        edRecordDuration.Value := edSamplingInterval.Value*edNumSAmples.Value ;
        UpdateSamplingInterval ;
        end ;
     end;

procedure TRecordFrm.rbSingleProtocolClick(Sender: TObject);
begin
    SetRecordingMode( cbRecordingMode.ItemIndex ) ;
    end;

procedure TRecordFrm.rbProtocolListClick(Sender: TObject);
begin
    SetRecordingMode( cbRecordingMode.ItemIndex ) ;
    end;


procedure TRecordFrm.CreateProtocolListList(
          var cbList : TComboBox
          ) ;
{ --------------------------------------------
  Compile a list of protocol list (*.lst) files in the protocol directory
  and put the file names into a combo box
  --------------------------------------------------------------}
var
   SearchRec : TSearchRec ;
   First : Boolean ;
   FileFound : Integer ;
begin

     First := True ;
     cbList.Clear ;
     cbList.items.add( ' ' ) ;
     repeat
        { Find file }
        if First then
           FileFound := FindFirst( Settings.VProtDirectory + '*.lst',
                                   faAnyFile,
                                   SearchRec )
        else
           FileFound := FindNext( SearchRec ) ;

        { Add file name (no extension or path) to list }
        if FileFound = 0 then cbList.items.Add(ExtractFileNameOnly(SearchRec.Name))
                        else FindClose(SearchRec.FindHandle) ;
        First := False ;
        Until FileFound <> 0 ;

     end ;


function TRecordFrm.LoadProtocolList ;
// ----------------------------------------------------
// Load list of protocols to be executed from .LST file
// ----------------------------------------------------
var
    F: TextFile;
    s : String ;
begin

     Result := False ;

     if (cbProtocolList.ItemIndex <= 0) or (cbProtocolList.Items.Count <= 0) then begin
        meProtocolList.Clear ;
        Settings.ProtocolListFileName := '' ;
        Exit ;
        end ;

     Settings.ProtocolListFileName := cbProtocolList.Items[cbProtocolList.ItemIndex] ;
     if (Settings.ProtocolListFileName = '') or
        (Settings.ProtocolListFileName = ' ') then begin
        meProtocolList.Clear ;
        Settings.ProtocolListFileName := '' ;
        Exit ;
        end ;

     Settings.ProtocolListFileName := Settings.VProtDirectory +
                                      ExtractFileName(ChangeFileExt(Settings.ProtocolListFileName,'.lst')) ;

     if not FileExists(Settings.ProtocolListFileName) then begin
        meProtocolList.Clear ;
        Settings.ProtocolListFileName := '' ;
        Exit ;
        end ;

     // Open text input file
     AssignFile(F, Settings.ProtocolListFileName);
     Reset(F);

     meProtocolList.Lines.Clear ;
     repeat
         // Read line
         ReadLn( F, s ) ;
         meProtocolList.Lines.Add(ChangeFileExt(ExtractFileName(s),'')) ;
         until EOF(F)  ;

    CloseFile(F) ;

    Result := True ;

    end ;


function TRecordFrm.SaveProtocolList(
         FileName : String
         ) : Boolean ;
// --------------------------------------------------
// Save list of protocols to be executed to .LST file
// --------------------------------------------------
var
    F: TextFile;
    i : Integer ;
begin

     if FileName = '' then begin
        Result := False ;
        Exit ;
        end ;

     FileName := Settings.VProtDirectory + ExtractFileName(ChangeFileExt(FileName,'.lst')) ;

     // Open text file for output
     AssignFile(F, FileName);
     Rewrite(F);

     for i := 0 to meProtocolList.Lines.Count-1 do begin
         WriteLn( F, meProtocolList.Lines[i] ) ;
         end ;

    CloseFile(F) ;

    Result := True ;

    end ;


procedure TRecordFrm.cbProtocolListChange(Sender: TObject);
// ---------------------
// Protocol list changed
// ---------------------
begin
    LoadProtocolList ;
    end;

    
procedure TRecordFrm.bAddProtocolToListClick(Sender: TObject);
// --------------------
// Add protocol to list
// --------------------
begin
     OpenDialog.options := [ofOverwritePrompt,ofHideReadOnly,ofPathMustExist] ;
     OpenDialog.FileName := '*.xml' ;//ExtractFileName( SaveDialog.FileName ) ;
     OpenDialog.InitialDir := Settings.VProtDirectory ;
     OpenDialog.Title := 'Add Stimulus Protocol to List' ;
     if OpenDialog.execute then begin
        meProtocolList.Lines.Add('') ;
        meProtocolList.Lines[meProtocolList.Lines.Count-2] :=
           ChangeFileExt(ExtractFileName(OpenDialog.FileName),'') ;
        SaveProtocolList(cbProtocolList.Text) ;
        end ;
     end ;


procedure TRecordFrm.bDeleteProtocolListClick(Sender: TObject);
// --------------------
// Delete protocol list
// --------------------
var
    FileName : String ;
begin
     FileName := cbProtocolList.Text ;
     if FileName = '' then Exit ;
     FileName := Settings.VProtDirectory + ChangeFileExt(FileName,'.lst') ;
     if FileExists(FileName) then begin
        DeleteFile(PChar(FileName)) ;
        cbProtocolList.ItemIndex := 0 ;
        meProtocolList.Clear ;
        UpdateStimulusProtocolList ;
        end ;
     end;


procedure TRecordFrm.bNewProtocolListClick(Sender: TObject);
// -------------------------------------------
// Create new stimulus protocol execution list
// -------------------------------------------
var
    FileName : String ;
    OK : Boolean ;
begin
     FileName := edNewProtocolListName.Text ;
     if FileName = '' then Exit ;
     FileName := Settings.VProtDirectory + ExtractFileName(ChangeFileExt(FileName,'.lst')) ;

     OK := True ;
     if FileExists(FileName) then begin
        if MessageDlg( 'Protocol list already exists! Overwrite it?', mtConfirmation,
          [mbYes,mbNo], 0 ) = mrNo then OK := False ;
        end ;

     if OK then begin
        meProtocolList.Clear ;
        SaveProtocolList( FileName ) ;
        cbProtocolList.Items.Add(edNewProtocolListName.Text);
        cbProtocolList.ItemIndex := cbProtocolList.Items.Count-1 ;
        LoadProtocolList ;
        end ;

     end ;

procedure TRecordFrm.DisplayClampMode ;
// ----------------------------------
// Display current/voltage clamp mode
// ----------------------------------
begin

     if Amplifier.ClampMode[0] = amVoltageClamp then begin
        rbVClamp0.Checked := True ;
        rbIClamp0.Checked := False ;
        end
     else begin
        rbVClamp0.Checked := False ;
        rbIClamp0.Checked := True ;
        end ;

     if Amplifier.ClampMode[1] = amVoltageClamp then begin
        rbVClamp1.Checked := True ;
        rbIClamp1.Checked := False ;
        end
     else begin
        rbVClamp1.Checked := False ;
        rbIClamp1.Checked := True ;
        end ;

     if Amplifier.ClampMode[2] = amVoltageClamp then begin
        rbVClamp2.Checked := True ;
        rbIClamp2.Checked := False ;
        end
     else begin
        rbVClamp2.Checked := False ;
        rbIClamp2.Checked := True ;
        end ;

     if Amplifier.ClampMode[3] = amVoltageClamp then begin
        rbVClamp3.Checked := True ;
        rbIClamp3.Checked := False ;
        end
     else begin
        rbVClamp3.Checked := False ;
        rbIClamp3.Checked := True ;
        end ;

     end ;


procedure TRecordFrm.rbVCLAMP0Click(Sender: TObject);
begin
     Amplifier.ClampMode[TRadioButton(Sender).tag] := amVoltageClamp ;
     DisplayClampMode ;
     end;

procedure TRecordFrm.rbICLAMP0Click(Sender: TObject);
begin
     Amplifier.ClampMode[TRadioButton(Sender).tag] := amCurrentClamp ;
     DisplayClampMode ;
     end;

Initialization
    BuffersAllocated := False ;
end.
