unit Wavgen;
{ =====================================================================
  WinWCP -Analogue & Digital Waveform Editor
  (c) J. Dempster, University of Strathclyde, 1996-97. All Rights Reserved.
  22/5/97 Bug in incrementing duration dig. pulse fixed.
          Minor offset error between dig & voltage pulse fixed.
  12/12/97 DAC update interval can now go down to 0.1 ms
           and is adjusted automatically based on pulse width
           and repeat period
  24/3/98  wvStep1 : Amplitude increment now updated correctly when
           when changed
  3/4/98   Data points in user-defined waveforms now automatically reduced
           in number to fit available space. Inter-record interval also
           adjusted if it is too long to be supported by D/A update interval
  =====================================================================}
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, StdCtrls, Grids, Global, SHared, ClipBrd, Labio,
  fileio, ToolHelp, maths, Spin ;

type
  TWavGenFrm = class(TForm)
    ControlGrp: TGroupBox;
    bOpen: TButton;
    bSave: TButton;
    bNew: TButton;
    bClose: TButton;
    ProtocolGrp: TGroupBox;
    DisplayGrp: TGroupBox;
    Prot1: TImage;
    Prot2: TImage;
    Prot3: TImage;
    Prot4: TImage;
    Prot5: TImage;
    Prot6: TImage;
    Prot7: TImage;
    Prot8: TImage;
    Prot9: TImage;
    Prot0: TImage;
    TableGrp: TGroupBox;
    Table: TStringGrid;
    ToolBoxGrp: TGroupBox;
    Step1: TImage;
    Step2: TImage;
    pTrain: TImage;
    Ramp: TImage;
    Wave: TImage;
    Step0: TImage;
    None: TImage;
    Label1: TLabel;
    dig0: TImage;
    Dig1: TImage;
    Dig2: TImage;
    Dig3: TImage;
    Dig4: TImage;
    Dig5: TImage;
    Dig6: TImage;
    Dig7: TImage;
    Label2: TLabel;
    RecSweep: TImage;
    Label3: TLabel;
    DigStep0: TImage;
    DigStep1: TImage;
    DigTrain: TImage;
    LeakGrp: TGroupBox;
    rbNoLeak: TRadioButton;
    rbPNLeak: TRadioButton;
    edNumLeaks: TEdit;
    pbDigDisplay: TPaintBox;
    lbVMax: TLabel;
    lbVMin: TLabel;
    SaveDialog: TSaveDialog;
    OpenDialog: TOpenDialog;
    DACGroup: TGroupBox;
    EdVDivide: TEdit;
    Label4: TLabel;
    shSelected: TShape;
    GroupBox1: TGroupBox;
    cbNextProtocol: TComboBox;
    lbVDisplay: TLabel;
    lbDigDisplay: TLabel;
    lbRecordTime: TLabel;
    shRecordingPeriod: TShape;
    lbRecording: TLabel;
    bPasteFromClipboard: TButton;
    pbVDisplay: TPaintBox;
    Label5: TLabel;
    Label6: TLabel;
    edLeakScale: TEdit;
    Timer1: TTimer;
    ckNoDisplay: TCheckBox;
    edDACInterval: TEdit;
    procedure bCloseClick(Sender: TObject);
    procedure Prot0DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure Prot0DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure Step0MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Prot0Click(Sender: TObject);
    procedure DigStep0MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure RecSweepClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure bNewClick(Sender: TObject);
    procedure TableKeyPress(Sender: TObject; var Key: Char);
    procedure bSaveClick(Sender: TObject);
    procedure bOpenClick(Sender: TObject);
    procedure rbNoLeakClick(Sender: TObject);
    procedure rbPNLeakClick(Sender: TObject);
    procedure dig0DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure dig0Click(Sender: TObject);
    procedure FormHide(Sender: TObject);
    procedure dig0DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure bPasteFromClipboardClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormActivate(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure TableExit(Sender: TObject);
    procedure edNumLeaksKeyPress(Sender: TObject; var Key: Char);
    procedure edLeakScaleKeyPress(Sender: TObject; var Key: Char);
    procedure cbNextProtocolChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure pbVDisplayPaint(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure ckNoDisplayClick(Sender: TObject);
    procedure EdVDivideKeyPress(Sender: TObject; var Key: Char);
  private
    { Private declarations }
    Procedure SetCursor( SetDrag : Boolean ) ;
    procedure FillParameterTable( Num : Integer ; FirstTime : Boolean ) ;

    procedure NewWaveform ;
    procedure UpdateIcons ;

    procedure CheckWaveformDuration ;
    procedure UpdateNumLeaks ;
    
    procedure UpdateEntry( const SourceText : string ;
                       var Destination : single ;
                       ConversionFactor : single ;
                       PositiveOnly : boolean ) ;
    procedure UpdateWaveform ;

    procedure HeapBuffers( Operation : THeapBufferOp ) ;
  
  end;

var
  WavGenFrm: TWavGenFrm;

implementation

{$R *.DFM}

uses wavrun, mdiform ;



Type
    TCurrentTool = record
                 ToolType : TWaveShape ;
                 Digital : Boolean ;
                 end ;


var
   CurrentTool : TCurrentTool ;
   CurrentWaveformNum : Integer ;
   Waveform : ^TWaveform ;
   ImageIndex : Array[0..20] of Integer ;
   DAC : ^TIntArray ;
   DigBuf : ^TIntArray ;
   TableChanged : boolean ;
   BuffersAllocated : boolean ;{ Indicates if memory buffers have been allocated }


procedure TWavGenFrm.HeapBuffers( Operation : THeapBufferOp ) ;
{ -----------------------------------------------
  Allocate/deallocation dynamic buffers from heap
  -----------------------------------------------}
begin
     case Operation of
          Allocate : begin
             if not BuffersAllocated then begin
                New(DAC) ;
                New(DigBuf) ;
                New(Waveform) ;
                BuffersAllocated := True ;
                end ;
             end ;
          Deallocate : begin
             if BuffersAllocated then begin
                Dispose(DAC) ;
                Dispose(DigBuf) ;
                Dispose(Waveform) ;
                BuffersAllocated := False ;
                end ;
             end ;
          end ;
     end ;


procedure TWavGenFrm.FormCreate(Sender: TObject);
begin
     { Disable Waveform Generator menu item }
     Main.WaveformGenerator.enabled := false ;

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

     { If Digidata 1200 interface in use, only 4 digital output channels
       are available }

     if TLaboratoryInterface(Settings.LaboratoryInterface) = DD then begin
        Dig4.visible := False ;
        Dig5.visible := False ;
        Dig6.visible := False ;
        Dig7.visible := False ;
        end
     else begin
        Dig4.visible := True ;
        Dig5.visible := True ;
        Dig6.visible := True ;
        Dig7.visible := True ;
        end ;
     end;


procedure TWavGenFrm.bCloseClick(Sender: TObject);
{ -------------------------------------
  Close form and update global settings
  -------------------------------------}
var
   FileHandle : longint ;
begin

     UpdateNumLeaks ;

     CheckWaveformDuration ;

     Settings.VCommand.DivideFactor := ExtractFloat(edVDivide.text,
                                                 Settings.VCommand.DivideFactor) ;

     { If the current waveform has been edited ... offer to save it to file }
     if not Waveform^.Saved then begin
        if MessageDlg( ' Stimulus Changed! Save it to ' + Waveform^.FileName,
           mtConfirmation,[mbYes,mbNo], 0 ) = mrYes then begin
           FileHandle := FileCreate( Waveform^.FileName ) ;
           if Sizeof(Waveform^) <> FileWrite(FileHandle,Waveform^,
                                   Sizeof(Waveform^)) then
              MessageDlg( ' File Write - Failed ', mtWarning, [mbOK], 0 ) ;
           FileClose( FileHandle ) ;
           Waveform^.Saved := True ;

           end ;
        end ;
     Settings.VProgramFileName := Waveform^.FileName ;
     Close ;
     end;


Procedure TWavGenFrm.SetCursor( SetDrag : Boolean ) ;
begin
     if SetDrag then begin
        Cursor := crDrag ;
        Step1.Cursor := crDrag ;
        ProtocolGrp.Cursor := crDrag ;
        ToolBoxGrp.Cursor := crDrag ;
        end
     else begin
          Cursor := crDefault ;
          Step1.Cursor :=crDefault ;
          ProtocolGrp.Cursor := crDefault ;
          ToolBoxGrp.Cursor := crDefault ;
          end ;
     end ;


{ -------------------------------------------------
  Mouse down events when a protocol tool is selected
  --------------------------------------------------}

procedure TWavGenFrm.Step0MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
     CurrentTool.ToolType := TWaveShape( TImage(Sender).Tag ) ;
     CurrentTool.Digital := False ;
     TIMage(Sender).BeginDrag( False ) ;
     end;


procedure TWavGenFrm.DigStep0MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
     CurrentTool.ToolType := TWaveShape( TImage(Sender).Tag ) ;
     CurrentTool.Digital := True ;
     TIMage(Sender).BeginDrag( False ) ;
     end;



{ -----------------------------------------------------------------
  When a protocol block is clicked its parameter table is displayed
  -----------------------------------------------------------------}

procedure TWavGenFrm.Prot0Click(Sender: TObject);
begin
     if TableChanged then UpdateWaveform ;
     FillParameterTable(TIMage(Sender).Tag,False) ;
     shSelected.Left := TIMage(Sender).Left ;
     shSelected.Top := TIMage(Sender).Top + TIMage(Sender).Height ;
     shSelected.Width := TIMage(Sender).Width ;
     end;


procedure TWavGenFrm.dig0Click(Sender: TObject);
begin
     if TableChanged then UpdateWaveform ;
     FillParameterTable(TIMage(Sender).Tag,False) ;
     shSelected.Left := TIMage(Sender).Left ;
     shSelected.Top := TIMage(Sender).Top + TIMage(Sender).Height ;
     shSelected.Width := TIMage(Sender).Width ;
     end;


procedure TWavGenFrm.RecSweepClick(Sender: TObject);
begin
     if TableChanged then UpdateWaveform ;
     FillParameterTable(-1,False) ;
     shSelected.Left := TIMage(Sender).Left ;
     shSelected.Top := TIMage(Sender).Top + TIMage(Sender).Height ;
     shSelected.Width := TIMage(Sender).Width ;
     end;

{ ---------------------------------------------------------------
  Procedures when a waveform shape is dropped on a protocol block
  ---------------------------------------------------------------}

procedure TWavGenFrm.Prot0DragDrop(Sender, Source: TObject; X, Y: Integer);
{ ------------------------------------------------------------
  Accept analogue waveform tool dropped on to waveform icon
  ------------------------------------------------------------}
var
   iTag : Integer ;
begin
     iTag := TImage(Sender).Tag ;
     if  (not CurrentTool.Digital) then begin
        TIMage(Sender).Picture := TImage(Source).Picture ;
        Waveform^.Shape[iTag] := TWaveShape( TImage(Source).Tag ) ;
        shSelected.Left := TIMage(Sender).Left ;
        shSelected.Top := TIMage(Sender).Top + TIMage(Sender).Height ;
        shSelected.Width := TIMage(Sender).Width ;
        end ;
     SetCursor( False ) ;
     FillParameterTable(iTag,True) ;
     pbVDisplay.Repaint ;
     end;


procedure TWavGenFrm.dig0DragDrop(Sender, Source: TObject; X, Y: Integer);
{ ------------------------------------------------------------
  Accept digital waveform tool dropped on to digital port icon
  ------------------------------------------------------------}
var
   iTag : Integer ;
begin
     { Tag property of image icon provides the index into Shape array }
     iTag := TImage(Sender).Tag ;
     { Update the port icon with the digital port tool's image }
     if CurrentTool.Digital or (TWaveShape(TImage(Source).Tag) = wvNone) then begin
        TIMage(Sender).Picture := TImage(Source).Picture ;
        Waveform^.Shape[iTag] := TWaveShape( TImage(Source).Tag ) ;
        shSelected.Left := TIMage(Sender).Left ;
        shSelected.Top := TIMage(Sender).Top + TIMage(Sender).Height ;
        shSelected.Width := TIMage(Sender).Width ;
        end ;
     { Update waveform parameter table }
     SetCursor( False ) ;
     FillParameterTable(iTag,True) ;
     Repaint ;
     end;


procedure TWavGenFrm.Prot0DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
{ -----------------------------------------------------------------
  If the dragged object is compatible with this waveform component
  change the cursor to acceptable (Note this procedure used by all
  10 protocol items
  ----------------------------------------------------------------}
begin
     if (Source is TBitmap) and (not CurrentTool.Digital) then accept := True ;
     end;


procedure TWavGenFrm.dig0DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
{ -----------------------------------------------------------------
  If the dragged object is compatible with a digital port icon
  change the cursor to acceptable (Note this procedure used by all
  8 digital ports
  ----------------------------------------------------------------}
begin
     if (Source is TBitmap) and CurrentTool.Digital then accept := True ;
     end;


procedure TWavgenFrm.FillParameterTable( Num : Integer ; FirstTime : Boolean ) ;
{ -------------------------------------------
  Fill waveform parameter table with settings
  -------------------------------------------}
var
   i,MaxWidth,RowHeight : Integer ;
begin

     CurrentWaveformNum := Num ;

     Table.options := [goFixedVertLine,goFixedHorzLine,
                       goVertLine,goHorzLine,goEditing] ;

     { Disable the Paste button (unless in user-entered waveform option) }
     bPasteFromClipboard.enabled := false ;

     if Num >= 0 then begin
        case Waveform^.Shape[Num] of
        wvNone : begin
             { No waveform }
             Table.RowCount := 1 ;
             Table.ColCount := 2 ;
             TableGrp.caption := ' No waveform ' ;
             Table.cells[0,0] := '' ;
             Table.cells[1,0] := '' ;
             end ;
        wvStep0 : begin
             if FirstTime then begin
                if Num = 0 then Waveform^.Delay[Num] := Waveform^.RecordDuration * 0.1
                           else Waveform^.Delay[Num] := 0. ;
                Waveform^.Amplitude[Num] := 0.01 ;
                Waveform^.Duration[Num] := Waveform^.RecordDuration * 0.8 ;
                Waveform^.NumSteps := MaxInt( [Waveform^.NumSteps,1 ] ) ;
                end ;
             { Rectangular step (fixed size) }
             Table.RowCount := 3 ;
             Table.ColCount := 2 ;
             TableGrp.caption := ' Rectangular pulse (fixed size) ' ;
             Table.cells[0,0] := 'Initial Delay ' ;
             Table.cells[1,0] := format('%.2f %s',
                                 [Waveform^.Delay[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,1] := ' Amplitude ' ;
             Table.cells[1,1] := format( '%.2f mV', [Waveform^.Amplitude[Num]*VoltsTomV] ) ;
             Table.cells[0,2] := ' Duration (ms) ' ;
             Table.cells[1,2] := format('%.2f %s',
                                 [Waveform^.Duration[Num]*Settings.TScale,Settings.TUnits] ) ;
             end ;
        wvStep1 : begin
             { Family of steps varying in amplitude }
             if FirstTime then begin
                if Num = 0 then Waveform^.Delay[Num] := Waveform^.RecordDuration * 0.1
                           else Waveform^.Delay[Num] := 0. ;
                Waveform^.Amplitude[Num] := 0.01 ;
                Waveform^.Increment[Num] := 0.01 ;
                Waveform^.Duration[Num] := Waveform^.RecordDuration * 0.8 ;
                Waveform^.NumSteps := 10 ;
                end ;

             TableGrp.caption := ' Family of rectangular pulses (varying in amplitude) ' ;
             Table.RowCount := 5 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := 'Initial Delay ' ;
             Table.cells[1,0] := format('%.2f %s',
                                 [Waveform^.Delay[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,1] := ' Start at Amplitude ' ;
             Table.cells[1,1] := format( '%.2f mV', [Waveform^.Amplitude[Num]*VoltsTomV] ) ;
             Table.cells[0,2] := ' Increment by ' ;
             Table.cells[1,2] := format( '%.2f mV', [Waveform^.Increment[Num]*VoltsTomV] ) ;
             Table.cells[0,3] := ' Number of increments ' ;
             Table.cells[1,3] := format( '%5d', [Waveform^.NumSteps] ) ;
             Table.cells[0,4] := ' Pulse Duration ' ;
             Table.cells[1,4] := format('%.2f %s',
                                 [Waveform^.Duration[Num]*Settings.TScale,Settings.TUnits] ) ;
             end ;

        wvStep2 : begin
             { Family of steps varying in duration }
             if FirstTime then begin
                if Num = 0 then Waveform^.Delay[Num] := Waveform^.RecordDuration * 0.1
                           else Waveform^.Delay[Num] := 0. ;
                Waveform^.Amplitude[Num] := 0.01 ;
                Waveform^.Duration[Num] := Waveform^.RecordDuration * 0.8 ;
                Waveform^.Increment[Num] := Waveform^.Duration[Num] * 0.1  ;
                Waveform^.NumSteps := 10 ;
                end ;
             TableGrp.caption := ' Family of rectangular pulses (varying in duration) ' ;
             Table.RowCount := 5 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := 'Initial Delay ' ;
             Table.cells[1,0] := format('%.2f %s',
                                 [Waveform^.Delay[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,1] := ' Amplitude ' ;
             Table.cells[1,1] := format( '%.2f mV',[Waveform^.Amplitude[Num]*VoltsTomV] ) ;
             Table.cells[0,2] := ' Starting Duration ' ;
             Table.cells[1,2] := format('%.2f %s',
                                 [Waveform^.Duration[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,3] := ' Increment by ' ;
             Table.cells[1,3] := format('%.2f %s',
                                 [Waveform^.Increment[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,4] := ' Number of increments ' ;
             Table.cells[1,4] := format( '%5d', [Waveform^.NumSteps] ) ;
             end ;

          wvRamp : begin
             { Voltage ramp }
             if FirstTime then begin
                if Num = 0 then Waveform^.Delay[Num] := Waveform^.RecordDuration * 0.1
                           else Waveform^.Delay[Num] := 0. ;
                Waveform^.RampStart[Num] := -0.1 ;
                Waveform^.RampEnd[Num] := 0.1 ;
                Waveform^.Duration[Num] := Waveform^.RecordDuration * 0.8 ;
                Waveform^.NumSteps := MaxInt( [Waveform^.NumSteps,1 ] ) ;
                end ;
             TableGrp.caption := ' Voltage ramp ' ;
             Table.RowCount := 4;
             Table.ColCount := 2 ;
             Table.cells[0,0] := 'Initial Delay ' ;
             Table.cells[1,0] := format('%.2f %s',[Waveform^.Delay[Num]*Settings.TScale,
                                                   Settings.TUnits] ) ;
             Table.cells[0,1] := ' Start at Amplitude ' ;
             Table.cells[1,1] := format( '%.2f mV', [Waveform^.RampStart[Num]*VoltsTomV] ) ;
             Table.cells[0,2] := ' End at Amplitude ' ;
             Table.cells[1,2] := format( '%.2f mV', [Waveform^.RampEnd[Num]*VoltsTomV] ) ;
             Table.cells[0,3] := ' Ramp Duration ' ;
             Table.cells[1,3] := format('%.2f %s',
                                 [Waveform^.Duration[Num]*Settings.TScale,Settings.TUnits] ) ;
             end ;

          wvPTrain : begin
             { Train of pulses }
             if FirstTime then begin

                if Num = 0 then Waveform^.Delay[Num] := Waveform^.RecordDuration * 0.1
                           else Waveform^.Delay[Num] := 0. ;
                Waveform^.Amplitude[Num] := 0.01 ;
                Waveform^.Duration[Num] := 0.01 ;
                Waveform^.PulseInterval[Num] := 0.1 ;
                Waveform^.NumPulses[Num] := 2 ;
                Waveform^.NumSteps := MaxInt( [Waveform^.NumSteps,1 ] ) ;
                end ;

             TableGrp.caption := ' Train of pulses ' ;
             Table.RowCount := 5 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := 'Initial Delay ' ;
             Table.cells[1,0] := format( '%.2f %s',
                                 [Waveform^.Delay[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,1] := ' Amplitude ' ;
             Table.cells[1,1] := format( '%.2f mV', [Waveform^.Amplitude[Num]*VoltsTomV] ) ;
             Table.cells[0,2] := ' Duration ' ;
             Table.cells[1,2] := format( '%.2f %s',
                                 [Waveform^.Duration[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,3] := ' Pulse interval (within train) ' ;
             Table.cells[1,3] := format( '%.2f %s',
                                 [Waveform^.PulseInterval[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,4] := ' Number of pulses ' ;
             Table.cells[1,4] := format( '%5d', [Waveform^.NumPulses[Num]] ) ;
             end ;

          wvWave : begin
             { Voltage waveform from file }
             TableGrp.caption := ' Waveform from file ' ;
             Table.RowCount := Waveform^.ExtEndOfData + 2 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := ' Initial Delay ' ;
             Table.cells[1,0] := format('%.3g %s',
                                 [Waveform^.Delay[Num]*Settings.TScale,Settings.TUnits] ) ;
             for i := 0 to Waveform^.ExtEndofData do begin
                 Table.cells[0,i+1] := format( '%.3g %s',
                                       [i*Waveform^.ExtDACdt*Settings.TScale,Settings.TUnits]) ;
                 Table.cells[1,i+1] := format( '%.3g mV',
                                              [Waveform^.ExtWaveData[i]*VoltsTomV]) ;
                 end ;
             Waveform^.NumSteps := MaxInt( [Waveform^.NumSteps,1 ] ) ;
             bPasteFromClipboard.enabled := True ;
             end ;

          wvDigStep0 : begin
             { Single digital pulse (with incrementable delay) }
             if FirstTime then begin
                Waveform^.Delay[Num] := Waveform^.RecordDuration * 0.1 ;
                Waveform^.Duration[Num] := 0.1 ;
                Waveform^.Increment[Num] := 0. ;
                Waveform^.Invert[Num] := False ;
                Waveform^.NumSteps := MaxInt( [Waveform^.NumSteps,1 ] ) ;
                end ;

             TableGrp.caption := ' Digital pulse ' ;
             Table.RowCount := 4 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := ' Initial Delay ' ;
             Table.cells[1,0] := format( '%.2f %s',
                                 [Waveform^.Delay[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,1] := ' Increment delay by ' ;
             Table.cells[1,1] := format( '%.2f %s',
                                 [Waveform^.Increment[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,2] := ' Duration ' ;
             Table.cells[1,2] := format( '%.2f %s',
                                 [Waveform^.Duration[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,3] := ' Invert Signal (Yes/No) ' ;
             if Waveform^.Invert[Num] then Table.Cells[1,3] := 'Yes'
                                      else Table.Cells[1,3] := 'No' ;

             end ;

          wvDigStep1 : begin
             { Family of digital pulse steps varying in duration }
             if FirstTime then begin
                Waveform^.Delay[Num] := Waveform^.RecordDuration * 0.1 ;
                Waveform^.Duration[Num] := 0.01 ;
                Waveform^.Increment[Num] := 0.01 ;
                Waveform^.NumSteps := 10 ;
                Waveform^.Invert[Num] := False ;
                end ;

             TableGrp.caption := ' Family of digital pulses (varying in duration) ' ;
             Table.RowCount := 5 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := 'Initial Delay ' ;
             Table.cells[1,0] := format( '%.2f %s',
                                 [Waveform^.Delay[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,1] := ' Starting Duration ' ;
             Table.cells[1,1] := format( '%.2f %s',
                                 [Waveform^.Duration[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,2] := ' Increment by ' ;
             Table.cells[1,2] := format( '%.2f %s',
                                 [Waveform^.Increment[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,3] := ' Number of increments ' ;
             Table.cells[1,3] := format( '%5d', [Waveform^.NumSteps] ) ;
             Table.cells[0,4] := ' Invert Signal (Yes/No) ' ;
             if Waveform^.Invert[Num] then Table.Cells[1,4] := 'Yes'
                                      else Table.Cells[1,4] := 'No'
             end ;

          wvDigTrain : begin
             { Digital pulse train }
             if FirstTime then begin
                Waveform^.Delay[Num] := Waveform^.RecordDuration * 0.1  ;
                Waveform^.Duration[Num] := 0.01 ;
                Waveform^.PulseInterval[Num] := 0.1 ;
                Waveform^.NumPulses[Num] := 2 ;
                Waveform^.Invert[Num] := False ;
                Waveform^.NumSteps := MaxInt( [Waveform^.NumSteps,1 ] ) ;
                end ;

             TableGrp.caption := ' Train of digital pulses ' ;
             Table.RowCount := 5 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := 'Initial Delay ' ;
             Table.cells[1,0] := format( '%.2f %s',
                                 [Waveform^.Delay[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,1] := ' Pulse Duration ' ;
             Table.cells[1,1] := format( '%.2f %s',
                                 [Waveform^.Duration[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,2] := ' Inter-pulse interval ' ;
             Table.cells[1,2] := format( '%.2f %s',
                                 [Waveform^.PulseInterval[Num]*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,3] := ' Number of pulses ' ;
             Table.cells[1,3] := format( '%5d', [Waveform^.NumPulses[Num]] ) ;
             Table.cells[0,4] := ' Invert Signal (Yes/No) ' ;
             if Waveform^.Invert[Num] then Table.Cells[1,4] := 'Yes'
                                      else Table.Cells[1,4] := 'No'
             end ;
             end ;

          end
        else begin
             { Recording sweep parameters }
             Table.RowCount := 6 ;
             Table.ColCount := 2 ;
             TableGrp.caption := ' Recording sweep ' ;
             Table.cells[0,0] := ' Interval between recording sweeps ' ;
             Table.cells[1,0] := format( '%.2f %s',
                                 [Waveform^.RecordInterval*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,1] := ' Duration of recording sweep ' ;
             Table.cells[1,1] := format( '%.2f %s',
                                 [Waveform^.RecordDuration*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,2] := ' Number of repetitions of each waveform ' ;
             Table.cells[1,2] := format( '%7d', [Waveform^.NumRepeats] ) ;
             Table.cells[0,3] := ' Delay before start of recording ' ;
             Table.cells[1,3] := format( '%.2f %s',
                                 [Waveform^.RecordDelay*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,4] := ' Delay Increment ' ;
             Table.cells[1,4] := format( '%.2f %s',
                                 [Waveform^.RecordDelayIncrement*Settings.TScale,Settings.TUnits] ) ;
             Table.cells[0,5] := ' Holding Voltage ' ;
             Table.cells[1,5] := format( '%.2f mV', [Waveform^.HoldingVoltage*VoltsTomV] ) ;
             end ;

        MaxWidth := 0 ;
        RowHeight := Table.canvas.TextHeight(Table.cells[0,0]) ;
        for i := 0 to Table.RowCount-1 do begin
            Table.RowHeights[i] := RowHeight ;
            if Table.canvas.TextWidth(Table.cells[0,i]+' ') > MaxWidth then
               MaxWidth := Table.canvas.TextWidth(Table.cells[0,i]+' ') ;
            end ;
        Table.ColWidths[0] := MaxWidth ;


        end ;


procedure TWavGenFrm.FormShow(Sender: TObject);
{ ----------------------------------
  Initial setup when form is opened
  ---------------------------------}
var
   i,FileHandle,nRead : Integer ;
begin
     { Create buffer to hold D/A waveforms }
     HeapBuffers( Allocate ) ;

     Waveform^.RecordDuration := 0.5 ;
     Waveform^.RecordInterval := 1. ;
     Waveform^.HoldingVoltage := 0. ;
     for i := 0 to High(Waveform^.Shape) do Waveform^.Shape[i] := wvNone ;

     { Set tag and hint properties of tool components }
     { Note. tags contain the waveform type used in the TWaveform record }
     Step0.Tag := Ord( wvStep0 ) ;
     ImageIndex[Step0.Tag] := Step0.ComponentIndex ;

     Step1.Tag := Ord( wvStep1 ) ;
     ImageIndex[Step1.Tag] := Step1.ComponentIndex ;

     Step2.Tag := Ord( wvStep2 ) ;
     ImageIndex[Step2.Tag] := Step2.ComponentIndex ;

     Ramp.Tag := Ord( wvRamp ) ;
     ImageIndex[Ramp.Tag] := Ramp.ComponentIndex ;

     PTRain.Tag := Ord( wvPTrain ) ;
     ImageIndex[PTrain.Tag] := PTrain.ComponentIndex ;

     Wave.Tag := Ord( wvWave ) ;
     ImageIndex[Wave.Tag] := Wave.ComponentIndex ;

     None.Tag := Ord(wvNone) ;
     None.hint := 'No waveform' ;
     ImageIndex[None.Tag] := None.ComponentIndex ;

     DigStep0.tag := Ord(wvDigStep0) ;
     ImageIndex[DigStep0.Tag] := DigStep0.ComponentIndex ;

     DigStep1.tag := Ord(wvDigStep1) ;
     ImageIndex[DigStep1.Tag] := DigStep1.ComponentIndex ;

     DigTrain.tag := Ord(wvDigTrain) ;
     ImageIndex[DigTrain.Tag] := DigTrain.ComponentIndex ;

     { Display general recording sweep parameter table}


     { Create "Link To" protocol list }
     CreateVProgramList( cbNextProtocol ) ;

     { Update patch/voltage clamp command voltage divide factor
       (Note that this value is not stored with the voltage protocol
        it is part of the settings data stored in the INI file) }
     edVDivide.text := format( '%6.1f', [Settings.VCommand.DivideFactor] ) ;

     { Waveform generator No Display check box setting }
     ckNoDisplay.checked := Settings.WavGenNoDisplay ;

     { Load the voltage protocol in current use (if any) }
     if (Settings.VProgramFileName <> '' )
        and FileExists(Settings.VProgramFileName) then begin
            Waveform^.FileName := Settings.VProgramFileName ;
            Caption := 'Stimulus Generator : ' + Waveform^.FileName ;
            FileHandle := FileOpen( Waveform^.FileName, fmOpenReadWrite ) ;
            if Sizeof(Waveform^) = FileRead( FileHandle, Waveform^,
                                   Sizeof(Waveform^) )then UpdateIcons
            else MessageDlg( ' File Read - Failed ', mtWarning, [mbOK], 0 ) ;
            FileClose( FileHandle ) ;

            FillParameterTable(-1,False) ;
            shSelected.Left := TIMage(Sender).Left ;
            shSelected.Top := TIMage(Sender).Top + TIMage(Sender).Height ;
            shSelected.Width := TIMage(Sender).Width ;
            Waveform^.Saved := True ;
            end
    else begin
         { Create a new voltage protocol if none in use at the moment}
         NewWaveform ;
         end ;

    shSelected.brush.style := bsClear ;
    shSelected.Left := RecSweep.Left ;
    shSelected.Top := RecSweep.Top + RecSweep.Height ;
    shSelected.Width := RecSweep.Width ;

    Timer1.enabled := True ;
    TableChanged := False ;
    {pbVDisplay.Repaint ;}
    end ;


procedure TWavGenFrm.bNewClick(Sender: TObject);
begin
     { Create a new voltage program }
     NewWaveform ;
     end;


procedure TWavGenFrm.TableKeyPress(Sender: TObject; var Key: Char);
begin
     TableChanged := True ;
     if key = chr(13) then begin
        UpdateWaveform ;
        FillParameterTable(CurrentWaveformNum,False) ;
        end ;
     end ;


procedure TWavGenFrm.UpdateWaveform ;
{ ----------------------------------------------
  Update appropriate variable in waveform record
  when user makes a change to parameter table
  ---------------------------------------------- }
var
   Val, ValDef, Total : Single ;
   iVal, iValDef, Num : Integer ;
begin
     Num := CurrentWaveformNum ;
     if Num >= 0 then begin
         case Waveform^.Shape[Num] of
             wvStep0 : begin
                  { Rectangular step (fixed size) }
                  UpdateEntry(Table.Cells[1,0],Waveform^.Delay[Num],Settings.TScale,True) ;
                  UpdateEntry(Table.Cells[1,1],Waveform^.Amplitude[Num],VoltsTomV,False) ;
                  UpdateEntry(Table.Cells[1,2],Waveform^.Duration[Num],Settings.TScale,True) ;
                  end ;
             wvStep1 : begin
                  { Family of steps varying in amplitude }
                  UpdateEntry(Table.Cells[1,0],Waveform^.Delay[Num],Settings.TScale,True);
                  UpdateEntry(Table.Cells[1,1],Waveform^.Amplitude[Num],VoltsTomV,False);
                  UpdateEntry(Table.Cells[1,2],Waveform^.Increment[Num],VoltsTomV,False) ;
                  iVal := ExtractInt(Table.Cells[1,3]) ;
                  if iVal > 0 then Waveform^.NumSteps := iVal ;
                  UpdateEntry(Table.Cells[1,4],Waveform^.Duration[Num],Settings.TScale,True) ;
                  end ;
             wvStep2 : begin
                  { Family of steps varying in duration }
                  UpdateEntry(Table.Cells[1,0],Waveform^.Delay[Num],Settings.TScale,True);
                  UpdateEntry(Table.Cells[1,1],Waveform^.Amplitude[Num],VoltsTomV,False);
                  UpdateEntry(Table.Cells[1,2],Waveform^.Duration[Num],Settings.TScale,True);
                  UpdateEntry(Table.Cells[1,3],Waveform^.Increment[Num],Settings.TScale,False);
                  iVal := ExtractInt(Table.Cells[1,4]) ;
                  if iVal > 0 then Waveform^.NumSteps := iVal ;
                  end ;
             wvRamp : begin
                  { Voltage ramp }
                  UpdateEntry(Table.Cells[1,0],Waveform^.Delay[Num],Settings.TScale,True);
                  UpdateEntry(Table.Cells[1,1],Waveform^.RampStart[Num],VoltsTomV,False);
                  UpdateEntry(Table.Cells[1,2],Waveform^.RampEnd[Num],VoltsTomV,False);
                  UpdateEntry(Table.Cells[1,3],Waveform^.Duration[Num],Settings.TScale,True) ;
                  end ;
             wvPTrain : begin
                  { Train of pulses }
                  UpdateEntry(Table.Cells[1,0],Waveform^.Delay[Num],Settings.TScale,True);
                  UpdateEntry(Table.Cells[1,1],Waveform^.Amplitude[Num],VoltsTomV,False);
                  UpdateEntry(Table.Cells[1,2],Waveform^.Duration[Num],Settings.TScale,True);
                  UpdateEntry(Table.Cells[1,3],Waveform^.PulseInterval[Num],Settings.TScale,True);
                  iVal := ExtractInt(Table.Cells[1,4]) ;
                  if iVal > 0 then Waveform^.NumPulses[Num] := iVal ;
                  end ;
             wvWave : begin
                  UpdateEntry(Table.Cells[1,0],Waveform^.Delay[Num],Settings.TScale,True);
                  UpdateEntry(Table.Cells[1,1],Waveform^.ExtWaveData[Table.Row-2],
                                  VoltstomV,False)
                  end ;
             wvDigStep0 : begin
                  { Single digital pulse }
                  UpdateEntry(Table.Cells[1,0],Waveform^.Delay[Num],Settings.TScale,true);
                  UpdateEntry(Table.Cells[1,1],Waveform^.Increment[Num],Settings.TScale,False);
                  UpdateEntry(Table.Cells[1,2],Waveform^.Duration[Num],Settings.TScale,True);
                  if Pos('Y',UpperCase(Table.Cells[1,3])) > 0 then
                       Waveform^.Invert[Num] := True
                  else Waveform^.Invert[Num] := False ;
                  end ;
               wvDigStep1 : begin
                  { Variable duration digital pulse }
                  UpdateEntry(Table.Cells[1,0],Waveform^.Delay[Num],Settings.TScale,true);
                  UpdateEntry(Table.Cells[1,1],Waveform^.Duration[Num],Settings.TScale,True);
                  UpdateEntry(Table.Cells[1,2],Waveform^.Increment[Num],Settings.TScale,False);
                  iVal := ExtractInt(Table.Cells[1,3]) ;
                  if iVal > 0 then Waveform^.NumSteps := iVal ;
                  if Pos('Y',UpperCase(Table.Cells[1,4])) > 0 then
                       Waveform^.Invert[Num] := True
                  else Waveform^.Invert[Num] := False ;
                  end ;
             wvDigTrain : begin
                  { Digital pulse train }
                  UpdateEntry(Table.Cells[1,0],Waveform^.Delay[Num],Settings.TScale,true);
                  UpdateEntry(Table.Cells[1,1],Waveform^.Duration[Num],Settings.TScale,True);
                  UpdateEntry(Table.Cells[1,2],Waveform^.PulseInterval[Num],Settings.TScale,True);
                  iVal := ExtractInt(Table.Cells[1,3]) ;
                  if iVal > 0 then Waveform^.NumPulses[Num] := iVal ;
                  if Pos('Y',UpperCase(Table.Cells[1,4])) > 0 then
                       Waveform^.Invert[Num] := True
                  else Waveform^.Invert[Num] := False ;
                  end ;
             end;
         end
     else begin
         { Num=-1 ... Recording sweep parameters }
         UpdateEntry(Table.Cells[1,0],Waveform^.RecordInterval,Settings.TScale,True);
         UpdateEntry(Table.Cells[1,1],Waveform^.RecordDuration,Settings.TScale,True);
         iVal := ExtractInt(Table.Cells[1,2]) ;
         if iVal > 0 then Waveform^.NumRepeats := iVal ;
         UpdateEntry(Table.Cells[1,3],Waveform^.RecordDelay,Settings.TScale,True);
         UpdateEntry(Table.Cells[1,4],Waveform^.RecordDelayIncrement,Settings.TScale,True);
         UpdateEntry(Table.Cells[1,5],Waveform^.HoldingVoltage,VoltsTomV,False) ;
         end ;

     { Mark waveform as needing to be saved }
     Waveform^.Saved := False ;
     TableChanged := False ;
     pbVDisplay.RePaint ;
     end ;


procedure TWavGenFrm.bSaveClick(Sender: TObject);
{ -----------------------------
  Save voltage protocol to file
  -----------------------------}
var
   FileHandle : Integer ;
begin
     SaveDialog.options := [ofOverwritePrompt,ofHideReadOnly,ofPathMustExist] ;
     SaveDialog.FileName := ExtractFileName( SaveDialog.FileName ) ;
     SaveDialog.InitialDir := Settings.VProtDirectory ;
     SaveDialog.Title := 'Save Stimulus Protocol' ;
     if FileExists(Waveform^.FileName) then SaveDialog.FileName := Waveform^.FileName
                                       else SaveDialog.FileName := '*.vpr' ;
     if SaveDialog.execute then begin
        { Save waveform record to file }
        Waveform^.FileName := SaveDialog.FileName ;
        Caption := 'Stimulus Generator : ' + Waveform^.FileName ;
        Waveform^.FileName := ReplaceFileEnding( Waveform^.FileName, '.vpr' ) ;
        FileHandle := FileCreate( Waveform^.FileName ) ;
        if FileWrite(FileHandle,Waveform^,Sizeof(Waveform^)) <> Sizeof(Waveform^) then
           MessageDlg( ' WavGen: File Write Failed ', mtWarning, [mbOK], 0 ) ;
        FileClose( FileHandle ) ;
        Waveform^.Saved := True ;
        { Update Link To protocol list }
        CreateVProgramList( cbNextProtocol ) ;
        end ;
     end ;


procedure TWavGenFrm.bOpenClick(Sender: TObject);
{ ------------------------------------------------------------
  When Open File button clicked load voltage program from file
  ------------------------------------------------------------}
var
   FileHandle : Integer ;
begin

     { If the current waveform has been edited ... offer to save it to file }
     if not Waveform^.Saved then begin
        if MessageDlg( ' Waveform Changed! Save it to ' + Waveform^.FileName,
           mtConfirmation,[mbYes,mbNo], 0 ) = mrYes then begin
           FileHandle := FileCreate( Waveform^.FileName ) ;
           if Sizeof(Waveform^) <> FileWrite(FileHandle,Waveform^,
                                   Sizeof(Waveform^)) then
              MessageDlg( ' File Write - Failed ', mtWarning, [mbOK], 0 ) ;
           FileClose( FileHandle ) ;
           Waveform^.Saved := True ;

           end ;
        end ;

     OpenDialog.options := [ofOverwritePrompt,ofHideReadOnly,ofPathMustExist] ;
     OpenDialog.FileName := ExtractFileName( SaveDialog.FileName ) ;
     OpenDialog.InitialDir := Settings.VProtDirectory ;
     OpenDialog.Title := 'Load Stimulus Protocol' ;
     if OpenDialog.execute then begin
        { Load voltage program record to file }
        LoadVProgramFromFile( OpenDialog.FileName, Waveform^ ) ;
        Caption := 'Stimulus Generator : ' + Waveform^.FileName ;
        { Update display icons }
        UpdateIcons ;
         { Update leak subtraction buttons }
        if Waveform^.NumLeaks <> 0 then begin
           rbPNLeak.checked := True ;
           edNumLeaks.text := format( ' %d pulses', [Waveform^.NumLeaks] ) ;
           edLeakScale.text := format( ' %d ', [Waveform^.LeakScale] ) ;
           end
        else rbNoLeak.checked := True ;
        Waveform^.Saved := True ;
        { Make sure recording options table is displayed }
        FillParameterTable(-1,False) ;
        shSelected.Left := RecSweep.Left ;
        shSelected.Top := RecSweep.Top + RecSweep.Height ;
        shSelected.Width := RecSweep.Width ;
        pbVDisplay.Repaint ;
        end ;
     end ;


procedure TWavGenFrm.NewWaveform ;
{ --------------------------
  Create a new waveform file
  -------------------------- }
var
   i : Integer ;
begin

     { Set waveform elements to none }
     for i := 0 to High(Waveform^.Shape) do begin
         Waveform^.Shape[i] := wvNone ;
         Waveform^.Delay[i] := 0. ;
         Waveform^.Duration[i] := 0. ;
         end ;

     { Update waveform icons }
     UpdateIcons ;

     Waveform^.NumRepeats := 1 ;
     Waveform^.NumSteps := 1 ;
     Waveform^.RecordDuration := 0.5 ;
     Waveform^.RecordInterval := 1. ;
     Waveform^.HoldingVoltage := 0. ;
     Waveform^.RecordDelay := 0. ;
     Waveform^.RecordDelayIncrement := 0. ;
     Waveform^.NumLeaks := 0 ;
     Waveform^.DigitalInUse := False ;
     Waveform^.FileName := Settings.VProtDirectory + 'Untitled.vpr' ;
     WavGenFrm.caption := 'Stimulus Generator : ' + Waveform^.FileName ;
     Waveform^.Saved := True ;
     Waveform^.RecordIntervalChanged := False ;
     Waveform^.ExtEndofData := -1 ;
     Waveform^.DACdt := 0.001 ;
     Waveform^.ExtDACdt := 0.001 ;
     FillParameterTable(-1,False) ;
{     shSelected.BoundsRect := RecSweep.BoundsRect ;}
     shSelected.Height := 2 ;
     shSelected.Width := RecSweep.Width ;
     shSelected.Top := RecSweep.Top + RecSweep.Height ;
     pbVDisplay.Repaint ;
     end;



procedure TWavGenFrm.UpdateIcons ;
{ -----------------------------------
  Update waveform shape display icons
  ----------------------------------- }
begin
      Prot0.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[0])]]).Picture ;
      Prot1.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[1])]]).Picture ;
      Prot2.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[2])]]).Picture ;
      Prot3.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[3])]]).Picture ;
      Prot4.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[4])]]).Picture ;
      Prot5.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[5])]]).Picture ;
      Prot6.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[6])]]).Picture ;
      Prot7.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[7])]]).Picture ;
      Prot8.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[8])]]).Picture ;
      Prot9.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[9])]]).Picture ;
      Dig0.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[10])]]).Picture ;
      Dig1.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[11])]]).Picture ;
      Dig2.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[12])]]).Picture ;
      Dig3.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[13])]]).Picture ;
      Dig4.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[14])]]).Picture ;
      Dig5.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[15])]]).Picture ;
      Dig6.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[16])]]).Picture ;
      Dig7.Picture := TIMage(Components[ImageIndex[Ord(Waveform^.Shape[17])]]).Picture ;

      { Update leak subtraction buttons }
      if Waveform^.NumLeaks <> 0 then begin
           rbPNLeak.checked := True ;
           edNumLeaks.enabled := True ;
           edLeakScale.enabled := True ;
           edNumLeaks.text := format( ' %d pulses', [Waveform^.NumLeaks] ) ;
           edLeakScale.text := format( ' %d ', [Waveform^.LeakScale] ) ;
           end
      else begin
           rbNoLeak.checked := True ;
           edNumLeaks.enabled := False ;
           edLeakScale.enabled := False ;
           end ;

      cbNextProtocol.ItemIndex := cbNextProtocol.Items.IndexOf(
                                 ExtractFileNameOnly(Waveform^.NextProtocolFileName)) ;
      end ;


