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
  2/7/99   V3.0 32 bit version
  11/12/99 Stimulus Protocol list in Record To Disk window now updated
           if that window is open
  20/7/01 Num Leaks and Leak scale labels now visible
  20/12/01 Updated to use SESLabIO.DACMaxValue for scaling display
  18.7.02  RecordInterval now automatically updated to ensure that it is
           5% (or 300ms) greater than RecordDuration
  5/2/04 ..... Normal/Inverted trigger pulse setting added
  30/3/04 .... User-defined waveforms can now contain negative voltages
  2/11/04 .... Two DAC channels now supported
               Waveform display now much faster
  16/12/04 ... Display now ignores zero duration elements
               Changes to waveform elements now trigger protocol save
  20/05.05 ... (V3.5.6) Number of pulse increments for each waveform element
               now stored in Prog.NumPulses array (except for pulse trains)
               So number of increments in pulse families no longer set to 1
               when additional single pulse elements added
  04/07/05 ... Ramps now displayed correctly (changed to PlotVStep)
  05/07/05 ... No. steps in DAC0 families no longer set to one when DAC1 has single steps
               Pulse families with incrementing duration now displayed correctly
  19/07/05 ... Pointer fault when blank Next Protocol selected fixed
               Time course of user defined waveforms now adapted to to DAC update interval
  05/06/07 ... DAC update interval box now updated correctly
  06/06/07 ... DigStep0 no longer asks for increment which isn't used
  04/06/08 ... Blank Dig step now erases existing digital waveforms
  13/05/09 ... Long user-defined waveforms now supported
               First point in display no longer inverted when non-zero holding potential in use
  =====================================================================}
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, StdCtrls, Grids, Global, SHared, ClipBrd,
  fileio, maths, Spin, ScopeDisplay, ValEdit, ValidatedEdit, math, strutils  ;

const
    NumDACChannels = 2 ;
    NumDigChannels = 8 ;
    NumDisplayChannels = NumDACChannels + NumDigChannels ;
    // Plot channels
    DAC0 = 0 ;
    DACPlotStart = 0 ;
    DACPlotEnd = 1 ;
    DAC1 = 1 ;
    DigPlotStart = 2 ;
    DigPlotEnd = 9 ;

    MaxProtocolShapes = 28 ;
    // DAC0 shape #s
    DAC0Start = 0 ;
    DAC0End = 9 ;
    // Digital shape #s
    DigStart = 10 ;
    DigEnd = 17 ;
    // DAC1 shape #s
    DAC1Start = 18 ;
    DAC1End = 27 ;

type

  TDisplayChannel = record
      X : Single ;
      Y : Single ;
      XStart : Single ;
      YStart : Single ;
      XScale : Single ;
      YScale : Single ;
      XZero : Single ;
      YZero : Single ;
      end ;

  TWavGenFrm = class(TForm)
    ControlGrp: TGroupBox;
    bOpen: TButton;
    bSave: TButton;
    bNew: TButton;
    ProtocolGrp: 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;
    lbDig0: TLabel;
    RecSweep: TImage;
    Label3: TLabel;
    DigStep0: TImage;
    DigStep1: TImage;
    DigTrain: TImage;
    LeakGrp: TGroupBox;
    rbNoLeak: TRadioButton;
    rbPNLeak: TRadioButton;
    SaveDialog: TSaveDialog;
    OpenDialog: TOpenDialog;
    DACGrp: TGroupBox;
    Label4: TLabel;
    shSelected: TShape;
    GroupBox1: TGroupBox;
    cbNextProtocol: TComboBox;
    bPasteFromClipboard: TButton;
    lbLeakScale: TLabel;
    Timer1: TTimer;
    edVDivide0: TValidatedEdit;
    edNumLeaks: TValidatedEdit;
    edLeakScale: TValidatedEdit;
    lbNumLeaks: TLabel;
    lbDig1: TLabel;
    lbDig2: TLabel;
    lbDig3: TLabel;
    lbDig4: TLabel;
    lbDig5: TLabel;
    lbDig6: TLabel;
    lbDig7: TLabel;
    DigNone: TImage;
    Label2: TLabel;
    Label6: TLabel;
    DisplayGrp: TGroupBox;
    pbDisplay: TPaintBox;
    Label7: TLabel;
    Prot18: TImage;
    Prot19: TImage;
    Prot20: TImage;
    Prot21: TImage;
    Prot22: TImage;
    Prot23: TImage;
    Prot24: TImage;
    Prot25: TImage;
    Prot26: TImage;
    Prot27: TImage;
    edVDivide1: TValidatedEdit;
    Label8: TLabel;
    bSetStimFolder: TButton;
    GroupBox2: TGroupBox;
    rbTmsecs: TRadioButton;
    rbTsecs: TRadioButton;
    bLoadFromFile: TButton;
    GroupBox3: TGroupBox;
    edDACInterval: TEdit;
    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 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 pbDisplayPaint(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure edVDivide0KeyPress(Sender: TObject; var Key: Char);
    procedure edVDivide1KeyPress(Sender: TObject; var Key: Char);
    procedure bSetStimFolderClick(Sender: TObject);
    procedure bLoadFromFileClick(Sender: TObject);
    procedure rbTmsecsClick(Sender: TObject);
    procedure rbTsecsClick(Sender: TObject);
  private
    { Private declarations }
   PlotChannel : Array[0..MaxProtocolShapes-1] of Integer ;
   DisplayChannel : Array[0..31] of TDisplayChannel ;
   TScale : Single ; // Time display units scaling
   TUnits : String ; // Time display units
   //DAC : TSmallIntArray ;         // D/A waveform buffer
   //DigBuf : TSmallIntArray ;      // Digital pattern buffer

    Procedure SetCursor( SetDrag : Boolean ) ;
    procedure FillParameterTable( Num : Integer ; FirstTime : Boolean ) ;

    procedure NewWaveform ;
    procedure LoadWaveform( FileName : string ) ;
    procedure SaveWaveform( FileName : string ) ;
    procedure UpdateIcons ;

    {procedure CheckWaveformDuration ;}
    procedure UpdateNumLeaks ;

    procedure UpdateEntry( const SourceText : string ;
                       var Destination : single ;
                       ConversionFactor : single ;
                       PositiveOnly : boolean ) ;
    procedure SetNumSteps( NewValue : Integer ) ;

    procedure UpdateWaveform ;

    procedure PlotVStep(
              ChanNum : Integer ;
              Amplitude : Single ;
              Time : Single
              ) ;
    procedure PlotVLine(
              ChanNum : Integer ;
              Amplitude : Single ;
              Time : Single
              ) ;
    procedure PlotDigStep(
              ChanNum : Integer ;
              Polarity : Boolean ;
              Time : Single
              ) ;

   procedure DisplayStimulusProtocol ;
   function ProtocolDuration : single ;
   procedure SetTimeScale ;

  end;

var
  WavGenFrm: TWavGenFrm;

implementation

uses Mdiform, StimModule, Rec, SESLabIO ;

{$R *.DFM}



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


var
   CurrentTool : TCurrentTool ;
   CurrentWaveformNum : Integer ;
   ImageIndex : Array[0..20] of Integer ;
   TableChanged : boolean ;


procedure TWavGenFrm.FormShow(Sender: TObject);
{ ----------------------------------
  Initial setup when form is opened
  ---------------------------------}
var
   i : Integer ;
begin

     { Disable Waveform Generator menu item }
     Main.WaveformGenerator.enabled := false ;

     { If recording in progress, abort it }
     if Main.FormExists('RecordFrm') then begin
        if (RecordFrm.State <> Idle) then RecordFrm.StopSweep ;
        end ;

     { Determine how many digital output channels are available (4/8) }
     if Main.SESLabIO.DigNumOutputs <= 4 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 ;

     Stimulator.Prog.RecordDuration := 0.5 ;
     Stimulator.Prog.RecordInterval := 1. ;
     Stimulator.Prog.HoldingVoltage[0] := 0. ;
     Stimulator.Prog.HoldingVoltage[1] := 0. ;

     for i := 0 to High(Stimulator.Prog.Shape) do Stimulator.Prog.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 DAC 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 ;

     DigNone.Tag := Ord(wvDigNone) ;
     DigNone.hint := 'No digital waveform' ;
     ImageIndex[DigNone.Tag] := DigNone.ComponentIndex ;


     { Display general recording sweep parameter table}

     { Create "Link To" protocol list }
     Stimulator.CreateProgramList( cbNextProtocol ) ;

     //  Note the following two values are stored as part of
     //  the settings data stored in the INI file not the .VPR file

     // Patch/voltage clamp command voltage divide factor
     edVDivide0.Value := Settings.DAC[0].DivideFactor ;
     edVDivide1.Value := Settings.DAC[1].DivideFactor ;

     // Define shape-> plot channel mappings
     for i := DAC0Start to DAC0End do PlotChannel[i] := DAC0 ;
     for i := DAC1Start to DAC1End do PlotChannel[i] := DAC1 ;
     for i := DigStart to DigEnd do PlotChannel[i] :=  DigPlotStart + i - DigStart ;

     // Set time scaling
     SetTimeScale ;

     { Load the voltage protocol in current use (if any) }
     if (Settings.VProgramFileName <> '' )
        and FileExists(Settings.VProgramFileName) then
        LoadWaveform(Settings.VProgramFileName)
     else NewWaveform ;

     Timer1.enabled := True ;
     TableChanged := False ;

     ClientWidth := ProtocolGrp.Left + ProtocolGrp.Width + 5 ;
     ClientHeight := ProtocolGrp.Top + ProtocolGrp.Height + 5 ;



     end ;


procedure TWavGenFrm.LoadWaveform(
          FileName : string
          ) ;
{ ------------------------------
  Load an existing waveform file
  ------------------------------ }
begin

     // Clear all waveform elements in protocol
     Stimulator.ClearWaveformElements ;

     FileName := ChangeFileExt( FileName, '.vpr' ) ;
     Caption := 'Stimulus Protocol : ' + ExtractFileNameOnly(FileName) ;
     
     { Get voltage program from VPR file }
     Stimulator.LoadProgram( FileName ) ;

     { Set leak subtraction parameters }
     if Stimulator.Prog.NumLeaks = 0 then rbNoLeak.Checked := True ;
     edNumLeaks.Value := Stimulator.Prog.NumLeaks ;
     edLeakScale.Value := Stimulator.Prog.LeakScale ;

     UpdateIcons ;

     FillParameterTable(-1,False) ;

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

     Stimulator.Prog.Saved := True ;

     end ;


procedure TWavGenFrm.NewWaveform ;
{ --------------------------
  Create a new waveform file
  -------------------------- }
begin

     // Clear all waveform elements in protocol
     Stimulator.ClearWaveformElements ;

     { Update waveform icons }
     UpdateIcons ;

     Stimulator.Prog.NumRepeats := 1 ;
     Stimulator.Prog.NumSteps := 1 ;
     Stimulator.Prog.RecordDuration := 0.5 ;
     Stimulator.Prog.RecordInterval := 1. ;
     Stimulator.Prog.HoldingVoltage[0] := 0. ;
     Stimulator.Prog.HoldingVoltage[1] := 0. ;
     Stimulator.Prog.NumLeaks := 0 ;
     Stimulator.Prog.DigitalInUse := False ;
     Stimulator.FileName := Settings.VProtDirectory + 'Untitled.vpr' ;

     Stimulator.Prog.RecordIntervalChanged := False ;
     Stimulator.Prog.ExtNumPoints := -1 ;
     Stimulator.Prog.ExtNumPointsOld := -1 ;
     Stimulator.Prog.DACdt := 0.001 ;
     Stimulator.Prog.ExtDACdt := 0.001 ;
     if Stimulator.ExtVBuf <> Nil then FreeMem(Stimulator.ExtVBuf) ;
     Stimulator.ExtVBuf := Nil ;

     FillParameterTable(-1,False) ;
{     shSelected.BoundsRect := RecSweep.BoundsRect ;}
     shSelected.Height := 2 ;
     shSelected.Width := RecSweep.Width ;
     shSelected.Top := RecSweep.Top + RecSweep.Height ;

     pbDisplay.Invalidate ;

     Stimulator.Prog.Saved := False ;

     end;



procedure TWavGenFrm.SaveWaveform(
          FileName : string
          ) ;
{ -------------------------
  Save waveform to VPR file
  ------------------------- }
begin
     FileName := ChangeFileExt( FileName, '.vpr' ) ;
     Caption := 'Stimulus Protocol : ' + ExtractFileNameOnly(FileName) ;
     Stimulator.SaveProgram( FileName ) ;
     { Update Link To protocol list }
     Stimulator.CreateProgramList( cbNextProtocol ) ;
     cbNextProtocol.ItemIndex := cbNextProtocol.Items.IndexOf(
                                 ExtractFileNameOnly(Stimulator.Prog.NextProtocolFileName)) ;

     { If recording window is open, call NewFile method to update list
       of available stimulus protocols }
     if Main.FormExists('RecordFrm') then RecordFrm.NewFile ;

     end ;


procedure TWavGenFrm.UpdateIcons ;
{ -----------------------------------
  Update waveform shape display icons
  ----------------------------------- }
var
    i : Integer ;
begin

      Caption := 'Stimulus Protocol : ' + ExtractFileNameOnly(Stimulator.FileName) ;

      // DAC1 waveform element
      Prot0.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[0])]]).Picture ;
      Prot1.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[1])]]).Picture ;
      Prot2.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[2])]]).Picture ;
      Prot3.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[3])]]).Picture ;
      Prot4.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[4])]]).Picture ;
      Prot5.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[5])]]).Picture ;
      Prot6.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[6])]]).Picture ;
      Prot7.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[7])]]).Picture ;
      Prot8.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[8])]]).Picture ;
      Prot9.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[9])]]).Picture ;

      // Ensure digital waveform elements have correct empty icon
      for i := DigStart to DigEnd do if Stimulator.Prog.Shape[i] = wvNone then
          Stimulator.Prog.Shape[i] := wvDigNone ;

      // Digital waveform elements
      Dig0.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[10])]]).Picture ;
      Dig1.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[11])]]).Picture ;
      Dig2.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[12])]]).Picture ;
      Dig3.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[13])]]).Picture ;
      Dig4.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[14])]]).Picture ;
      Dig5.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[15])]]).Picture ;
      Dig6.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[16])]]).Picture ;
      Dig7.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[17])]]).Picture ;

      // DAC1 waveform element
      Prot18.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[18])]]).Picture ;
      Prot19.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[19])]]).Picture ;
      Prot20.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[20])]]).Picture ;
      Prot21.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[21])]]).Picture ;
      Prot22.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[22])]]).Picture ;
      Prot23.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[23])]]).Picture ;
      Prot24.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[24])]]).Picture ;
      Prot25.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[25])]]).Picture ;
      Prot26.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[26])]]).Picture ;
      Prot27.Picture := TIMage(Components[ImageIndex[Ord(Stimulator.Prog.Shape[27])]]).Picture ;

      { Update leak subtraction buttons }
      if Stimulator.Prog.NumLeaks <> 0 then begin
           rbPNLeak.checked := True ;
           ednumleaks.visible := True ;
           lbnumleaks.visible := True ;
           edleakscale.visible := True ;
           lbleakscale.visible := True ;
           edNumLeaks.Value := Stimulator.Prog.NumLeaks ;
           edLeakScale.Value := Stimulator.Prog.LeakScale ;
           end
      else begin
           rbNoLeak.checked := True ;
           ednumleaks.visible := False ;
           lbnumleaks.visible := False ;
           edleakscale.visible := False ;
           lbleakscale.visible := False ;
           end ;

      cbNextProtocol.ItemIndex := cbNextProtocol.Items.IndexOf(
                                 ExtractFileNameOnly(Stimulator.Prog.NextProtocolFileName)) ;

      edDACInterval.Text := format(' %.4g ms',[Stimulator.Prog.DACdt*1000.0]) ;

      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);
