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
  ==============================================================================}
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, ExtCtrls, Global, Shared, FileIO, WavRun, Labio, vds,
  maths, ced1401 ;

type
  TRecordFrm = class(TForm)
    ControlGrp: TGroupBox;
    bRecord: TButton;
    bStop: TButton;
    lbTMin: TLabel;
    lbTMax: TLabel;
    TriggerGrp: TGroupBox;
    rbFreeRun: TRadioButton;
    rbExtTrigger: TRadioButton;
    rbEventDetector: TRadioButton;
    cbDetectChannel: TComboBox;
    Label1: TLabel;
    EdDetectionThreshold: TEdit;
    Label2: TLabel;
    rbPulseProgram: TRadioButton;
    cbPulseProgram: TComboBox;
    ckSaveRecords: TCheckBox;
    Timer1: TTimer;
    DisplayGrp: TGroupBox;
    bErase: TButton;
    edStatus: TEdit;
    edIdent: TEdit;
    Label4: TLabel;
    pbDisplay: TPaintBox;
    TimerGrp: TGroupBox;
    edTimeOfDay: TEdit;
    bResetTimer: TButton;
    EdRecordIdent: TEdit;
    Bevel1: TBevel;
    edPreTrigger: TEdit;
    edRecordsRequired: TEdit;
    Label3: TLabel;
    Label5: TLabel;
    procedure Timer1Timer(Sender: TObject);
    procedure bRecordClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormPaint(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 FormActivate(Sender: TObject);
    procedure pbDisplayDblClick(Sender: TObject);
    procedure pbDisplayMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormShow(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure bEraseClick(Sender: TObject);
    procedure edPreTriggerKeyPress(Sender: TObject; var Key: Char);
  private
    { Private declarations }
    procedure DrawHorizontalCursor( var pb : TPaintBox ;
                                    Const Gr : TChannel ;
                                     Level : Integer ) ;
    procedure SaveSweep ;
    procedure UpdateStopwatch ;
    procedure ShutDownLabInterface ;
    procedure HeapBuffers( Operation : THeapBufferOp ) ;
  public
    { Public declarations }
    TimerBusy : boolean ;
  end;

  TState = ( Idle, StartSweep, SweepInProgress, StopRecording, BlankDisplay ) ;

var
  RecordFrm: TRecordFrm;


implementation

{$R *.DFM}

uses MDiform,Zoom ;
function ReadDACTimer : Word ; far ; external 'dd1200i' ;
type
    TDet = record
         StartAt : LongInt ;
         EndAt : LongInt ;
         PreTriggerPoints : LongInt ;
         Baseline : LongInt ;
         Threshold : Integer ;
         Polarity : Integer ;
         Pointer : Integer ;
         EndOfBuf : LongInt ;
         Chan : Integer ;
         LastChan : Integer ;
         ChanSelected : Integer ;
         EventDetected : boolean ;
         TimeDetected : single ;
         BufferCycles : LongInt ;
         end ;

    TStopwatch = record
               ElapsedTime : TDateTime ;
               Hour : Word ;
               Minute : Word ;
               Second : Word ;
               MSec : Word ;
               TickStart : single ;
               OldSeconds : single ;
               end ;
const

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

var
  ADC : ^TIntArray ;
  ADCHnd : THandle ;
  DAC : ^TIntArray ;
  DACHnd : THandle ;
  DetADCIn : ^TIntArray ;
  DETHnd : THandle ;
  Sum : ^TIntArray ; { Summation buffer used to average LEAK records }
  DigBuf : ^TIntArray ; { Digital port output array }
  State : TState ;   { Current operational state of this module }
  ii,ii1,ii2 : word ;

  DetADCOut : ^TIntArray ;
  Det : TDet ;

  x,dx : Single ;
  NextSample,EndOfBuf : LongInt ;
  yLast : Array[0..ChannelLimit] of LongInt ;
  xyPixOld : Array[0..ChannelLimit] of TPoint ;
  xyPix : TPoint ;
  RH : TRecHeader ;
  NumRecordsDone,EndAtRecord : LongInt ;
  HeightMin : LongInt ;
  ADCActive : Boolean ; { A/D sub-system active }
  DACActive : Boolean ; { D/A sub-system active }
  DIGActive : Boolean ; { Digital port sub-system active }
  NewDigitalPattern : boolean ;

  VProgram : ^TWaveform ;    { Voltage program in current use }
  TimeStart : Single ;      { Time that first record in file was collected }
  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 : longInt ; { Group of TEST and LEAK records produced by same
                            voltage program waveform }
  OldStepCounter : LongInt ; { Previous voltage program step number }
  Stopwatch : TStopwatch ;
  Inactive : boolean ;
  EndOfSweep : boolean ;
  AbortRecording : boolean ;
  MouseOverChannel : Integer ;
  EraseScreen : boolean ;
  BuffersAllocated : boolean ;{ Indicates if memory buffers have been allocated }
  ADCPointer : LongInt ;


procedure TRecordFrm.HeapBuffers( Operation : THeapBufferOp ) ;
{ -----------------------------------------------
  Allocate/deallocation dynamic buffers from heap
  -----------------------------------------------}
var
   Err : Word ;
begin
     case Operation of
          Allocate : begin
             if not BuffersAllocated then begin
                { Create A/D and D/A data buffers }
                ADCHnd := GlobalAlloc( GMEM_FIXED, Sizeof(TIntArray) ) ;
                GlobalFix( ADCHnd ) ;
                ADC := GlobalLock( ADCHnd ) ;
                DetHnd := GlobalAlloc( GMEM_FIXED, Sizeof(TIntArray) )  ;
                GlobalFix( DetHnd ) ;
                DetADCIn := GlobalLock( DetHnd ) ;
                DACHnd := GlobalAlloc( GMEM_FIXED, Sizeof(TIntArray) ) ;
                GlobalFix( DACHnd ) ;
                DAC := GlobalLock( DACHnd ) ;

                New(Sum) ;
                { Create buffers for event detection procedures }
                New(DetADCOut) ;
                New(DigBuf) ;
                New(VProgram) ;
                BuffersAllocated := True ;
                end ;
             end ;
          Deallocate : begin
             if BuffersAllocated then begin
                GlobalUnlock( ADCHnd ) ;
                GlobalUnFix( ADCHnd ) ;
                GlobalFree( ADCHnd ) ;
                GlobalUnlock( DetHnd ) ;
                GlobalUnFix( DetHnd ) ;
                GlobalFree( DetHnd ) ;
                GlobalUnlock( DACHnd ) ;
                GlobalUnFix( DACHnd ) ;
                GlobalFree( DACHnd ) ;
                Dispose(Sum) ;
                Dispose(DetADCOut) ;
                Dispose(DigBuf) ;
                Dispose(VProgram) ;
                BuffersAllocated := False ;
                end ;
             end ;
          end ;
     end ;


procedure TRecordFrm.FormCreate(Sender: TObject);
{ ------------------------------------
  Initialisation when form is created
  ----------------------------------- }
var
   Supplier,Model : string ;
begin
     { Make sure event timer is off }
     Timer1.Enabled := False ;

     { Disable Record to disk option in Record menu }
     Main.RecordToDisk.enabled := false ;
     { Make Record item in Windows menu visible }
     Main.ShowRecord.visible := True ;

     { Allocate memory buffers }
     BuffersAllocated := False ;
     HeapBuffers( Allocate ) ;

     { Select which kind of lab. interface hardware is to be used }
     SelectLaboratoryInterface( Settings.LaboratoryInterface ) ;

     { Call GetLabInterfaceInfo to force initialisation of lab. interface
       (this can take 3-4 secs for National Instruments interfaces)
       Note timer turned off to prevent premature execution of A/D routines }
     Timer1.Enabled := False ;                             {31/5/98}
     GetLabInterfaceInfo( Supplier, Model ) ;
     Timer1.Enabled := True ;

     { Set size of window }
     HeightMin := TimerGrp.Top + TimerGrp.Height + 50 ;
     Height := HeightMin ;
     Width := edRecordIdent.Left + EdRecordIdent.Width + 30 ;
     Caption := 'Record ' + RawFH.FileName ;
     edIdent.text := RawFH.IdentLine ;
     RH.dt := RawFH.dt ;
     ADCActive := False ;
     DACActive := False ;
     DigActive := False ;
     EraseScreen := False ;

     { Set trigger mode }
     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 ;

     edDetectionThreshold.text := format(' %6.1f',[Settings.EventDetector.Threshold])
                                  + ' %' ;
     edPreTrigger.text := format(' %6.1f',[Settings.EventDetector.PreTrigger])
                                  + ' %' ;

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

     { 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 }
     if Settings.CED1902.InUse then SetCED1902( Settings.CED1902 ) ;

     edRecordsRequired.text := format( ' %d ',[Settings.NumRecordsRequired] ) ;
     edStatus.font.color := clBlack ;
     TimerBusy := False ;
     State := BlankDisplay ;
     EndofSweep := False ;
     Timer1.Enabled := True ;
     end;


procedure TRecordFrm.Timer1Timer(Sender: TObject);
{ ---------------------
  Timed Event scheduler
  ---------------------}
var
   Interval,i,j,ch,iSample : LongInt ;
   xPix,yPix,iY,PointerPlus1 : Integer ;
   Value,DACdt,Delay,y : single ;
   ExtTrigger : Boolean ;
   NumPointsDone,NumIterationsDone,NumBytes : LongInt ;
   Done : Boolean ;
   Offsets : Array[0..ChannelLimit] of LongInt ;
   NumValuesOut,NumSamplesIn : LongInt ;
   NextCh : Integer ;
   Dummy : single ;

begin

     if not TimerBusy then begin

        TimerBusy := True ;

        case State of

          StartSweep : Begin
               { Start recording sweep(s) }

               GetChannelOffsets( Offsets, RawFH.NumChannels ) ;
               for ch := 0 to RawFH.NumChannels-1 do
                   Channel[ch].ChannelOffset := Offsets[ch] ;

               InitializeDisplay(Channel,RawfH.NumChannels,RH,
                                 lbTMin,lbTMax,pbDisplay) ;

               dx := 1. ;
               x := -dx ;
               NextSample := 1 ;
               EndofBuf := (RawFH.NumChannels*RawFH.NumSamples) - 1;

               { If in pulse program or external trigger modes ...
                 Request A/D converter to wait for a trigger pulse }
               if rbExtTrigger.checked or rbPulseProgram.checked then
                   ExtTrigger := True
               else
                   ExtTrigger := False ;

               { If in pulse program mode ... use the sweep
                 duration from the waveform record }
               if rbPulseProgram.checked then
                  RawFH.dt := VProgram^.RecordDuration/RawFH.NumSamples ;

               { Start A/D converter sampling }

               if rbEventDetector.checked then begin
                    { *** Event detection mode *** }
                    if not ADCActive then begin
                       { Set up continuous sampling into a circulating buffer }
                       { Set size of circular buffer
                         (ensure that it is a multiple of no. of channels }

                       if (TLaboratoryInterface(Settings.LaboratoryInterface) = CED)
                          and (CED_GetType = 0) then
                          { Limit size of circular buffer in standard 1401
                            because it only has 64K RAM }
{                          Det.EndOfBuf := (RawFH.NumChannels*RawFH.NumSamples*2) -1 }
                          Det.EndofBuf := (( 8192 div RawFH.NumChannels)
                                          * RawFH.NumChannels)  - 1
                       else
                          { Set circular buffer as big as possible }
                          Det.EndofBuf := (((High(DetADCIn^)+1) div RawFH.NumChannels)
                                          * RawFH.NumChannels)  - 1 ;

                       { Set channel counter }
                       Det.LastChan := RawFH.NumChannels - 1 ;
                       Det.Chan := Det.LastChan ;
                       Det.ChanSelected := cbDetectChannel.ItemIndex ;

                       { Set event detection threshold and polarity }
                       Det.Threshold := Trunc( ( MaxADCValue/200. ) *
                                        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 := MaxADCValue ;
                          end
                       else begin
                          Det.Polarity := -1 ;
                          Det.Baseline := MinADCValue ;
                          end ;
                       Det.Threshold := Abs(Det.Threshold) ;

                       { Initialise sample pointer }
                       Det.Pointer := 0 ;
                       { Number of pre-trigger points }
                       Det.PreTriggerPoints :=  Trunc ( 0.01*RawFH.NumSamples*
                                            FloatLimitTo(
                                            ExtractFloat(edPreTrigger.text,10. ),
                                            1. , 99. ))* RawFH.NumChannels ;

                       { Start continous sampling into circular buffer }
                       ADCToMemory( DetADCIn^,RawfH.NumChannels,
                                    (Det.EndofBuf+1) div RawfH.NumChannels,RawfH.dt,
                                    RawfH.ADCVoltageRange,false,true) ;
                       ADCPointer := 0 ;
                       end ;

                    { Fill A/D buffer with "empty" flags }
                    for i := 0 to (RawFH.NumSamples*RawFH.NumChannels)-1 do
                        ADC^[i] := EmptyFlag ;

                    Det.EventDetected := False ;
                    end
               else begin
                   { Other modes ... set up a A/D recording single sweep }
                   ADCToMemory( ADC^, RawfH.NumChannels,
                                RawfH.NumSamples,RawfH.dt,
                                RawfH.ADCVoltageRange,ExtTrigger,false) ;
                   ADCPointer := 0 ;
                   end ;
               ADCActive := True ;

               { Voltage program (if needed) }

               if rbPulseProgram.checked then begin

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

                  if OldStepCounter <> VProgram^.StepCounter then Inc(GroupNumber) ;
                  OldStepCounter := VProgram^.StepCounter ;

                  { Create TEST voltage program waveform }
                  if not InLeakMode then begin
                     { If repeat count = 0 ... create waveform }
                     if VProgram^.RepeatCounter = 0 then begin
                        CreateWaveform( VProgram^, DAC^, DigBuf^ ) ;
                        ConvertToDACCodes(DAC^,NumDACChannels,VProgram^.EndOfInterval) ;
                        VProgram^.LeakCounter := 0 ;
                        NewDigitalPattern := True ;
                        end ;

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

                     end ;

                  if Not InLeakMode then begin
                     { Output digital data (if required) }
                     if VProgram^.DigitalInUse then begin
                        MemoryToDigitalPort( Handle, DigBuf^, VProgram^.EndOfInterval,
                                             DAC^, VProgram^.RecordingStart,
                                             VProgram^.DACdt, NewDigitalPattern ) ;
                        DIGActive := True ;
                        end ;

                     Inc(VProgram^.RepeatCounter) ;
                     if VProgram^.RepeatCounter >= VProgram^.NumRepeats then begin
                        VProgram^.RepeatCounter := 0 ;
                        Inc(VProgram^.StepCounter) ;
                        LeakCollected := False ;
                        end ;
                     end ;

                   { Add leak information ... if this is a LEAK record }
                  if InLeakMode then
                      edStatus.text := edStatus.text
                                       + format( ' ( Leak %d/%d)',
                                       [VProgram^.LeakCounter+1,Abs(VProgram^.NumLeaks)]);

                  { Start D/A conversion (in continuous cycle mode) }
                  MemoryToDAC( DAC^,NumDACChannels,VProgram^.EndofInterval,
                               VProgram^.DACdt, 0, DACActive ) ;
                  DACActive := True ;

                  end
               else begin
                    edStatus.text := format( 'Rec %d ( %d/%d ) ',
                                     [RawFH.NumRecords,
                                      NumRecordsDone,Settings.NumRecordsRequired] ) ;
                    end ;
               State := SweepInProgress ;
               EndofSweep := False ;
               End ;


          SweepInProgress : Begin

               { *** Spontaneous event detector *** }



               if rbEventDetector.checked then begin

                  GetADCSamples( @DetADCIn^[ADCPointer], NumSamplesIn ) ;
                  ADCPointer := ADCPointer + NumSamplesIn ;
                  if ADCPointer > Det.EndOfBuf then begin
                     ADCPointer := 0 ;
                     end ;

                  { Scan circular A/D buffer for an event }
                  PointerPlus1 := Det.Pointer + 1 ;
                  if PointerPlus1 > Det.EndOfBuf then PointerPlus1 := 0 ;
                  if DetADCIn^[PointerPlus1] = EmptyFlag then Done := True
                                                         else Done := False ;
                  while not Done do begin
                     iY := DetADCIn^[Det.Pointer] ;
                     DetADCOut^[Det.Pointer] := iY ;
                     ConvertADCValue(iy,Det.Pointer,
                                     Det.Chan,RawFH.NumChannels,x,dx) ;
                     DetADCIn^[Det.Pointer] := EmptyFlag ;

                     if not Det.EventDetected then begin
                        if Det.Chan = Det.ChanSelected then begin

                           { Event detected ... if signal exceeds threshold }

                           if Det.Polarity*(iY - Det.Baseline) > Det.Threshold then begin
                              Det.EventDetected := True ;
                              { Get segment of buffer to be extracteds }
                              Det.StartAt := (Det.Pointer div RawFH.NumChannels) *
                                           RawFH.NumChannels - Det.PreTriggerPoints ;
                              if Det.StartAt < 0 then Det.StartAt :=
                                                   Det.StartAt + (Det.EndofBuf+1) ;
                              Det.EndAt := Det.StartAt
                                           + RawfH.NumSamples*RawFH.NumChannels -1 ;
                              if Det.EndAt > Det.EndofBuf then Det.EndAt :=
                                                   Det.EndAt - (Det.EndofBuf+1) ;
                              { Time of detection }
                              Det.TimeDetected := Det.BufferCycles ;
                              Det.TimeDetected := RawFH.dt *
                                                  ((Det.TimeDetected*(Det.EndOfBuf+1)
                                                  + Det.Pointer ) / RawFH.NumChannels) ;

                              end ;
                           end ;
                        end
                     else begin
                        { If an event has been 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 (RawFH.NumSamples*RawFH.NumChannels)-1 do begin
                                 ADC^[i] := DetADCOut^[j] ;
                                 Inc(j) ;
                                 if j > Det.EndOfBuf then j := 0 ;
                                 end ;
                             dx := 1. ;
                             x := -dx ;
                             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 pointer }
                     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 ;

                     { Exit loop if no samples left }
                     if DetADCIn^[PointerPlus1] = EmptyFlag then Done := True
                     end ;
                  end
               else begin
                   { This routine ONLY needed when using CED 1401 with
                     single sweep recording modes }
                   GetADCSamples( @ADC^[ADCPointer], NumSamplesIn ) ;
                   ADCPointer := ADCPointer + NumSamplesIn ;
                   end ;

               { *** Display sample record on screen *** }

               While (ADC^[NextSample] <> EmptyFlag) and (not EndOfSweep) do Begin

                     { Erase display once sweep has started (if required) }
                     if (NextSample = 1) and
                        (Settings.AutoErase or EraseScreen ) then begin
                        EraseScreen := False ;
                        EraseDisplay( pbDisplay ) ;
                        InitializeDisplay(Channel,RawfH.NumChannels,
                                          RH,lbTMin,lbTMax,pbDisplay) ;
                        { Draw horizontal zero level cursor(s) }
                        for ch := 0 to RawfH.NumChannels-1 do
                            if Channel[ch].InUse then
                               DrawHorizontalCursor( pbDisplay, Channel[ch],
                                                     Channel[ch].ADCZero ) ;
                        end ;

                     { Calculate pixel location on screen for next data point }

                     iSample := NextSample - 1 ;
                     ConvertADCValue(ADC^[iSample],iSample,
                                     NextCh,RawFH.NumChannels,x,dx) ;
                     yLast[NextCh] := ADC^[iSample] ;
                     y := yLast[NextCh] ;

                     if y < Channel[NextCh].yMin then y := Channel[NextCh].yMin ;
                     if y > Channel[NextCh].yMax then y := Channel[NextCh].yMax ;

                     xPix := Trunc(Channel[NextCh].xScale*(x - Channel[NextCh].xMin))
                     + Channel[NextCh].Left  ;
                     yPix := Channel[NextCh].Bottom -
                     Trunc(Channel[NextCh].yScale*(y - Channel[NextCh].yMin));
                     xyPix := Point(xPix,yPix) ;

                     if x = 0. then xyPixOld[NextCh] := xyPix ;

                     pbdisplay.canvas.polyline( [xyPixOld[NextCh],xyPix]);
                     xyPixOld[NextCh] := xyPix ;

                     if NextSample >= EndOfBuf then EndOfSweep := True ;

                     { Increment sample counter & channel counter }
                     Inc(NextSample) ;
                     End ;

               { *** End of sweep processing *** }

               if EndOfSweep then begin

                  { Stop A/D conversion ... unless in detect event mode }
                  if not rbEventDetector.checked then begin
                     StopADC ;
                     ADCActive := False ;
                     end ;

                  { Save sweep data to file }
                  SaveSweep ;

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

                  if AbortRecording then begin
                     State := StopRecording ;
                     end
                  else if rbPulseProgram.checked then begin
                     { Voltage pulse mode }
                     if VProgram^.StepCounter >= VProgram^.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(VProgram^.NextProtocolFileName) then begin
                            cbPulseProgram.ItemIndex := cbPulseProgram.Items.IndexOf(
                                 ExtractFileNameOnly(VProgram^.NextProtocolFileName)) ;
                            LoadVProgramFromFile(VProgram^.NextProtocolFileName,VProgram^) ;
                            VProgram^.StepCounter := 0 ;
                            VProgram^.RepeatCounter := 0 ;
                            VProgram^.LeakCounter := 0 ;
                            WriteToLogFile(
                            'Next Program = '+VProgram^.NextProtocolFileName ) ;
                            { Stop D/A output }
                            if DACActive then begin
                               StopDAC ;
                               WriteOutputPorts( Settings.VCommand.HoldingVoltage *
                                                 Settings.VCommand.DivideFactor,
                                                 Settings.DigitalPort.Value ) ;
                               DACActive := False ;
                               end ;
                            State := StartSweep ;
                            end
                        else State := StopRecording  ;
                        end
                     else State := StartSweep ;
                     end
                  else begin
                     { Other modes }
                     Inc(NumRecordsDone) ;
                     if (NumRecordsDone >= Settings.NumRecordsRequired) then
                          State := StopRecording
                     else State := StartSweep ;
                     end ;
                  end ;

               End ;

          StopRecording : Begin

               { Disable A/D and D/A sub-systems (if necessary) }
               ShutDownLabInterface ;

               edStatus.font.color := clBlack ;
               edStatus.text := format( 'Stopped %d records in file',
                                [RawFH.NumRecords] ) ;

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

               State := Idle ;
               bRecord.enabled := True ;
               bStop.enabled := False ;
               Main.SetMenus ;
               if RawFH.NumRecords > 0 then Main.ShowRaw.enabled := True ;
               RawFH.IdentLine := edIdent.text ;
               { Save header block }
               SaveHeader( RawfH ) ;
               { Set file header in use to raw file header }
               FH := RawFH ;

               end ;

          BlankDisplay : Begin

               { Erase display }
               EraseDisplay( pbDisplay ) ;
               RH.dt := RawFH.dt ;
               InitializeDisplay(Channel,RawfH.NumChannels,
                                 RH,lbTMin,lbTMax,pbDisplay) ;
               { Draw horizontal zero level cursor(s) }
               for ch := 0 to RawfH.NumChannels-1 do
                   if Channel[ch].InUse then begin
                      Channel[ch].ADCZero := 0 ;
                      DrawHorizontalCursor( pbDisplay, Channel[ch],
                                            Channel[ch].ADCZero ) ;
                      end ;

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

               { Set event detector }
               cbDetectChannel.items := ChannelNames ;
               Settings.EventDetector.Channel := MinInt( [MaxInt(
                                       [Settings.EventDetector.Channel,0 ]),
                                       RawFH.NumChannels-1 ] ) ;
               cbDetectChannel.ItemIndex := Settings.EventDetector.Channel ;
               edDetectionThreshold.text := format( ' %6.1f',
                                    [Settings.EventDetector.Threshold]) + ' %';
               State := Idle ;
               end ;

          Idle : begin
              { Procedures when recording is in idle mode }
              if (edRecordsRequired.text = '')
                 and (not rbPulseProgram.checked) then
                 edRecordsRequired.text := IntToStr( Settings.NumRecordsRequired ) ;

              Settings.EventDetector.Channel := cbDetectChannel.ItemIndex ;

              { Update holding potential if it has changed }
              if not Inactive then begin
                 { Update digital output port settings }
                 if Settings.UpdateOutputs then begin
                    WriteOutputPorts( Settings.VCommand.HoldingVoltage *
                                      Settings.VCommand.DivideFactor,
                                      Settings.DigitalPort.Value ) ;
                    Settings.UpdateOutputs := False ;
                    end ;
                 end ;

              end ;
          end ;

        UpdateStopwatch ;
        TimerBusy := False ;

        end ;
     end ;

procedure TRecordFrm.ShutDownLabInterface ;
{ ---------------------------------------------
  Shut down A/D, D/A and digital O/P subsystems
  ---------------------------------------------}
begin
     { Disable A/D }
     if ADCActive then begin
        StopADC ;
        ADCActive := False ;
        end ;
     { Disable D/A waveform generation }
     if DACActive then begin
        { Return voltage command to holding voltage and Sync. O/P to 5V }
        StopDAC ;
        WriteOutputPorts( Settings.VCommand.HoldingVoltage *
                          Settings.VCommand.DivideFactor,
                          Settings.DigitalPort.Value ) ;
        DACActive := False ;
        end ;
     { Disable digital waveform generation }
     if DIGActive then begin
        StopDIG( Settings.DigitalPort.Value ) ;
        DIGActive := False ;
        end ;
     end ;


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

     { If a CED 1902 amplifier is attached to Ch.0, use its gain
       setting for the Ch.0 amplifier gain (V1.4) }
     if Settings.CED1902.InUse then
        Channel[0].ADCAmplifierGain := Settings.CED1902.GainValue ;

     { Update record voltage range for each channel
       taking into account amplifier gain }
     for ch := 0 to fH.NumChannels-1 do
         rH.ADCVoltageRange[ch] := fH.ADCVoltageRange /
                                   Channel[ch].ADCAmplifierGain ;

     { Replace any Empty flags in the buffer with the most recently
       recorded sample for that channel }
     ch := RawFH.NumChannels-1 ;
     for i := 0 to EndOfBuf do begin
         if ADC^[i] = EmptyFlag then ADC^[i] := yLast[ch] ;
         Dec(ch) ;
         if ch < 0 then ch := RawFH.NumChannels - 1 ;
         end ;

     { Special processing if this is a leak current record }
     if InLeakMode then begin
        {Add leak record to average }
        if VProgram^.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(VProgram^.LeakCounter) ;
        if VProgram^.LeakCounter >= Abs(VProgram^.NumLeaks) then begin
           NumAvg := MaxInt( [VProgram^.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 := RawfH.dt ;

        { Update time that record was collected }
        if rbEventDetector.checked then begin
              { EVENT DETECTION MODE : Time obtained from sample buffer position }
              rH.Time := Det.TimeDetected ;
              end
        else begin
              { OTHER MODE : Time from PC clock }
              if RawFH.NumRecords = 1 then begin
                 TimeStart := TimeInMilliseconds*mStoSecs ;
                 rH.Time := 0. ;
                 end
              else rH.Time := (TimeInMilliseconds*mStoSecs) - TimeStart ;
              end ;


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

        if (not ckSaveRecords.checked) then begin
              RawfH.NumRecords := RawfH.NumRecords - 1 ;
              edStatus.text := 'Records not saved!' ;
              end ;
        end ;

     End ;


procedure TRecordFrm.DrawHorizontalCursor( var pb : TPaintBox ;
                                Const Gr : TChannel ;
                                Level : Integer ) ;
{ -----------------------------------------------
  Draw dotted horizontal cursor at ADC level 'Level'
  in display area defined by  record data channel 'Gr'
  ----------------------------------------------------}
var
   yPix,xPix,TicSize : Integer ;
   OldColor : TColor ;
   OldStyle : TPenStyle ;
   OldMode : TPenMode ;
begin
     with pb.canvas do begin
          OldColor := pen.color ;
          OldStyle := pen.Style ;
          OldMode := pen.mode ;
          pen.mode := pmXor ;
          pen.color := clRed ;
          end ;

     yPix := Trunc( Gr.Bottom - (Level - Gr.yMin)*Gr.yScale ) ;
     pb.canvas.polyline([Point(Gr.Left,yPix),Point(Gr.Right,yPix)]);

     { if zero level has been calculated from record show the point used }

     if (Gr.xMin <= Gr.ADCZeroAt) and (Gr.ADCZeroAt <= Gr.xMax ) then begin
          xPix := Trunc((Gr.ADCZeroAt - Gr.xMin)*Gr.xScale - Gr.Left) ;
          TicSize := pb.Canvas.textheight('X') ;
          pb.canvas.polyline( [Point( xPix, yPix+TicSize ),
                               Point( xPix, yPix-TicSize )] );
          end ;

     with pb.canvas do begin
          pen.style := OldStyle ;
          pen.color := OldColor ;
          pen.mode := OldMode ;
          end ;
    end ;


procedure TRecordFrm.bRecordClick(Sender: TObject);
{ -----------------------
  Start recording session
  ----------------------- }
begin
     State := StartSweep ;
     AbortRecording := False ;
     bRecord.Enabled := False ;
     bStop.Enabled := True ;
     edStatus.font.color := clRed ;

     WriteToLogFile( 'Recording Started ' ) ;

     { Set number of recording sweepsm to be collected }
     Settings.NumRecordsRequired := ExtractInt( edRecordsRequired.text ) ;
     EndAtRecord := RawFH.NumRecords + Settings.NumRecordsRequired ;
     NumRecordsDone := 0 ;
     if rbPulseProgram.checked then begin
        { Load voltage program }
        Settings.VProgramFileName := Settings.VProtDirectory
                                     + cbPulseProgram.text + '.vpr' ;
        if FileExists( Settings.VProgramFileName ) then begin
           LoadVProgramFromFile(Settings.VProgramFileName,VProgram^) ;
           VProgram^.StepCounter := 0 ;
           VProgram^.RepeatCounter := 0 ;
           VProgram^.LeakCounter := 0 ;
           WriteToLogFile( 'Voltage Program = '+Settings.VProgramFileName ) ;
           end
        else begin
             MessageDlg( ' No Voltage Program - Recording Aborted! ',
                         mtWarning, [mbOK], 0 ) ;
             State := Idle ;
              end ;

        { Get last group number used }
        if RawFH.NumRecords > 0 then begin
           GetRecordHeaderOnly( RawfH, rH, RawfH.NumRecords ) ;
           GroupNumber := trunc(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 ;

        OldStepCounter := 0 ;
        end ;

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

     end;


procedure TRecordFrm.FormDestroy(Sender: TObject);
begin
     { Release memory used by objects created in FORMCREATE}
     HeapBuffers( Deallocate ) ;
     end;


procedure TRecordFrm.FormPaint(Sender: TObject);
begin
     if State <> SweepInProgress then State := BlankDisplay ;
     end;


procedure TRecordFrm.FormResize(Sender: TObject);
{ ------------------------------------------
  Adjust components when display is re-sized
  ------------------------------------------ }
begin
     if Height < HeightMin then Height := HeightMin ;

     edStatus.Top := Height - 50 - EdStatus.Height ;
     lbTMin.Top := edStatus.Top ;
     lbTMax.Top := edStatus.Top ;
     pbDisplay.Height := edStatus.Top - pbDisplay.Top  - 10 ;
     pbDisplay.Width := Width - pbDisplay.Left - 20 ;
     lbTMin.left := pbDisplay.Left ;
     lbTMax.Left := pbDisplay.Left + pbDisplay.Width - lbTMax.width ;
     State := BlankDisplay ;
     end;


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

     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
         LoadVProgramFromFile(Settings.VProgramFileName,VProgram^) ;
         Settings.VCommand.HoldingVoltage := VProgram^.HoldingVoltage ;
         Settings.DigitalPort.Value := VProgram^.DigitalPortValue ;
         end ;
     end;


procedure TRecordFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
     Main.RecordToDisk.enabled := true ;
     Main.ShowRecord.visible := False ;
     { Shut down Lab. interface hardware ... essential to avoid program crash }
     ShutDownLabInterface ;
     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 ;

           Stopwatch.ElapsedTime := EncodeTime(Stopwatch.Hour,Stopwatch.Minute,
                                            Stopwatch.Second,Stopwatch.Msec ) ;
           edTimeofDay.text := TimetoStr(Time)
                               + ' (' + TimetoStr(Stopwatch.ElapsedTime) +')';
           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.EdDetectionThresholdKeyPress(Sender: TObject;
  var Key: Char);
{ -------------------------------------
  Get event detection threshold level
  -----------------------------------}
begin
     if key = chr(13) then begin
        Settings.EventDetector.Threshold := FloatLimitTo(
                                            ExtractFloat(edDetectionThreshold.text,
                                            Settings.EventDetector.Threshold ),
                                            -100. , 100. ) ;
        edDetectionThreshold.text := format( ' %6.1f',
                                     [Settings.EventDetector.Threshold]) + ' %';
        end ;
     end ;



procedure TRecordFrm.FormDeactivate(Sender: TObject);
{ ---------------------------------------
  Actions to be taken if form loses focus
  ---------------------------------------}
begin
     { Release lab. interface to avoid conflicts with other windows/programs }
     ShutDownLabInterface ;
     { If we're recording give up (in disgust!!) }
     State := StopRecording ;
     Inactive := True ;
     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.FormActivate(Sender: TObject);
begin
     { Set tick in Windows menu }
     Main.ClearTicksInWindowsMenu ;
     Main.ShowRecord.checked := True ;
     Inactive := False ;
     end;

procedure TRecordFrm.pbDisplayDblClick(Sender: TObject);
{ -------------------------------
  Activate Zoom In/Out dialog box
  -------------------------------}
var
   i : Integer ;
begin
     if State = Idle then begin
        FH := RawFH ;
        ZoomFrm.ChOnDisplay := MouseOverChannel ;
        ZoomFrm.ShowModal ;
        end ;
     end;






procedure TRecordFrm.pbDisplayMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
{ ----------------------------------------
  Find the channel that the mouse is over
  ---------------------------------------}
var
   ch : Integer ;
begin
     if State = Idle then begin
        { Find the channel that the mouse is over }
        for ch := 0 to RawFH.NumChannels-1 do
            if (Channel[ch].Bottom >= Y) and (Y >= Channel[ch].top) then
               MouseOverChannel := ch ;
        end ;
     end;

procedure TRecordFrm.FormShow(Sender: TObject);
begin
     { Set tick in Windows menu }
     Main.ClearTicksInWindowsMenu ;
     Main.ShowRecord.checked := True ;
     end;





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

procedure TRecordFrm.bEraseClick(Sender: TObject);
{ -------------------------
  Erase the display screen
  ------------------------}
begin
     if bRecord.Enabled then begin
        { Erase screen immediately if not recording }
        EraseDisplay( pbDisplay ) ;
        InitializeDisplay(Channel,RawfH.NumChannels,RH,lbTMin,lbTMax,pbDisplay) ;
        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 begin
        Settings.EventDetector.PreTrigger := FloatLimitTo(
                                          ExtractFloat(edPreTrigger.text,
                                            Settings.EventDetector.PreTrigger ),
                                            1. , 99. ) ;
        edPreTrigger.text := format( ' %6.1f',
                                     [Settings.EventDetector.PreTrigger]) + ' %';
        end ;
     end ;



end.