procedure TWavGenFrm.CheckWaveformDuration ;
{ --------------------------------------------------------
  Warn user if recording interval has had to be increased
  --------------------------------------------------------}
begin

     if Waveform^.RecordIntervalChanged then begin
        Waveform^.StepCounter := 0 ;
        CreateWaveform( Waveform^, DAC^, DigBuf^ ) ;
        MessageDlg( format('Interval between records too small! Changed to %.0f %s',
                    [Waveform^.RecordInterval*Settings.TScale,Settings.TUnits]),
                              mtWarning, [mbOK], 0 ) ;
        Waveform^.RecordIntervalChanged := False ;
        Waveform^.Saved := False ;
        end ;
     end ;


procedure TWavGenFrm.rbNoLeakClick(Sender: TObject);
{ --------------------------
  Turn off leak subtraction
  -------------------------}
begin
     Waveform^.NumLeaks := 0 ;
     edNumLeaks.enabled := False ;
     edLeakScale.enabled := False ;
     Waveform^.Saved := False ;
     end;

procedure TWavGenFrm.rbPNLeakClick(Sender: TObject);
begin
     UpdateNumLeaks ;
     edNumLeaks.enabled := True ;
     edLeakScale.enabled := True ;
     Waveform^.Saved := False ;
     end;