// ----------------------------------------------------
// Recording sweep settings icon clicked - update table
// ----------------------------------------------------
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 ;
        Stimulator.Prog.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) ;
     Stimulator.Prog.Saved := False ;
     pbDisplay.invalidate ;
     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) = wvDigNone) then begin
        TIMage(Sender).Picture := TImage(Source).Picture ;
        Stimulator.Prog.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) ;
     Stimulator.Prog.Saved := False ;
     pbDisplay.invalidate ;
     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 ;
     bLoadFromFile.enabled := false ;

     if Num >= 0 then begin
        case Stimulator.Prog.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 Stimulator.Prog.Delay[Num] := Stimulator.Prog.RecordDuration * 0.1
                           else Stimulator.Prog.Delay[Num] := 0. ;
                Stimulator.Prog.Amplitude[Num] := 0.01 ;
                Stimulator.Prog.Duration[Num] := Stimulator.Prog.RecordDuration * 0.8 ;
                Stimulator.Prog.NumSteps := Max( Stimulator.Prog.NumSteps,1 ) ;
                end ;
             { Rectangular step (fixed size) }
             Table.RowCount := 3 ;
             Table.ColCount := 2 ;
             TableGrp.caption := ' Rectangular pulse (fixed size) ' ;
             Table.cells[0,0] := ' Delay ' ;
             Table.cells[1,0] := format('%.2f %s',
                                 [Stimulator.Prog.Delay[Num]*Tscale,TUnits] ) ;
             Table.cells[0,1] := ' Amplitude ' ;
             Table.cells[1,1] := format( '%.2f mV', [Stimulator.Prog.Amplitude[Num]*VoltsTomV] ) ;
             Table.cells[0,2] := ' Duration (ms) ' ;
             Table.cells[1,2] := format('%.2f %s',
                                 [Stimulator.Prog.Duration[Num]*Tscale,TUnits] ) ;
             Stimulator.Prog.NumPulses[Num] := 1 ;
             end ;
        wvStep1 : begin
             { Family of steps varying in amplitude }
             if FirstTime then begin
                if Num = 0 then Stimulator.Prog.Delay[Num] := Stimulator.Prog.RecordDuration * 0.1
                           else Stimulator.Prog.Delay[Num] := 0. ;
                Stimulator.Prog.Amplitude[Num] := 0.01 ;
                Stimulator.Prog.Increment[Num] := 0.01 ;
                Stimulator.Prog.Duration[Num] := Stimulator.Prog.RecordDuration * 0.8 ;
                Stimulator.Prog.NumSteps := 10 ;
                Stimulator.Prog.NumPulses[Num] := 10 ;
                end ;

             TableGrp.caption := ' Family of rectangular pulses (varying in amplitude) ' ;
             Table.RowCount := 5 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := ' Delay ' ;
             Table.cells[1,0] := format('%.2f %s',
                                 [Stimulator.Prog.Delay[Num]*Tscale,TUnits] ) ;
             Table.cells[0,1] := ' Initial Amplitude ' ;
             Table.cells[1,1] := format( '%.2f mV', [Stimulator.Prog.Amplitude[Num]*VoltsTomV] ) ;
             Table.cells[0,2] := ' Increment by ' ;
             Table.cells[1,2] := format( '%.2f mV', [Stimulator.Prog.Increment[Num]*VoltsTomV] ) ;
             Table.cells[0,3] := ' Number of increments ' ;
             // Get value from NumSteps to maintain compatibility with pre V3.5.6 protocol files
             Stimulator.Prog.NumPulses[Num] := Stimulator.Prog.NumSteps ;
             Table.cells[1,3] := format( '%5d', [Stimulator.Prog.NumPulses[Num]] ) ;
             Table.cells[0,4] := ' Duration ' ;
             Table.cells[1,4] := format('%.2f %s',
                                 [Stimulator.Prog.Duration[Num]*Tscale,TUnits] ) ;
             end ;

        wvStep2 : begin
             { Family of steps varying in duration }
             if FirstTime then begin
                if Num = 0 then Stimulator.Prog.Delay[Num] := Stimulator.Prog.RecordDuration * 0.1
                           else Stimulator.Prog.Delay[Num] := 0. ;
                Stimulator.Prog.Amplitude[Num] := 0.01 ;
                Stimulator.Prog.Duration[Num] := Stimulator.Prog.RecordDuration * 0.8 ;
                Stimulator.Prog.Increment[Num] := Stimulator.Prog.Duration[Num] * 0.1  ;
                Stimulator.Prog.NumSteps := 10 ;
                end ;
             TableGrp.caption := ' Family of rectangular pulses (varying in duration) ' ;
             Table.RowCount := 5 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := ' Delay ' ;
             Table.cells[1,0] := format('%.2f %s',
                                 [Stimulator.Prog.Delay[Num]*Tscale,TUnits] ) ;
             Table.cells[0,1] := ' Amplitude ' ;
             Table.cells[1,1] := format( '%.2f mV',[Stimulator.Prog.Amplitude[Num]*VoltsTomV] ) ;
             Table.cells[0,2] := ' Initial Duration ' ;
             Table.cells[1,2] := format('%.2f %s',
                                 [Stimulator.Prog.Duration[Num]*Tscale,TUnits] ) ;
             Table.cells[0,3] := ' Increment by ' ;
             Table.cells[1,3] := format('%.2f %s',
                                 [Stimulator.Prog.Increment[Num]*Tscale,TUnits] ) ;
             Table.cells[0,4] := ' Number of increments ' ;
             // Get value from NumSteps to maintain compatibility with pre V3.5.6 protocol files
             Stimulator.Prog.NumPulses[Num] := Stimulator.Prog.NumSteps ;
             Table.cells[1,4] := format( '%5d', [Stimulator.Prog.NumPulses[Num]] ) ;
             end ;

          wvRamp : begin
             { Voltage ramp }
             if FirstTime then begin
                if Num = 0 then Stimulator.Prog.Delay[Num] := Stimulator.Prog.RecordDuration * 0.1
                           else Stimulator.Prog.Delay[Num] := 0. ;
                Stimulator.Prog.RampStart[Num] := -0.1 ;
                Stimulator.Prog.RampEnd[Num] := 0.1 ;
                Stimulator.Prog.Duration[Num] := Stimulator.Prog.RecordDuration * 0.8 ;
                Stimulator.Prog.NumSteps := Max( Stimulator.Prog.NumSteps,1 ) ;
                end ;
             TableGrp.caption := ' Voltage ramp ' ;
             Table.RowCount := 4;
             Table.ColCount := 2 ;
             Table.cells[0,0] := ' Delay ' ;
             Table.cells[1,0] := format('%.2f %s',[Stimulator.Prog.Delay[Num]*Tscale,
                                                   TUnits] ) ;
             Table.cells[0,1] := ' Initial Amplitude ' ;
             Table.cells[1,1] := format( '%.2f mV', [Stimulator.Prog.RampStart[Num]*VoltsTomV] ) ;
             Table.cells[0,2] := ' Final Amplitude ' ;
             Table.cells[1,2] := format( '%.2f mV', [Stimulator.Prog.RampEnd[Num]*VoltsTomV] ) ;
             Table.cells[0,3] := ' Duration ' ;
             Table.cells[1,3] := format('%.2f %s',
                                 [Stimulator.Prog.Duration[Num]*Tscale,TUnits] ) ;
             end ;

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

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

             TableGrp.caption := ' Series of pulses ' ;
             Table.RowCount := 5 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := ' Delay ' ;
             Table.cells[1,0] := format( '%.2f %s',
                                 [Stimulator.Prog.Delay[Num]*Tscale,TUnits] ) ;
             Table.cells[0,1] := ' Amplitude ' ;
             Table.cells[1,1] := format( '%.2f mV', [Stimulator.Prog.Amplitude[Num]*VoltsTomV] ) ;
             Table.cells[0,2] := ' Duration ' ;
             Table.cells[1,2] := format( '%.2f %s',
                                 [Stimulator.Prog.Duration[Num]*Tscale,TUnits] ) ;
             Table.cells[0,3] := ' Pulse interval ' ;
             Table.cells[1,3] := format( '%.2f %s',
                                 [Stimulator.Prog.PulseInterval[Num]*Tscale,TUnits] ) ;
             Table.cells[0,4] := ' Number of pulses ' ;
             Table.cells[1,4] := format( '%5d', [Stimulator.Prog.NumPulses[Num]] ) ;
             end ;

          wvWave : begin
             { Voltage waveform from file }
             TableGrp.caption := ' Waveform from file ' ;
             Table.RowCount := 3; //Min(Stimulator.Prog.ExtNumPoints,MaxWaveBufPoints)+2 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := ' Delay ' ;
             Table.cells[1,0] := format('%.5g %s',
                                 [Stimulator.Prog.Delay[Num]*Tscale,TUnits] ) ;

             Table.cells[0,1] := ' DAC update interval ' ;
             Table.cells[1,1] := format('%.5g %s',
                                 [Stimulator.Prog.ExtDACdt*Tscale,TUnits] ) ;

             Table.cells[0,2] := ' No. points ' ;
             Table.cells[1,2] := format('%d',
                                 [Stimulator.Prog.ExtNumPoints] ) ;