procedure TWavGenFrm.UpdateNumLeaks ;
begin
     if rbPNLeak.checked then begin
        Waveform^.NumLeaks := ExtractInt( edNumLeaks.text ) ;
        Waveform^.NumLeaks := IntLimitTo( Waveform^.NumLeaks,0, 1000 ) ;
        edNumLeaks.text := format( '%d pulses', [Waveform^.NumLeaks] ) ;
        Waveform^.LeakScale := ExtractInt( edLeakScale.text ) ;
        Waveform^.LeakScale := IntLimitTo( Waveform^.LeakScale, -32, 32 ) ;
        if Waveform^.LeakScale = 0 then Waveform^.LeakScale := 1 ;
        end
     else begin
        Waveform^.NumLeaks := 0 ;
        rbNoLeak.checked := True ;
        end ;
     end ;

procedure TWavGenFrm.FormHide(Sender: TObject);
begin
     { De-allocate memory }
     HeapBuffers( Deallocate ) ;
     end;




procedure TWavGenFrm.UpdateEntry( const SourceText : string ;
                       var Destination : single ;
                       ConversionFactor : single ;
                       PositiveOnly : boolean ) ;
{ Update the variable "Destination" with the ASCII number in "SourceText"
  after applying the conversion factor "ConversionFactor". }