{             for i := 0 to Table.RowCount-3 do begin
                 Table.cells[0,i+2] := format( '%.5g %s',
                                       [i*Stimulator.Prog.ExtDACdt*Tscale,TUnits]) ;
                 Table.cells[1,i+2] := format( '%.5g mV',
                                              [Stimulator.Prog.ExtWaveData[i]*VoltsTomV]) ;
                 end ;}
             Stimulator.Prog.NumSteps := Max( Stimulator.Prog.NumSteps,1 ) ;
             bPasteFromClipboard.enabled := True ;
             bLoadFromFile.enabled := True ;
             end ;

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

             TableGrp.caption := ' Digital pulse ' ;
             Table.RowCount := 3 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := ' Delay ' ;
             Table.cells[1,0] := format( '%.2f %s',
                                 [Stimulator.Prog.Delay[Num]*Tscale,TUnits] ) ;
             Table.cells[0,1] := ' Duration ' ;
             Table.cells[1,1] := format( '%.2f %s',
                                 [Stimulator.Prog.Duration[Num]*Tscale,TUnits] ) ;
             Table.cells[0,2] := ' Invert Signal (Yes/No) ' ;
             if Stimulator.Prog.Invert[Num] then Table.Cells[1,2] := 'Yes'
                                      else Table.Cells[1,2] := 'No' ;

             end ;

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

             TableGrp.caption := ' Family of digital pulses (varying in duration) ' ;
             Table.RowCount := 5 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := ' Delay ' ;
             Table.cells[1,0] := format( '%.2f %s',
                                 [Stimulator.Prog.Delay[Num]*Tscale,TUnits] ) ;
             Table.cells[0,1] := ' Initial Duration ' ;
             Table.cells[1,1] := format( '%.2f %s',
                                 [Stimulator.Prog.Duration[Num]*Tscale,TUnits] ) ;
             Table.cells[0,2] := ' Increment by ' ;
             Table.cells[1,2] := format( '%.2f %s',
                                 [Stimulator.Prog.Increment[Num]*Tscale,TUnits] ) ;
             Table.cells[0,3] := ' Number of increments ' ;
             // Get value from NumSteps to maintain compatibility with pre V3.5.6 protocol files
             Stimulator.Prog.NumPulses[Num] := Stimulator.Prog.NumSteps ;
             Table.cells[1,3] := format( '%5d', [Stimulator.Prog.NumPulses[Num]] ) ;
             Table.cells[0,4] := ' Invert Signal (Yes/No) ' ;
             if Stimulator.Prog.Invert[Num] then Table.Cells[1,4] := 'Yes'
                                      else Table.Cells[1,4] := 'No'
             end ;

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

             TableGrp.caption := ' Series of digital pulses ' ;
             Table.RowCount := 5 ;
             Table.ColCount := 2 ;
             Table.cells[0,0] := ' Delay ' ;
             Table.cells[1,0] := format( '%.2f %s',
                                 [Stimulator.Prog.Delay[Num]*Tscale,TUnits] ) ;
             Table.cells[0,1] := ' Duration ' ;
             Table.cells[1,1] := format( '%.2f %s',
                                 [Stimulator.Prog.Duration[Num]*Tscale,TUnits] ) ;
             Table.cells[0,2] := ' Inter-pulse interval ' ;
             Table.cells[1,2] := format( '%.2f %s',
                                 [Stimulator.Prog.PulseInterval[Num]*Tscale,TUnits] ) ;
             Table.cells[0,3] := ' Number of pulses ' ;
             Table.cells[1,3] := format( '%5d', [Stimulator.Prog.NumPulses[Num]] ) ;
             Table.cells[0,4] := ' Invert Signal (Yes/No) ' ;
             if Stimulator.Prog.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 sweeps ' ;
             Table.cells[1,0] := format( '%.2f %s',
                                 [Stimulator.Prog.RecordInterval*Tscale,TUnits] ) ;
             Table.cells[0,1] := ' Duration ' ;
             Table.cells[1,1] := format( '%.2f %s',
                                 [Stimulator.Prog.RecordDuration*Tscale,TUnits] ) ;
             Table.cells[0,2] := ' No. of waveform repetitions ' ;
             Table.cells[1,2] := format( '%7d', [Stimulator.Prog.NumRepeats] ) ;
             Table.cells[0,3] := ' Holding Voltage (DAC0)' ;
             Table.cells[1,3] := format( '%.2f mV', [Stimulator.Prog.HoldingVoltage[0]*VoltsTomV] ) ;
             Table.cells[0,4] := ' Holding Voltage (DAC1)' ;
             Table.cells[1,4] := format( '%.2f mV', [Stimulator.Prog.HoldingVoltage[1]*VoltsTomV] ) ;
             Table.cells[0,5] := ' External stimulus trigger (Y/N)' ;
             if Stimulator.Prog.RecordInterval = StimulusExtTriggerFlag then begin
                Table.cells[1,5] := 'Y' ;
                Table.cells[1,0] := Table.cells[1,1] ;
                end
             else Table.cells[1,5] := 'N' ;
             end ;

        MaxWidth := 0 ;
        RowHeight := (4*Table.canvas.TextHeight(Table.cells[0,0])) div 3 ;
        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 + 10 ;


        end ;


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