var
   val : single ;
begin
     Val := ExtractFloat(SourceText,Destination*ConversionFactor) ;
     if PositiveOnly and (Val < 0. ) then Val := Destination*ConversionFactor ;
     Destination := Val / ConversionFactor ;
     end ;


procedure TWavGenFrm.bPasteFromClipboardClick(Sender: TObject);
{ ---------------------------------------------------------------
  Paste voltage waveform data from clipboard into waveform record
  ---------------------------------------------------------------}
const
     BufSize = 30000 ;
var
   T,TOld : single ;
   nValues,i,iSkip,NumChars : Integer ;
   DataList : TStringList ;
   Values : Array[0..2] of single ;
   MaxRecordInterval : single ;
   Buf0 : PChar ;
begin

     try
        { Allocate buffers }
        DataList := TStringList.create ;
        Buf0 := StrAlloc( BufSize ) ;
        { Get ASCII waveform text list from from clipboard }
        NumChars := ClipBoard.GetTextBuf( Buf0, BufSize ) ;
        { Copy list into string list }
        DataList.SetText( Buf0 ) ;

        { Convert to floating point and store in waveform record}

        { Determine how many data points must be skipped to make
          the waveform fit into buffer }
        iSkip := (DataList.Count + High(Waveform^.ExtWaveData)) div
                 (High(Waveform^.ExtWaveData)+1) ;
        if iSkip > 1 then
               MessageDlg( format('Too many point in pasted waveform, 1/%d used.',
                           [iSkip]),mtWarning, [mbOK], 0 ) ;
        i := 0 ;
        Waveform^.ExtEndofData := -1 ;
        repeat
            { Extract number from row }
            nValues := ExtractListOfFloats( DataList[i], Values, True ) ;
            if nValues >= 2 then begin
               Inc(Waveform^.ExtEndofData) ;
               TOld := T ;
               T := Values[0] ;
               if Waveform^.ExtEndofData = 1 then
                  Waveform^.ExtDACdt := (T - TOld)*Settings.TUnScale ;
               { Read voltage value }
               Waveform^.ExtWaveData[Waveform^.ExtEndofData] := Values[1]*mVtoVolts ;
               end
            else Waveform^.ExtWaveData[Waveform^.ExtEndofData] := 0.0 ;
            i := i + iSkip ;
            until i >= DataList.Count ;

            { If the required D/A output interval cannot support the
              currently selected inter-record interval, reduce the interval
              to the maximum that can be supported }

            MaxRecordInterval := ((Waveform^.ExtDACdt*(MaxTBuf-1))/2.0) ;
            if Waveform^.RecordInterval > MaxRecordInterval then begin
               Waveform^.RecordInterval := MaxRecordInterval ;
               MessageDlg( format(' Recording interval changed to %.3g s.',
                           [MaxRecordInterval]),mtWarning, [mbOK], 0 ) ;
               end ;
            if Waveform^.RecordDuration > MaxRecordInterval then
               Waveform^.RecordDuration := MaxRecordInterval - 0.1 ;
     finally
        StrDispose( Buf0 ) ;
        DataList.Free ;
        end ;

     { Update table with waveform data }
     FillParameterTable( CurrentWaveformNum ,True) ;
     pbVDisplay.Repaint ;
     end;