procedure TWavGenFrm.TableKeyPress(Sender: TObject; var Key: Char);
// ----------------------------------------------
// Update waveform parameters when CR key pressed
// ----------------------------------------------
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
   iVal, Num, i : Integer ;
begin

     Num := CurrentWaveformNum ;
     if Num >= 0 then begin
         case Stimulator.Prog.Shape[Num] of
             wvStep0 : begin
                  { Rectangular step (fixed size) }
                  UpdateEntry(Table.Cells[1,0],Stimulator.Prog.Delay[Num],Tscale,True) ;
                  UpdateEntry(Table.Cells[1,1],Stimulator.Prog.Amplitude[Num],VoltsTomV,False) ;
                  UpdateEntry(Table.Cells[1,2],Stimulator.Prog.Duration[Num],Tscale,True) ;
                  Stimulator.Prog.NumPulses[Num] := 1 ;
                  end ;
             wvStep1 : begin
                  { Family of steps varying in amplitude }
                  UpdateEntry(Table.Cells[1,0],Stimulator.Prog.Delay[Num],Tscale,True);
                  UpdateEntry(Table.Cells[1,1],Stimulator.Prog.Amplitude[Num],VoltsTomV,False);
                  UpdateEntry(Table.Cells[1,2],Stimulator.Prog.Increment[Num],VoltsTomV,False) ;
                  SetNumSteps(ExtractInt(Table.Cells[1,3])) ;
                  UpdateEntry(Table.Cells[1,4],Stimulator.Prog.Duration[Num],Tscale,True) ;
                  end ;
             wvStep2 : begin
                  { Family of steps varying in duration }
                  UpdateEntry(Table.Cells[1,0],Stimulator.Prog.Delay[Num],Tscale,True);
                  UpdateEntry(Table.Cells[1,1],Stimulator.Prog.Amplitude[Num],VoltsTomV,False);
                  UpdateEntry(Table.Cells[1,2],Stimulator.Prog.Duration[Num],Tscale,True);
                  UpdateEntry(Table.Cells[1,3],Stimulator.Prog.Increment[Num],Tscale,False);
                  SetNumSteps(ExtractInt(Table.Cells[1,4])) ;
                  end ;
             wvRamp : begin
                  { Voltage ramp }
                  UpdateEntry(Table.Cells[1,0],Stimulator.Prog.Delay[Num],Tscale,True);
                  UpdateEntry(Table.Cells[1,1],Stimulator.Prog.RampStart[Num],VoltsTomV,False);
                  UpdateEntry(Table.Cells[1,2],Stimulator.Prog.RampEnd[Num],VoltsTomV,False);
                  UpdateEntry(Table.Cells[1,3],Stimulator.Prog.Duration[Num],Tscale,True) ;
                  Stimulator.Prog.NumPulses[Num] := 1 ;
                  end ;
             wvPTrain : begin
                  { Train of pulses }
                  UpdateEntry(Table.Cells[1,0],Stimulator.Prog.Delay[Num],Tscale,True);
                  UpdateEntry(Table.Cells[1,1],Stimulator.Prog.Amplitude[Num],VoltsTomV,False);
                  UpdateEntry(Table.Cells[1,2],Stimulator.Prog.Duration[Num],Tscale,True);
                  UpdateEntry(Table.Cells[1,3],Stimulator.Prog.PulseInterval[Num],Tscale,True);
                  Stimulator.Prog.NumPulses[Num] := Max( ExtractInt(Table.Cells[1,4]),1 ) ;
                  end ;
             wvWave : begin
                  UpdateEntry(Table.Cells[1,0],Stimulator.Prog.Delay[Num],Tscale,True);
                  UpdateEntry(Table.Cells[1,1],Stimulator.Prog.ExtDACdt,Tscale,True);
{                  for i := 0 to Table.RowCount-3 do begin
                    UpdateEntry(Table.Cells[1,i+2],Stimulator.Prog.ExtWaveData[i],VoltstomV,False) ;
                    Stimulator.ExtVBuf[i] := Stimulator.Prog.ExtWaveData[i] ;
                    end ;}
                  Stimulator.Prog.NumPulses[Num] := 1 ;
                  end ;
             wvDigStep0 : begin
                  { Single digital pulse }
                  UpdateEntry(Table.Cells[1,0],Stimulator.Prog.Delay[Num],Tscale,true);
                  UpdateEntry(Table.Cells[1,1],Stimulator.Prog.Duration[Num],Tscale,True);
                  if Pos('Y',UpperCase(Table.Cells[1,2])) > 0 then
                       Stimulator.Prog.Invert[Num] := True
                  else Stimulator.Prog.Invert[Num] := False ;
                  Stimulator.Prog.NumPulses[Num] := 1 ;
                  end ;
               wvDigStep1 : begin
                  { Variable duration digital pulse }
                  UpdateEntry(Table.Cells[1,0],Stimulator.Prog.Delay[Num],Tscale,true);
                  UpdateEntry(Table.Cells[1,1],Stimulator.Prog.Duration[Num],Tscale,True);
                  UpdateEntry(Table.Cells[1,2],Stimulator.Prog.Increment[Num],Tscale,False);
                  SetNumSteps(ExtractInt(Table.Cells[1,3])) ;
                  if Pos('Y',UpperCase(Table.Cells[1,4])) > 0 then
                       Stimulator.Prog.Invert[Num] := True
                  else Stimulator.Prog.Invert[Num] := False ;
                  end ;
             wvDigTrain : begin
                  { Digital pulse train }
                  UpdateEntry(Table.Cells[1,0],Stimulator.Prog.Delay[Num],Tscale,true);
                  UpdateEntry(Table.Cells[1,1],Stimulator.Prog.Duration[Num],Tscale,True);
                  UpdateEntry(Table.Cells[1,2],Stimulator.Prog.PulseInterval[Num],Tscale,True);
                  Stimulator.Prog.NumPulses[Num] := Max( ExtractInt(Table.Cells[1,3]),1 ) ;
                  if Pos('Y',UpperCase(Table.Cells[1,4])) > 0 then
                       Stimulator.Prog.Invert[Num] := True
                  else Stimulator.Prog.Invert[Num] := False ;
                  end ;
             end;

         // Note. Stimulator.Prog.NumPulses field indicates number
         // of pulse increment steps except for pulse trains where it represents number
         // of pulses in train within record

         Stimulator.Prog.NumSteps := 1 ;
         for i := 0 to High(Stimulator.Prog.Shape) do
             if (Stimulator.Prog.Shape[i] <> wvPTrain) and
                (Stimulator.Prog.Shape[i] <> wvDigTrain) and
                (Stimulator.Prog.Shape[i] <> wvNone) and
                (Stimulator.Prog.Shape[i] <> wvDigNone) then begin
                Stimulator.Prog.NumSteps :=  Max( Stimulator.Prog.NumPulses[i],
                                                  Stimulator.Prog.NumSteps ) ;
                end ;

         end
     else begin
         { Num=-1 ... Recording sweep parameters }
         UpdateEntry(Table.Cells[1,0],Stimulator.Prog.RecordInterval,Tscale,False);
         UpdateEntry(Table.Cells[1,1],Stimulator.Prog.RecordDuration,Tscale,True);
         iVal := ExtractInt(Table.Cells[1,2]) ;
         if iVal > 0 then Stimulator.Prog.NumRepeats := iVal ;
         UpdateEntry(Table.Cells[1,3],Stimulator.Prog.HoldingVoltage[0],VoltsTomV,False) ;
         UpdateEntry(Table.Cells[1,4],Stimulator.Prog.HoldingVoltage[1],VoltsTomV,False) ;
         if Pos('Y',UpperCase(Table.Cells[1,5])) > 0 then
            Stimulator.Prog.RecordInterval := StimulusExtTriggerFlag ;
         end ;

     { Mark waveform as needing to be saved }
     Stimulator.Prog.Saved := False ;
     TableChanged := False ;
     pbDisplay.invalidate ;
     end ;