procedure TWavGenFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
     { Disable Waveform Generator menu item }
     Main.WaveformGenerator.enabled := True ;
     Action := caFree ;
     end;


procedure TWavGenFrm.FormActivate(Sender: TObject);
begin
     pbVDisplay.RePaint ;
     end;

procedure TWavGenFrm.FormResize(Sender: TObject);
begin
     pbVDisplay.Repaint ;
     FillParameterTable(-1,False) ;
end;

procedure TWavGenFrm.TableExit(Sender: TObject);
begin
     if TableChanged then UpdateWaveform ;
     end;


procedure TWavGenFrm.edNumLeaksKeyPress(Sender: TObject; var Key: Char);
begin
     if key = chr(13) then UpdateNumLeaks ;
     end;

procedure TWavGenFrm.edLeakScaleKeyPress(Sender: TObject; var Key: Char);
begin
     if key = chr(13) then UpdateNumLeaks ;
     end;

procedure TWavGenFrm.cbNextProtocolChange(Sender: TObject);
begin
    if cbNextProtocol.text = '' then
       Waveform^.NextProtocolFileName := ''
    else
       Waveform^.NextProtocolFileName := Settings.VProtDirectory +
                                         cbNextProtocol.text + '.vpr' ;
    Waveform^.Saved := False ;
    end;