procedure TWavGenFrm.SetNumSteps(
          NewValue : Integer
          ) ;
// ----------------------------
// Set no. of steps in protocol
// ----------------------------
var
    i : Integer ;
begin


     for i := 0 to High(Stimulator.Prog.Shape) do begin
         case Stimulator.Prog.Shape[i] of
            wvStep1,wvStep2,wvDigStep1 : begin
               Stimulator.Prog.NumPulses[i] := Max(NewValue,1) ;
               end ;
            end ;
         end ;
     end ;


procedure TWavGenFrm.bSaveClick(Sender: TObject);
{ -----------------------------
  Save voltage protocol to file
  -----------------------------}
begin

     SaveDialog.options := [ofOverwritePrompt,ofHideReadOnly,ofPathMustExist] ;
     SaveDialog.FileName := ExtractFileName( SaveDialog.FileName ) ;
     SaveDialog.InitialDir := Settings.VProtDirectory ;
     SaveDialog.Title := 'Save Stimulus Protocol' ;
     if FileExists(Stimulator.FileName) then SaveDialog.FileName := Stimulator.FileName
                                       else SaveDialog.FileName := '*.vpr' ;

     if SaveDialog.execute then SaveWaveform(SaveDialog.FileName) ;

     end ;


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

     { If the current waveform has been edited ... offer to save it to file }
     if not Stimulator.Prog.Saved then begin
        if MessageDlg( ' Waveform Changed! Save it to ' + Stimulator.FileName,
           mtConfirmation,[mbYes,mbNo], 0 ) = mrYes then begin
           SaveWaveform( Stimulator.FileName ) ;
           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
        LoadWaveform( OpenDialog.FileName ) ;
        end ;

     pbDisplay.invalidate ;

     end ;


procedure TWavGenFrm.rbNoLeakClick(Sender: TObject);
{ --------------------------
  Turn off leak subtraction
  -------------------------}
begin
     Stimulator.Prog.NumLeaks := 0 ;
     ednumleaks.visible := False ;
     lbnumleaks.visible := False ;
     edleakscale.visible := False ;
     lbleakscale.visible := False ;
     Stimulator.Prog.Saved := False ;
     pbDisplay.Invalidate ;
     end;

procedure TWavGenFrm.rbPNLeakClick(Sender: TObject);
begin
     UpdateNumLeaks ;
     ednumleaks.visible := True ;
     lbnumleaks.visible := True ;
     edleakscale.visible := True ;
     lbleakscale.visible := True ;
     pbDisplay.Invalidate ;
     end;


procedure TWavGenFrm.UpdateNumLeaks ;
var
   OldValue : Integer ;
begin
     if rbPNLeak.checked then begin
        OldValue := Stimulator.Prog.NumLeaks ;
        Stimulator.Prog.NumLeaks := Round(edNumLeaks.Value) ;
        Stimulator.Prog.NumLeaks := IntLimitTo( Stimulator.Prog.NumLeaks,0,1000) ;
        if Stimulator.Prog.NumLeaks <> OldValue then Stimulator.Prog.Saved := False ;
        OldValue := Stimulator.Prog.LeakScale ;
        Stimulator.Prog.LeakScale := Round(edLeakScale.Value) ;
        Stimulator.Prog.LeakScale := IntLimitTo( Stimulator.Prog.LeakScale, -32, 32 ) ;
        if Stimulator.Prog.LeakScale = 0 then Stimulator.Prog.LeakScale := 1 ;
        if Stimulator.Prog.LeakScale <> OldValue then Stimulator.Prog.Saved := False ;
        end
     else begin
        Stimulator.Prog.NumLeaks := 0 ;
        rbNoLeak.checked := True ;
        end ;
     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
  ---------------------------------------------------------------}
var
   T,TOld,TScale : single ;
   nValues,i,iDataColumn : Integer ;
   DataList : TStringList ;
   Values : Array[0..2] of single ;
   Buf0 : PChar ;
   NumChars : Integer ;
   BufSize  : Integer ;
   DACUpdateIntervalSet : Boolean ;
begin

    // Exit if no text in clipboard
    if not Clipboard.HasFormat(CF_TEXT) then Exit ;

     try
        { Allocate buffers }

        DataList := TStringList.create ;

        { Get ASCII waveform text list from from clipboard }
        Buf0 := Nil ;
        BufSize := 50000 ;
        repeat
           BufSize := BufSize*2 ;
           if Buf0 <> Nil then StrDispose( Buf0 ) ;
           Buf0 := StrAlloc( BufSize ) ;
           NumChars := ClipBoard.GetTextBuf( Buf0, BufSize ) ;
           until NumChars < BufSize ;

        { Copy list into string list }
        DataList.SetText( Buf0 ) ;

        { Convert to floating point and store in waveform record}

        Stimulator.Prog.ExtNumPoints := 0 ;
        TOld := 0.0 ;
        DACUpdateIntervalSet := False ;
        if rbTmsecs.Checked then TScale := 0.001
                            else TScale := 1.0 ;

        // Allocate buffer space
        if Stimulator.ExtVBuf <> Nil then FreeMem(Stimulator.ExtVBuf) ;
        GetMem( Stimulator.ExtVBuf, DataList.Count*4 ) ;

        for i := 0 to DataList.Count-1 do begin

            { Extract number from row }
            nValues := ExtractListOfFloats( DataList[i], Values, False ) ;

            // Determine if data is one or two columns
            // (2 columns = time,V, 1 column = V)
            iDataColumn := Min(nValues-1,1) ;
            if iDataColumn = 0 then DACUpdateIntervalSet := True ;

            // Calculate update interval
            if not DACUpdateIntervalSet then begin
               T := Values[0] ;
               if Stimulator.Prog.ExtNumPoints > 0 then begin
                  Stimulator.Prog.ExtDACdt := (T - TOld)*TScale ;
                  DACUpdateIntervalSet := True ;
                  end ;
               TOld := T ;
               end ;

            { Read voltage value }
            Stimulator.ExtVBuf[Stimulator.Prog.ExtNumPoints] := Values[iDataColumn]*mVtoVolts ; ;
            if Stimulator.Prog.ExtNumPoints < High(Stimulator.Prog.ExtWaveData) then
               Stimulator.Prog.ExtWaveData[Stimulator.Prog.ExtNumPoints] :=
                      Stimulator.ExtVBuf[Stimulator.Prog.ExtNumPoints] ;

            // Update count
            Inc(Stimulator.Prog.ExtNumPoints) ;

            end ;

     finally
        StrDispose( Buf0 ) ;
        DataList.Free ;
        end ;

     { Update table with waveform data }
     FillParameterTable( CurrentWaveformNum ,True) ;
     pbDisplay.invalidate ;
     UpdateWaveform ;
     TableChanged := True ;

     end ;


procedure TWavGenFrm.FormClose(Sender: TObject; var Action: TCloseAction);
{ -------------------------------------
  Close form and update global settings
  -------------------------------------}
var
    i : Integer ;
begin

     // Command voltage pulse divide factor
     Settings.DAC[0].DivideFactor := edVDivide0.Value ;
     Settings.DAC[1].DivideFactor := edVDivide1.Value ;
     UpdateNumLeaks ;

     { If the current waveform has been edited ... offer to save it to file }
     if not Stimulator.Prog.Saved then begin

        UpdateWaveform ;

        if MessageDlg( ' Stimulus Changed! Save it to ' + Stimulator.FileName,
           mtConfirmation,[mbYes,mbNo], 0 ) = mrYes then begin
           SaveWaveform(Stimulator.FileName) ;
           end ;
        end ;
     Settings.VProgramFileName := Stimulator.FileName ;

     { If recording window is open, call NewFile method to update list
       of available stimulus protocols }
     for i := 0 to Main.MDIChildCount-1 do begin
         if ANSIContainsText(Main.MDIChildren[i].Name,'RecordFrm') then
            RecordFrm.UpdateStimulusProtocolList ;
         end ;

     { Enable Waveform Generator menu item }
     Main.WaveformGenerator.enabled := True ;

     Action := caFree ;
     end;


procedure TWavGenFrm.FormActivate(Sender: TObject);
// ---------------------------------------
// Update display when form becomes active
// ---------------------------------------
begin
     pbDisplay.invalidate ;
     end;


procedure TWavGenFrm.FormResize(Sender: TObject);
// ------------------------------------
// Adjust controls on form for new size
// ------------------------------------
begin

     ProtocolGrp.Width := ClientWidth - ProtocolGrp.Left - 5 ;
     ProtocolGrp.Top := ClientHeight - ProtocolGrp.Height - 5 ;

     DisplayGrp.Width := ProtocolGrp.Width ;
     DisplayGrp.Height := ProtocolGrp.Top - DisplayGrp.Top - 10 ;
     pbDisplay.Width := DisplayGrp.ClientWidth - pbDisplay.Left - 5 ;
     pbDisplay.Height := DisplayGrp.ClientHeight - pbDisplay.Top - 5 ;

     DACGrp.Top := ClientHeight - DacGrp.Height - 5 ;

     pbDisplay.Invalidate ;
     FillParameterTable(-1,False) ;
     end;