procedure TWavGenFrm.pbVDisplayPaint(Sender: TObject);
{ -----------------------------
  Update paintbox areas on form
  -----------------------------}
var
   Step,yZero,i,j : Integer ;
   xScale, yScale, DACScale,VRange : Single ;
   xPix,yPix,yPixOld,xPix0,xPix1 : Integer ;
   BitValue,OldValue,BitMask,Bit,DigLevel,DigHeight : Integer ;
   MaxDACVolts : single ;
begin
     { Erase various parts of voltage protocol display }
     pbVDisplay.canvas.brush.color := clWhite ;
     pbVDisplay.canvas.FillRect( Rect( 0,0,pbVDisplay.Width-1,pbVDisplay.Height-1) ) ;
     lbVDisplay.left := pbVDisplay.Left - lbVDisplay.Width - 3 ;
     lbVDisplay.Top := pbVDisplay.Top + (pbVDisplay.Height div 2) ;
     { Erase digital pulse protocol display }
     pbDigDisplay.canvas.brush.color := clWhite ;
     pbDigDisplay.canvas.FillRect( Rect( 0,0,pbDigDisplay.Width-1,pbDigDisplay.Height-1) ) ;
     lbDigDisplay.left := pbDigDisplay.Left - lbDigDisplay.Width - 3 ;
     lbDigDisplay.Top := pbDigDisplay.Top + (pbDigDisplay.Height div 2) ;

     { Get the maximum positive D/A voltage }
     MaxDACVolts := GetMaxDACVolts ;

     if not ckNoDisplay.checked then begin
        { Plot each waveform step on command voltage display }
        for Step := 0 to Waveform^.NumSteps-1 do begin
            { Create waveform in DAC buffer }
            Waveform^.StepCounter := Step ;
            CreateWaveform( Waveform^, DAC^, DigBuf^ ) ;
            if Waveform^.EndofProtocol > 0 then begin

               { Display D/A update interval }
               edDACInterval.text := format( ' %.1f ms',[Waveform^.DACdt*SecsToms]);

               { Display scaling factors }
               xScale := (pbVDisplay.Width) / MaxInt([Waveform^.EndofProtocol,1]) ;
               DACScale := (MaxDACValue*Settings.VCommand.DivideFactor) / MaxDACVolts ;
               { Set voltage display range to +/-200mV ... but adjust to larger
                 range if necessary to accomodate waveform }
               VRange := MaxFlt( [2.*Abs(Waveform^.VMin),2.*Abs(Waveform^.VMax),0.4 ] ) ;
               if VRange > 0.5 then begin
                  lbVmax.caption := format( '%.2f mV V', [ VRange*0.5] ) ;
                  lbVmin.caption := format( '%.2f mV V', [-VRange*0.5] ) ;
                  end
               else begin
                  lbVmax.caption := format( '%6.0f mV', [ VRange*500.] ) ;
                  lbVmin.caption := format( '%6.0f mV', [-VRange*500.] ) ;
                  end ;
               yScale := (pbVDisplay.Height) / (VRange  * DACScale);
               yZero := pbVDisplay.Height div 2 ;

               { Plot waveform from D/A buffer }
               j := VComm ;
               for i := 0 to Waveform^.EndofProtocol do begin
                   xPix := Trunc(xScale*i) ;
                   yPix := yZero - Trunc( DAC^[j]*yScale ) ;
                   if i = 0 then begin
                      pbVDisplay.canvas.MoveTo( xPix,yPix ) ;
                      yPixOld := yPix ;
                      end
                   else begin
                      pbVDisplay.canvas.LineTo( xPix,yPixOld ) ;
                      pbVDisplay.canvas.LineTo( xPix,yPix ) ;
                      end ;
                   yPixOld := yPix ;
                   j := j + NumDACChannels ;
                   end ;

               { Display digital pulse patterns (up to 8 ports) }

               if Waveform^.DigitalInUse then begin
                  BitMask := 1 ;
                  DigLevel := pbDigDisplay.Height - 1 ;
                  DigHeight := (pbDigDisplay.Height div 8) - 2 ;
                  for Bit := 0 to 7 do begin

                      { Set display cursor to start of digital trace }
                      xPix := 0 ;
                      BitValue := DigBuf^[0] and BitMask ;
                      OldValue := BitValue ;
                      yPix := DigLevel ;
                      if BitValue <> 0 then yPix := yPix - DigHeight ;
                      pbDigDisplay.canvas.MoveTo( xPix,yPix ) ;
                      yPixOld := yPix ;

                      { Display digital trace }
                      for i := 0 to Waveform^.EndofProtocol do begin
                          BitValue := DigBuf^[i] and BitMask ;
                          if (BitValue <> OldValue)
                             or (i=Waveform^.EndofProtocol) then begin
                             xPix := Trunc(xScale*i) ;
                             pbDigDisplay.canvas.LineTo( xPix,yPixOld ) ;
                             yPix := DigLevel ;
                             if BitValue <> 0 then yPix := yPix - DigHeight ;
                             pbDigDisplay.canvas.LineTo( xPix,yPix ) ;
                             yPixOld := yPix ;
                             OldValue := BitValue ;
                             end ;
                          end ;

                      BitMask := BitMask shl 1 ;
                      DigLevel := DigLevel - (pbDigDisplay.Height div 8)
                      end ;
                  end ;
               end ;
            end ;
        end ;

     { Plot rectangles indicating part of waveform during which recording occurs }
     xScale := pbVDisplay.Width / (Waveform^.EndOfProtocol*Waveform^.DACdt) ;
     xPix0 := Trunc( xScale*(WaveformCreationTime +
                            Waveform^.RecordDelay +
                            Step*Waveform^.RecordDelayIncrement) ) ;

     shRecordingPeriod.Brush.color := clRed ;
     shRecordingPeriod.top := pbDigDisplay.Top + pbDigDisplay.Height + 1 ;
     shRecordingPeriod.left := pbDigDisplay.Left + xPix0 ;
     shRecordingPeriod.Width := Trunc( xScale*Waveform^.RecordDuration) ;

     lbRecording.Left := shRecordingPeriod.Left - lbRecording.Width - 3 ;
     lbRecording.Top := shRecordingPeriod.Top ;

     lbRecordTime.top := shRecordingPeriod.top + shRecordingPeriod.Height + 1 ;
     lbRecordTime.left := shRecordingPeriod.left ;
     lbRecordTime.caption := format( '%6.1f %s',
                     [Settings.TScale*Waveform^.RecordDuration,Settings.TUnits] ) ;
     end ;