procedure TWavGenFrm.TableExit(Sender: TObject);
// -----------------------------------------------
// Focus has moved away from parameter entry table
// -----------------------------------------------
begin
     if TableChanged then UpdateWaveform ;
     end;


procedure TWavGenFrm.edNumLeaksKeyPress(Sender: TObject; var Key: Char);
// -----------------------------------------------
// No. Leak subtraction records has been changed
// -----------------------------------------------
begin
     if key = chr(13) then UpdateNumLeaks ;
     end;


procedure TWavGenFrm.edLeakScaleKeyPress(Sender: TObject; var Key: Char);
// -----------------------------------------------
// Leak subtraction scale factor has been changed
// -----------------------------------------------
begin
     if key = chr(13) then begin
        UpdateNumLeaks ;
        pbDisplay.Invalidate ;
        end ;
     end;


procedure TWavGenFrm.cbNextProtocolChange(Sender: TObject);
// ---------------------------
// Update next protocol field
// ---------------------------
begin
    if cbNextProtocol.text = '' then begin
       Stimulator.Prog.NextProtocolFileName := ' ' ;
       end
    else begin
       Stimulator.Prog.NextProtocolFileName := Settings.VProtDirectory +
                                         cbNextProtocol.text + '.vpr' ;
       end ;
    Stimulator.Prog.Saved := False ;
    end;


procedure TWavGenFrm.pbDisplayPaint(Sender: TObject);
{ -----------------------------
  Update paintbox areas on form
  -----------------------------}
begin

     DisplayStimulusProtocol ;

     // Update parameter table
     if CurrentWaveformNum = -1 then FillParameterTable( CurrentWaveformNum ,False) ;

     end ;


procedure TWavGenFrm.DisplayStimulusProtocol ;
// -------------------------
// Display stimulus protocol
// -------------------------
var
    Top :Integer ;
    Left :Integer ;
    i,j :Integer ;
    Shape :Integer ;
    DACChannelsHeight : Integer ;
    DACChannelsHalfHeight : Integer ;
    DigChannelsHeight :Integer ;
    YZero :Integer ;

    DisplayDuration : Single ;
    VoltsMax : Array[0..NumDACChannels-1] of Single ;
    VoltsMin : Array[0..NumDACChannels-1] of Single ;
    VoltsMaxLabel : Array[0..NumDACChannels-1] of String ;
    VoltsMinLabel : Array[0..NumDACChannels-1] of String ;
    DACChannelName : String ;
    DigChannelName : String ;
    Step : Integer ;
    TStartLabel : String ;      // Start of protocol time label
    TEndLabel : String ;        // End of protocol time label
    TLabelsTop : Integer ;      // Location of top of time labels
    RecordLabel : String ;      // Recording bar label
    PlotWidth : Integer ;       // Width of plotting area within display (pixels)
    Prog : TWaveform ;
    Loop : Integer ;
    NumLoops : Integer ;
    Amplitude : Single ;
    RampStart : Single ;
    RampEnd : Single ;

   NumPoints : Integer ;
   jNearest : Integer ;
   TDiff, TDiffMin : Single ;  // Time difference for wvWave

begin

     // Make internal copy of stimulator protocol
     Prog := Stimulator.Prog ;

     pbDisplay.canvas.Font.Size := 8 ;

     { Erase various parts of voltage protocol display }
     pbDisplay.canvas.brush.color := clWhite ;
     pbDisplay.canvas.FillRect( Rect( 0,0,pbDisplay.Width-1,pbDisplay.Height-1) ) ;

     Top := 2 ;
     DisplayDuration := ProtocolDuration ;

     // Set DAC display labels
     for i := 0 to 1 do begin
           VoltsMax[i] := Main.SESLabIO.DACVoltageRange / Settings.DAC[i].DivideFactor ;
           if VoltsMax[i] < 1.0 then VoltsMaxLabel[i] := format( '%.0f mV ',[VoltsMax[i]*1000.0])
                                else VoltsMaxLabel[i] := format( '%.0f V ',[VoltsMax[i]]) ;
           VoltsMin[i] := -VoltsMax[i] ;
           if Abs(VoltsMin[i]) < 1.0 then VoltsMinLabel[i] := format( '%.0f mV ',[VoltsMin[i]*1000.0])
                                     else VoltsMinLabel[i] := format( '%.0f V ',[VoltsMin[i]]) ;
           end ;

    Left := 5 + MaxInt( [ pbDisplay.Canvas.TextWidth('DAC0 '),
                          pbDisplay.Canvas.TextWidth(VoltsMaxLabel[0]),
                          pbDisplay.Canvas.TextWidth(VoltsMinLabel[0]),
                          pbDisplay.Canvas.TextWidth(VoltsMaxLabel[1]),
                          pbDisplay.Canvas.TextWidth(VoltsMinLabel[1])
                          ]) ;
    PlotWidth := pbDisplay.Width - Left - 2 ;

    // Display protocol duration labels
    TStartLabel := '0.0' ;
    TLabelsTop := pbDisplay.Height
                - pbDisplay.Canvas.TextHeight(TStartLabel) - 2 ;
    pbDisplay.Canvas.TextOut( Left, TLabelsTop, TStartLabel ) ;
    if DisplayDuration > 0.9 then TEndLabel := format( '%.5g s',[ DisplayDuration] )
                             else TEndLabel := format( '%.5g ms',[1000.0*DisplayDuration] ) ;
    pbDisplay.Canvas.TextOut( pbDisplay.Width
                              - pbDisplay.Canvas.TextWidth(TEndLabel) - 1,
                              TLabelsTop,
                              TEndLabel ) ;

    pbDisplay.canvas.brush.color := clWhite ;

    // Set sizes of DAC and digital plotting channels
     DACChannelsHeight := Max( ((3*(TLabelsTop - Top - 2)) div 8) - 2, 2) ;
     DigChannelsHeight := Max( (TLabelsTop - Top - (NumDACChannels*DACChannelsHeight) - 3 )
                                   div NumDigChannels, 2) ;
     DACChannelsHalfHeight := DACChannelsHeight div 2 ;

     // Set DAC channels display area
     YZero := DACChannelsHalfHeight + Top ;
     for i := 0 to NumDACChannels-1 do begin

         DisplayChannel[DAC0+i].YZero := YZero ;

         DisplayChannel[DAC0+i].YScale := ((DACChannelsHeight-1)*Settings.DAC[i].DivideFactor) /
                                          (2.0*Main.SESLabIO.DACVoltageRange) ;
         DisplayChannel[DAC0+i].XScale := (pbDisplay.Width - Left - 2) / DisplayDuration ;

         DisplayChannel[DAC0+i].XStart := Left ;
         DisplayChannel[DAC0+i].YStart := DisplayChannel[DAC0+i].YZero -
                                          (Prog.HoldingVoltage[i]*DisplayChannel[DAC0+i].YScale) ;

         // Display voltage range labels
         DACChannelName := format( 'DAC%d', [i] ) ;
         pbDisplay.Canvas.TextOut( Left - pbDisplay.Canvas.TextWidth(DACChannelName),
                                   Round(DisplayChannel[DAC0+i].YZero)
                                   - (pbDisplay.Canvas.TextHeight(DACChannelName) div 2),
                                   DACChannelName ) ;

         pbDisplay.Canvas.TextOut( Left - pbDisplay.Canvas.TextWidth(VoltsMaxLabel[i]),
                                   Round(DisplayChannel[DAC0+i].YZero) - DACChannelsHalfHeight,
                                   VoltsMaxLabel[i] ) ;
         pbDisplay.Canvas.TextOut( Left - pbDisplay.Canvas.TextWidth(VoltsMinLabel[i]),
                                   Round(DisplayChannel[DAC0+i].YZero)
                                   + DACChannelsHalfHeight
                                   - pbDisplay.Canvas.TextHeight(VoltsMinLabel[i]),
                                   VoltsMinLabel[i] ) ;

         // Display zero level as dotted line
         pbDisplay.Canvas.Pen.Style := psDot ;
         pbDisplay.Canvas.MoveTo( Left, YZero ) ;
         pbDisplay.Canvas.LineTo( Left + PlotWidth -1, YZero ) ;
         pbDisplay.Canvas.Pen.Style := psSolid ;

         YZero := YZero + DACChannelsHeight + 2 ;

         end ;

     // Initialise digital channels display
     YZero := Top + 2*DACChannelsHeight + 4 + DigChannelsHeight ;
     for i := DigPlotStart to DigPlotEnd do begin
         DisplayChannel[i].XStart := Left ;
         DisplayChannel[i].XScale := (pbDisplay.Width - Left - 2) / DisplayDuration ;
         DisplayChannel[i].YZero := YZero ;
         DisplayChannel[i].YStart := YZero ;
         DisplayChannel[i].YScale := DigChannelsHeight-1 ;
         YZero := YZero + DigChannelsHeight ;
         end ;

     // Digital channel names
     DigChannelName := 'Dig. 0-7' ;
     pbDisplay.Canvas.TextOut( Left - pbDisplay.Canvas.TextWidth(DigChannelName),
                                   Round(DisplayChannel[DigPlotStart].YZero)
                                   - DigChannelsHeight,
                                   DigChannelName ) ;

     // Two display loops if P/N leak subtraction enabled
     if rbPNLeak.Checked then NumLoops := 2
                         else NumLoops := 1 ;
     Prog.DigitalInUse := False ;
     for Loop := 1 to NumLoops do begin

         // Plot shapes within stimulus protocol
         for Step := 0 to Prog.NumSteps-1 do begin

             // Reset display channels
             for i := 0 to NumDisplayChannels-1 do begin
                 DisplayChannel[i].X := DisplayChannel[i].XStart ;
                 DisplayChannel[i].Y := DisplayChannel[i].YStart ;
                 pbDisplay.Canvas.MoveTo( Round(DisplayChannel[i].X),
                                          Round(DisplayChannel[i].Y )) ;
                 end ;

             for Shape := 0 to High(Prog.Shape) do begin

                 case Prog.Shape[Shape] of
                     wvStep0 : Begin
                         Amplitude := Prog.Amplitude[Shape] ;
                         if Loop = 2 then Amplitude := Amplitude / edLeakScale.Value ;
                         PlotVStep( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]],
                                    Prog.Delay[Shape] ) ;
                         PlotVStep(PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]] + Amplitude,
                                    Prog.Duration[Shape] ) ;
                         end ;
                      wvStep1 : Begin
                         Amplitude := Prog.Amplitude[Shape] + Prog.Increment[Shape]*Step ;
                         if Loop = 2 then begin
                            if edLeakScale.Value = 0.0 then edLeakScale.Value := 1.0 ;
                            Amplitude := Amplitude / edLeakScale.Value ;
                            end ;
                         PlotVStep( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]],
                                    Prog.Delay[Shape] ) ;
                         PlotVStep( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]] + Amplitude,
                                    Prog.Duration[Shape] ) ;
                         end ;

                       wvStep2 : Begin
                         { Family of steps incrementing in duration }
                         Amplitude := Prog.Amplitude[Shape] ;
                         if Loop = 2 then Amplitude := Amplitude / edLeakScale.Value ;
                         PlotVStep( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]],
                                    Prog.Delay[Shape] ) ;
                         PlotVStep( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]] + Amplitude,
                                    Prog.Duration[Shape]
                                    + Prog.Increment[Shape]*Step ) ;
                         end ;

                       wvRamp : Begin
                         { Voltage ramp }
                         RampStart := Prog.RampStart[Shape] ;
                         RampEnd := Prog.HoldingVoltage[PlotChannel[Shape]] + Prog.RampEnd[Shape] ;
                         if Loop = 2 then begin
                            RampStart := RampStart / edLeakScale.Value ;
                            RampEnd := RampEnd / edLeakScale.Value ;
                            end ;
                         PlotVStep( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]],
                                    Prog.Delay[Shape] ) ;
                         PlotVStep( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]] + RampStart,
                                    0.0 ) ;
                         PlotVLine( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]] + RampEnd,
                                    Prog.Duration[Shape] ) ;
                         PlotVStep( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]],
                                    0.0 ) ;
                         end ;

                       wvPTrain : Begin
                         { Pulse train }
                         Amplitude := Prog.Amplitude[Shape] ;
                         if Loop = 2 then Amplitude := Amplitude / edLeakScale.Value ;

                         PlotVStep( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]],
                                    Prog.Delay[Shape] ) ;
                         for i := 1 to Prog.NumPulses[Shape] do begin
                             PlotVStep( PlotChannel[Shape],
                                        Prog.HoldingVoltage[PlotChannel[Shape]] + Amplitude,
                                        Prog.Duration[Shape] ) ;
                             PlotVStep( PlotChannel[Shape],
                                        Prog.HoldingVoltage[PlotChannel[Shape]],
                                        Prog.PulseInterval[Shape] - Prog.Duration[Shape] ) ;
                             end ;
                         end ;

                       wvWave : Begin
                         { Waveform defined from an external file }
                         PlotVStep( PlotChannel[Shape],
                                    Prog.HoldingVoltage[PlotChannel[Shape]],
                                    Prog.Delay[Shape] ) ;

                         for i := 0 to Prog.ExtNumPoints-1 do
                             PlotVLine( PlotChannel[Shape],
                                        Prog.HoldingVoltage[PlotChannel[Shape]]
                                        + Stimulator.ExtVBuf[i],
                                        Prog.ExtDACdt ) ;
                         end ;

                       wvDigStep0 : Begin
                         { Digital pulse }
                         Prog.DigitalInUse := True ;
                         PlotDigStep( PlotChannel[Shape],
                                      Prog.Invert[Shape],
                                      Prog.Delay[Shape] ) ;
                         PlotDigStep( PlotChannel[Shape],
                                      not Prog.Invert[Shape],
                                      Prog.Duration[Shape] ) ;
                         end ;

                       wvDigStep1 : Begin
                         { Digital pulse (incrementing width)}
                         Prog.DigitalInUse := True ;
                         PlotDigStep( PlotChannel[Shape],
                                      Prog.Invert[Shape],
                                      Prog.Delay[Shape] ) ;
                         PlotDigStep( PlotChannel[Shape],
                                      not Prog.Invert[Shape],
                                      Prog.Duration[Shape] +
                                      Prog.Increment[Shape]*Step ) ;
                         end ;

                       wvDigTrain : begin
                         { Pulse train }
                         Prog.DigitalInUse := True ;
                         PlotDigStep( PlotChannel[Shape],
                                      Prog.Invert[Shape],
                                      Prog.Delay[Shape] ) ;
                         for i := 1 to Prog.NumPulses[Shape] do begin
                             { Pulse duration }
                             PlotDigStep( PlotChannel[Shape],
                                          not Prog.Invert[Shape],
                                          Prog.Duration[Shape] ) ;
                             PlotDigStep( PlotChannel[Shape],
                                          Prog.Invert[Shape],
                                          Prog.PulseInterval[Shape]
                                          - Prog.Duration[Shape] ) ;
                             end ;
                       end ;
                  end ;
                 end ;

             // Extend line to end stimulus protocol (DACs)
             for i := 0 to DACPlotEnd-DACPlotStart do begin
                 PlotVStep( i,
                            Prog.HoldingVoltage[i],
                            DisplayDuration ) ;
                 end ;

             // Extend line to end stimulus protocol (Digital O/Ps)
             for i := DigStart to DigEnd do begin
                 PlotDigStep( DigPlotStart + i - DigStart,
                              Prog.Invert[i],
                              DisplayDuration ) ;
                 end ;

             end ;


         end ;

     // Calculate DAC update interval
     Stimulator.SetADCDACUpdateIntervals( DisplayDuration ) ;
     edDACInterval.Text := format(' %.4g ms',[Stimulator.Prog.DACdt*1000.0]) ;

     end ;


procedure TWavGenFrm.PlotVStep(
          ChanNum : Integer ;
          Amplitude : Single ;
          Time : Single
          ) ;
// ----------------------------------
// Plot a DAC voltage step on display
// ----------------------------------
begin
      //if Time = 0.0 then Exit ;

      pbDisplay.Canvas.MoveTo( Round(DisplayChannel[ChanNum].X),
                               Round(DisplayChannel[ChanNum].Y) ) ;
      DisplayChannel[ChanNum].Y := DisplayChannel[ChanNum].YZero
                                   - DisplayChannel[ChanNum].YScale*Amplitude ;
      pbDisplay.Canvas.LineTo( Round(DisplayChannel[ChanNum].X),
                               Round(DisplayChannel[ChanNum].Y) ) ;
      DisplayChannel[ChanNum].X := DisplayChannel[ChanNum].X
                                   + DisplayChannel[ChanNum].XScale*Time ;
      pbDisplay.Canvas.LineTo( Round(DisplayChannel[ChanNum].X),
                               Round(DisplayChannel[ChanNum].Y) ) ;

      end ;


procedure TWavGenFrm.PlotVLine(
          ChanNum : Integer ;
          Amplitude : Single ;
          Time : Single
          ) ;
// ----------------------------------
// Plot a DAC voltage line on display
// ----------------------------------
begin
      if Time = 0.0 then Exit ;

      pbDisplay.Canvas.MoveTo( Round(DisplayChannel[ChanNum].X),
                               Round(DisplayChannel[ChanNum].Y) ) ;
      DisplayChannel[ChanNum].Y := DisplayChannel[ChanNum].YZero
                                   - DisplayChannel[ChanNum].YScale*Amplitude ;
      DisplayChannel[ChanNum].X :=  DisplayChannel[ChanNum].X
                                    + DisplayChannel[ChanNum].XScale*Time ;
      pbDisplay.Canvas.LineTo( Round(DisplayChannel[ChanNum].X),
                               Round(DisplayChannel[ChanNum].Y) ) ;

      end ;


procedure TWavGenFrm.PlotDigStep(
          ChanNum : Integer ;
          Polarity : Boolean ;
          Time : Single
          ) ;
// ----------------------------------
// Plot a TTL digital step on display
// ----------------------------------
var
    DigLevel : Integer ;
begin

      if Time = 0.0 then Exit ;

      if Polarity then DigLevel := 1
                  else DigLevel := 0 ;
      pbDisplay.Canvas.MoveTo( Round(DisplayChannel[ChanNum].X),
                               Round(DisplayChannel[ChanNum].Y) ) ;
      DisplayChannel[ChanNum].Y := DisplayChannel[ChanNum].YZero
                                   - DisplayChannel[ChanNum].YScale*DigLevel ;
      pbDisplay.Canvas.LineTo( Round(DisplayChannel[ChanNum].X),
                               Round(DisplayChannel[ChanNum].Y) ) ;
      DisplayChannel[ChanNum].X := DisplayChannel[ChanNum].X
                                   + DisplayChannel[ChanNum].XScale*Time ;
      pbDisplay.Canvas.LineTo( Round(DisplayChannel[ChanNum].X),
                               Round(DisplayChannel[ChanNum].Y) ) ;

      end ;