procedure TWavGenFrm.Timer1Timer(Sender: TObject);
{ -----------------------------------------------------
  Ensure table row width and column heights are correct
  -----------------------------------------------------}
var
   MaxWidth,RowHeight,i : Integer ;
begin
     MaxWidth := 0 ;
        RowHeight := Table.canvas.TextHeight(Table.cells[0,0]) ;
        for i := 0 to Table.RowCount-1 do begin
            Table.RowHeights[i] := RowHeight ;
            if Table.canvas.TextWidth(Table.cells[0,i]+' ') > MaxWidth then
               MaxWidth := Table.canvas.TextWidth(Table.cells[0,i]+' ') ;
            end ;
        Table.ColWidths[0] := MaxWidth ;

     Timer1.enabled := false ;
     end;

procedure TWavGenFrm.ckNoDisplayClick(Sender: TObject);
begin
     Settings.WavGenNoDisplay := ckNoDisplay.Checked ;
     if not ckNoDisplay.Checked then pbVDisplay.RePaint ;
     end;




procedure TWavGenFrm.EdVDivideKeyPress(Sender: TObject; var Key: Char);
{ ------------------------------------
  Update command voltage divide factor
  ------------------------------------}
begin
     if key = chr(13) then begin
        Settings.VCommand.DivideFactor := ExtractFloat(edVDivide.text,
                                                 Settings.VCommand.DivideFactor) ;
        edVDivide.text := format( '%6.1f', [Settings.VCommand.DivideFactor] ) ;
        end ;
     end;

end.