function TWavGenFrm.ProtocolDuration : Single ;
{ --------------------------------------------
  Calculate total duration of voltage protocol
  --------------------------------------------}

var
   i,NumExtra : Integer ;
   TSum : Array[0..MaxProtocolShapes-1] of Single ;
   TMax : Single ;
begin

     for i := 0 to MaxProtocolShapes-1 do TSum[i] := 0.0 ;

     { Add up time taken by all active waveform shapes }
     NumExtra := Stimulator.Prog.NumSteps - 1 ;

     for i := 0 to High(Stimulator.Prog.Shape) do begin
         case Stimulator.Prog.Shape[i] of
              { Analogue voltage waveforms. Occur one after the other
                   on DAC O/P channel 0) }
              wvStep0 :   begin
                  TSum[PlotChannel[i]] := TSum[PlotChannel[i]]
                                          + Stimulator.Prog.Duration[i] + Stimulator.Prog.Delay[i] ;
                  end ;
              wvStep1 : begin
                  TSum[PlotChannel[i]] := TSum[PlotChannel[i]]
                                          + Stimulator.Prog.Duration[i] + Stimulator.Prog.Delay[i] ;
                  end ;
              wvStep2 : begin
                  TSum[PlotChannel[i]] := TSum[PlotChannel[i]]
                                          + Stimulator.Prog.Duration[i] + Stimulator.Prog.Delay[i]
                                          + MaxFlt([Stimulator.Prog.Increment[i]*NumExtra,0. ]) ;
                  end ;
              wvRamp : begin
                  TSum[PlotChannel[i]] := TSum[PlotChannel[i]]
                                          + Stimulator.Prog.Duration[i] + Stimulator.Prog.Delay[i] ;
                  end ;
              wvPTrain : begin
                 TSum[PlotChannel[i]] := TSum[PlotChannel[i]]
                                         + (Stimulator.Prog.Delay[i] +
                                         + Stimulator.Prog.NumPulses[i]*Stimulator.Prog.PulseInterval[i]) ;
                 end ;
              wvWave : begin
                 TSum[PlotChannel[i]] := TSum[PlotChannel[i]]
                                         + (Stimulator.Prog.ExtNumPoints)*Stimulator.Prog.DACdt ;
                 end ;

              { Digital waveforms. Each waveform output simultaneously (on bits 0..7) }
              wvDigStep0 : begin
                 TSum[PlotChannel[i]] := TSum[PlotChannel[i]] +
                                         Stimulator.Prog.Delay[i] + Stimulator.Prog.Duration[i] ;
                 end ;
              wvDigStep1 : begin
                 TSum[PlotChannel[i]] := TSum[PlotChannel[i]] +
                                         Stimulator.Prog.Delay[i] + Stimulator.Prog.Duration[i] +
                                         (Stimulator.Prog.Increment[i]*NumExtra) ;
                end ;
              wvDigTrain : begin
                 TSum[PlotChannel[i]] := TSum[PlotChannel[i]] +
                                         Stimulator.Prog.Delay[i] +
                                         (Stimulator.Prog.NumPulses[i]*Stimulator.Prog.PulseInterval[i]) ;
                end ;
              end ;
         end ;

     TMax := Stimulator.Prog.RecordDuration ;
     for i := 0 to NumDisplayChannels-1 do if TMax < TSum[i] then TMax := TSum[i] ;

     // Add 10% to end of protocol
     Result := 1.1*TMax ;

     Result := TMax ;
     end ;



procedure TWavGenFrm.Timer1Timer(Sender: TObject);
{ -----------------------------------------------------
  Ensure table row width and column heights are correct
  -----------------------------------------------------}
var
   MaxWidth,RowHeight,i : Integer ;
begin
     MaxWidth := 0 ;
        RowHeight := (4*Table.canvas.TextHeight(Table.cells[0,0])) div 3  ;
        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 + 10 ;

     Timer1.enabled := false ;
     end;


procedure TWavGenFrm.edVDivide0KeyPress(Sender: TObject; var Key: Char);
{ ------------------------------------
  Update command voltage divide factor
  ------------------------------------}
begin
     if key = #13 then begin
        Settings.DAC[0].DivideFactor := edVDivide0.Value ;
        pbDisplay.Invalidate ;
        end ;
     end;

procedure TWavGenFrm.edVDivide1KeyPress(Sender: TObject; var Key: Char);
{ ------------------------------------
  Update command voltage divide factor
  ------------------------------------}
begin
     if key = #13 then begin
        Settings.DAC[1].DivideFactor := edVDivide1.Value ;
        pbDisplay.Invalidate ;
        end ;
     end;

procedure TWavGenFrm.bSetStimFolderClick(Sender: TObject);
// -----------------------------
//  Set voltage protocol file folder
// -----------------------------
begin
     Main.OpenDialog.FileName := '_' ;
     Main.OpenDialog.InitialDir := Settings.VProtDirectory ;
     Main.OpenDialog.Title := 'Select protocol file folder' ;
     if Main.OpenDialog.execute then begin
        Settings.VProtDirectory := ExtractFilePath( Main.OpenDialog.FileName ) ;
        end ;

     end ;

procedure TWavGenFrm.bLoadFromFileClick(Sender: TObject);
// -----------------------
// Load waveform from file
// -----------------------
var
    F: TextFile;
    T,TOld,TScale : single ;
    nValues,iDataColumn,NumLines : Integer ;
    Values : Array[0..10] of single ;
    DACUpdateIntervalSet : Boolean ;
    s : String ;
begin

     OpenDialog.options := [ofOverwritePrompt,ofHideReadOnly,ofPathMustExist] ;
     OpenDialog.FileName := ExtractFileName( SaveDialog.FileName ) ;
     OpenDialog.InitialDir := Settings.VProtDirectory ;
     OpenDialog.Title := 'Load waveform data file' ;
     OpenDialog.Filter := 'Tab text files (*.txt,*.dat)|*.txt;*.dat|' ;
     OpenDialog.options := [ofPathMustExist] ;
     OpenDialog.DefaultExt := 'txt' ;

     if not OpenDialog.execute then Exit ;

     AssignFile(F, OpenDialog.FileName); { File selected in dialog }
     Reset(F);

     // Determine number of lines
     NumLines := 0 ;
     repeat
        ReadLn( F, s ) ;
        Inc(NumLines) ;
        until EOF(F) ;
     CloseFile(F) ;
     Reset(F) ;

     Stimulator.Prog.ExtNumPoints := 0 ;
     TOld := 0.0 ;
     DACUpdateIntervalSet := False ;
     if rbTmsecs.Checked then TScale := 0.001
                         else TScale := 1.0 ;

     // Allocate buffer space
     if Stimulator.ExtVBuf <> Nil then FreeMem(Stimulator.ExtVBuf) ;
     GetMem( Stimulator.ExtVBuf, Max(NumLines,1)*4 ) ;

     repeat
            // Read line
            ReadLn( F, s ) ;
            { Extract number from row }
            nValues := ExtractListOfFloats( s, Values, False ) ;

            // Determine in data is one or two columns
            // (2 columns = time,V, 1 column = V)
            iDataColumn := Min(nValues-1,1) ;
            if iDataColumn = 0 then DACUpdateIntervalSet := True ;

            // Calculate update interval
            if not DACUpdateIntervalSet then begin
               T := Values[0] ;
               if Stimulator.Prog.ExtNumPoints > 0 then begin
                  Stimulator.Prog.ExtDACdt := (T - TOld)*TScale ;
                  DACUpdateIntervalSet := True ;
                  end ;
               TOld := T ;
               end ;

            { Read voltage value }
            Stimulator.ExtVBuf[Stimulator.Prog.ExtNumPoints] := Values[iDataColumn]*mVtoVolts ; ;
            if Stimulator.Prog.ExtNumPoints <= High(Stimulator.Prog.ExtWaveData) then
               Stimulator.Prog.ExtWaveData[Stimulator.Prog.ExtNumPoints] :=
                      Stimulator.ExtVBuf[Stimulator.Prog.ExtNumPoints] ;

            // Update count
            Inc(Stimulator.Prog.ExtNumPoints) ;

            until ( EOF(F) or (Stimulator.Prog.ExtNumPoints >= NumLines)) ;


     { Update table with waveform data }
     FillParameterTable( CurrentWaveformNum ,True) ;
     pbDisplay.invalidate ;
     //UpdateWaveform ;
     TableChanged := True ;

     end;


procedure TWavGenFrm.rbTmsecsClick(Sender: TObject);
// ----------------------
// ms time units selected
// ----------------------
begin
     SetTimeScale ;
     FillParameterTable( CurrentWaveformNum, False ) ;
     end;

procedure TWavGenFrm.SetTimeScale ;
// ----------------------------------
// Set time display units and scaling
// ----------------------------------
begin
     if rbTmsecs.checked then begin
        TScale := 1000.0 ;
        TUnits := 'ms' ;
        end
     else begin
        TScale := 1.0 ;
        TUnits := 's' ;
        end ;



     end ;


procedure TWavGenFrm.rbTsecsClick(Sender: TObject);
// ----------------------
// ms time units selected
// ----------------------
begin
     SetTimeScale ;
     FillParameterTable( CurrentWaveformNum, False ) ;
     end;


Initialization

end.
