unit StimModule;
{ =============================================
  WinFluor -Analogue & Digital Waveform Generator
  27.05.05 Based upon WinWCP stimModule.pas
  =============================================
  05.09.05 .. MainFrm.ADCScanInterval now used
  20.07.06 .. Spurious pulse to holding potential at end of stimulus pulse fixed
              (for Chicago)
  11.10.06 .. Series of pulse trains protocol added.
  27.05.07 .. Modified for WinEDR PRBS noise waveform added
  29.08.07 .. FillDigBuf now has correct upper limit for buffer
  12.03.08 .. Extra 100 ms no longer added to stimulus repeat period
  04.02.09 ..
  }

interface


uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  stdctrls, maths, winprocs, math, global, seslabio ;

const
     DigShapes = 10 ; { First digital waveform shape }
     MaxWaveShapes = 28 ;
     MaxOldWaveShapes = 18 ;
     MaxProtocolShapes = 28 ;
     MaxDACPointsPerProtocol = 8000 ;
     MaxWaveBufPoints = 1024 ;

     // DAC0 shape #s
     V0Start = 0 ;
     V0End = 9 ;
     // Digital shape #s
     DigStart = 10 ;
     DigEnd = 17 ;
     // DAC1 shape #s
     V1Start = 18 ;
     V1End = 27 ;
     NumDigChannels = 8 ;
     NumDACChannels = 2 ;
     NumVChannels = 2 ;
     DAC0 = 0 ;
     DAC1 = 1 ;

     //WaveformCreationTime = 0.05 ;

type

// Stimulator waveform types
TWaveShape = ( wvNone, wvStep0, wvStep1, wvStep2, wvRamp, wvpTrain, wvWave,
               wvDigStep0,wvDigStep1, wvDigTrain, wvDigNone, wvNoise ) ;
// Waveform definition record
TWaveform = packed record
          Shape : Array[0..MaxWaveShapes-1] of TWaveShape ;
          Amplitude :     Array[0..MaxWaveShapes-1] of Single ;
          Duration :      Array[0..MaxWaveShapes-1] of Single ;
          Increment :     Array[0..MaxWaveShapes-1] of Single ;
          RampStart :     Array[0..MaxWaveShapes-1] of Single ;
          RampEnd :       Array[0..MaxWaveShapes-1] of Single ;
          PulseInterval : Array[0..MaxWaveShapes-1] of Single ;
          NumPulses : Array[0..MaxWaveShapes-1] of LongInt ;
          Period :        Array[0..MaxWaveShapes-1] of Single ;
          Delay :         Array[0..MaxWaveShapes-1] of Single ;
          Invert :        Array[0..MaxWaveShapes-1] of Boolean ;
          HoldingVoltage : Array[DAC0..DAC1] of Single ;
          VMin : Single ;
          VMax : Single ;
          RecordInterval : Single ;
          RecordDuration : Single ;
          NumSteps : LongInt ;
          ExtNumPoints : LongInt ;
          DACdt : Single ;
          FileName : string[255] ;
          ExtWaveData : Array[0..MaxWaveBufPoints-1] of single ;
          ExtNumPointsOld : SmallInt ;
          ExtDACdt : single ;
          StepCounter : LongInt ;
          NumDACPoints : LongInt ;
          NotInUse : LongInt ;
          DigitalInUse : Boolean ;
          DigitalPortValue : LongInt ;
          RecordingStart : LongInt ;
          InUse : Boolean ;
          Saved : Boolean ;
          RepeatedStimulus : Boolean ;
          NextProtocolFileName : string[255] ;
          end ;



  TStimulator = class(TDataModule)
    procedure DataModuleCreate(Sender: TObject);
  private
    { Private declarations }
    VChannel : Array[0..MaxProtocolShapes-1] of Integer ;
    DACPointer : Array[0..NumVChannels-1] of Integer ;
    DigChannel : Array[0..MaxProtocolShapes-1] of Integer ;
    DigPointer : Array[0..NumDigChannels-1] of Integer ;
    NumDACPoints : Integer ;



    procedure FillDACBuf(
          VChan : Integer ;
          Time : Single ;
          DACVolts : Single ;
          DACBuf : PSmallIntArray
          ) ;

    procedure FillDACBufNoise(
              VChan : Integer ;
              Time : Single ;
              DACVolts : Single ;
              DACBuf : PSmallIntArray
              ) ;

    procedure FillDACBufRamp(
          VChan : Integer ;
          Time : Single ;
          DACVoltsStart : Single ;
          DACVoltsEnd : Single ;
          DACBuf : PSmallIntArray
          ) ;

    procedure FillDACBufWave(
              VChan : Integer ;
              var Wave : Array of Single ;
              NumPoints : Integer ;
              DACBuf : PSmallIntArray
              ) ;

    procedure FillDigBuf(
              DigChannel : Integer ;
              Time : Single ;
              BitOn : Boolean ;
              DigBuf : PSmallIntArray    // Pointers to digital buffers
              ) ;

  public
    { Public declarations }
    FileName : String ;
    Prog : TWaveform ;
    ExtVBuf : PSingleArray ; // Pointer to extended user-defined waveform buffer
    
    procedure CreateWaveform(
              DACBuf : PSmallIntArray ;
              DigBuf : PSmallIntArray ;
              NumDACPointsIn : Integer
              )  ;

    procedure CreateProgramList(
              var cbList : TComboBox ) ;
    procedure LoadProgram(
              const Name : string ) ;
    procedure SaveProgram(
              const Name : string ) ;
    procedure ClearWaveformElements ;
    function ProtocolDuration : single ;

  end;

var
  Stimulator: TStimulator;

implementation

uses MDIform, shared, Wavgen ;

{$R *.DFM}


procedure TStimulator.CreateWaveform(
          DACBuf : PSmallIntArray ;
          DigBuf : PSmallIntArray ;
          NumDACPointsIn : Integer
          )  ;
{ -----------------------------------------------------
  Create command voltage waveforms defined by protocol
  ----------------------------------------------------}
var
   TProtocol,V,dV : Single ;
   NumRamp,i,j,Shape,NumExtra : Integer ;

   VChan : Integer ;            // Voltage stimulus channel
   TInterval : Single ;
   BitWord : SmallInt ;
   BitMask : SmallInt ;
   Bit : SmallInt ;
   DACValue : Integer ;
   DACScale : Single ;
   iStart : Integer ;
   Step : Integer ;


begin

     // Define shape-> plot channel mappings
     for i := V0Start to V0End do VChannel[i] := 0 ;
     for i := V1Start to V1End do VChannel[i] := 1 ;
     for i := DigStart to DigEnd do DigChannel[i] := i - DigStart ;

     // Calculate total duration of voltage protocol
     TProtocol := Prog.NumSteps*Prog.RecordInterval ;
     NumDACPoints := NumDACPointsIn ;

     { Determine if a digital channel is in use }
     Prog.DigitalInUse := False ;
     NumExtra := Prog.NumSteps - 1 ;
     for i := 0 to High(Prog.Shape) do begin
         case Prog.Shape[i] of
              wvDigStep0 : Prog.DigitalInUse := True ;
              wvDigStep1 : Prog.DigitalInUse := True ;
              wvDigTrain : Prog.DigitalInUse := True ;
              end ;
         end ;

     // Fill DAC buffers with holding potential
     // ---------------------------------------

     for VChan := DAC0 to DAC1 do begin

        // Set V->DAC scaling factor
        if Settings.VCommand[VChan].DivideFactor <= 0. then Settings.VCommand[VChan].DivideFactor := 1. ;
        DACScale := (Main.SESLabIO.DACMaxValue*Settings.VCommand[VChan].DivideFactor) / Main.SESLabIO.DACVoltageRange ;

        // Calculate DAC value
        DACValue := Round( DACScale*Settings.VCommand[VChan].HoldingVoltage ) ;
        DACValue := Min(DACValue,Main.SESLabIO.DACMaxValue) ;
        DACValue := Max(DACValue,-Main.SESLabIO.DACMaxValue-1) ;

        // Update buffer
        j := VChan ;
        for i := 1 to NumDACPoints do begin
            DACBuf[j] := DACValue ;
            j := j + NumDACChannels ;
            end ;
        end ;

     // Fill digital buffers with holding bit pattern
     // ---------------------------------------------

      // Create bit and bit mask values for holding values of digital stimulus channel
      BitMask := 0 ;
      BitWord := 0 ;
      Bit := 1 ;
      for Shape := DigStart to DigEnd do begin
          BitMask := BitMask or Bit ;
          if Prog.Invert[Shape] then BitWord := BitWord or Bit ;
          Bit := Bit*2 ;
          end ;

      BitMask := not BitMask ;
      for i := 0 to NumDACPoints-1 do begin
            DigBuf[i] := (DigBuf[i] and BitMask) or BitWord ;
            end ;


     // Fill DAC and digital buffers with protocols shapes
     // --------------------------------------------------

     for Step := 0 to Prog.NumSteps-1 do begin

         // Initialise buffer pointers
         // (leaving first point always set to holding voltages)
         iStart := Trunc((Prog.RecordInterval*Step)/Main.SESLabIO.DACUpdateInterval) ;
         for i := 0 to High(DACPointer) do DACPointer[i] := iStart ;
         for i := 0 to High(DigPointer) do DigPointer[i] := iStart ;

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

             case Prog.Shape[Shape] of
              wvStep0 : Begin
                  { Fixed size step }
                  FillDACBuf( VChannel[Shape],
                              Prog.Delay[Shape],
                              0.0,
                              DACBuf) ;
                  FillDACBuf( VChannel[Shape],
                              Prog.Duration[Shape],
                              Prog.Amplitude[Shape],
                              DACBuf) ;
                  end ;

              wvStep1 : Begin
                  { Family of steps incrementing in amplitude }
                  FillDACBuf( VChannel[Shape],
                              Prog.Delay[Shape],
                              0.0,
                              DACBuf) ;
                  FillDACBuf( VChannel[Shape],
                              Prog.Duration[Shape],
                              Prog.Amplitude[Shape] + Prog.Increment[Shape]*Step,
                              DACBuf) ;
                  end ;
              wvStep2 : Begin
                  { Family of steps incrementing in duration }
                  FillDACBuf( VChannel[Shape],
                              Prog.Delay[Shape],
                              0.0,
                              DACBuf) ;
                  FillDACBuf( VChannel[Shape],
                              (Prog.Duration[Shape]
                              + Prog.Increment[Shape]*Step ),
                              Prog.Amplitude[Shape],
                              DACBuf) ;
                  end ;

              wvRamp : Begin
                  { Voltage ramp }
                  FillDACBuf( VChannel[Shape],
                              Prog.Delay[Shape],
                              0.0,
                              DACBuf
                              ) ;
                  FillDACBufRamp( VChannel[Shape],
                                  Prog.Duration[Shape],
                                  Prog.RampStart[Shape],
                                  Prog.RampEnd[Shape],
                                  DACBuf
                                  ) ;

                  end ;

              wvPTrain : Begin
                  { Pulse train }
                  FillDACBuf( VChannel[Shape],
                              Prog.Delay[Shape],
                              0.0,
                              DACBuf) ;
                  for i := 1 to Prog.NumPulses[Shape] do begin
                      { Pulse duration }
                      FillDACBuf( VChannel[Shape],
                                  Prog.Duration[Shape],
                                  Prog.Amplitude[Shape],
                                  DACBuf) ;
                      { Inter pulse interval }
                      { Note ... Train ends at last pulse }
                      TInterval := Max(
                                   (Prog.PulseInterval[Shape] - Prog.Duration[Shape]),
                                   Main.SESLabIO.DACUpdateInterval
                                   ) ;
                      if i = Prog.NumPulses[Shape] then TInterval := Main.SESLabIO.DACUpdateInterval ;
                      FillDACBuf( VChannel[Shape],
                                  TInterval,
                                  0.0,
                                  DACBuf) ;
                      end ;
                  end ;

              wvNoise : Begin
                  // Pseudo-random binary noise sequence
                  { Initial delay }
                  FillDACBuf( VChannel[Shape],
                              Prog.Delay[Shape],
                              0.0,
                              DACBuf) ;
                  // Noise
                  FillDACBufNoise( VChannel[Shape],
                                   Prog.Duration[Shape],
                                   Prog.Amplitude[Shape],
                                   DACBuf) ;
                  end ;

              wvWave : Begin
                  { Waveform defined from an external file }
                  FillDACBuf( VChannel[Shape],
                              Prog.Delay[Shape],
                              0.0,
                              DACBuf
                              ) ;
                  FillDACBufWave( VChannel[Shape],
                                  ExtVBuf^,
                                  Prog.ExtNumPoints,
                                  DACBuf
                                  ) ;
                  end ;

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

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

              wvDigTrain : begin
                  { Pulse train }
                  FillDigBuf( DigChannel[Shape],
                              Prog.Delay[Shape],
                              Prog.Invert[Shape],
                              DigBuf) ;
                  for i := 1 to Prog.NumPulses[Shape] do begin
                      { Pulse duration }
                      FillDigBuf( DigChannel[Shape],
                                  Prog.Duration[Shape],
                                  not Prog.Invert[Shape],
                                  DigBuf) ;
                      { Inter pulse interval }
                      { Note ... Train ends at last pulse }
                      TInterval := Prog.PulseInterval[Shape] - Prog.Duration[Shape] ;
                      if i = Prog.NumPulses[Shape] then TInterval := Main.SESLabIO.DACUpdateInterval ;
                      FillDigBuf( DigChannel[Shape],
                                  TInterval,
                                  Prog.Invert[Shape],
                                  DigBuf) ;
                      end ;
                  Prog.DigitalInUse := True ;
                  end ;
              end ;
             end ;
         end ;

     {Set default value for digital ports }
     Prog.DigitalPortValue := DigBuf[0] ;

     end ;


procedure TStimulator.FillDACBuf(
          VChan : Integer ;                    // Voltage O/P channel to be updated
          Time : Single ;                      // Time at voltage level
          DACVolts : Single ;                  // Voltage level
          DACBuf : PSmallIntArray    // Pointers to DAC buffers
          ) ;
{ -------------------------------------------------------------
  Copy a series of NumPoints D/A values of amplitude Value into
  channel Chan of a D/A output buffer Buf, starting at point BufPointer
  ---------------------------------------------------------------------
  23/11/01 DAC buffer binary wrap-round error fixed}
var
   i,j : Integer ;
   DACValue : Integer ;
   DACScale : Single ;
   NumPoints : Integer ;
begin

     if Time <= 0.0 then Exit ;

     // Set V->DAC scaling factor
     if Settings.VCommand[VChan].DivideFactor <= 0. then Settings.VCommand[VChan].DivideFactor := 1. ;
     DACScale := (Main.SESLabIO.DACMaxValue*Settings.VCommand[VChan].DivideFactor) / Main.SESLabIO.DACVoltageRange ;

     // Scaled D/A voltage value (Note holding voltage for channel added
     DACValue := Round(DACScale*(DACVolts + Settings.VCommand[VChan].HoldingVoltage)) ;
     DACValue := Max( Min( DACValue, Main.SESLabIO.DACMaxValue ),-Main.SESLabIO.DACMaxValue-1 ) ;

     NumPoints := Max( Round(Time/Main.SESLabIO.DACUpdateInterval),1) ;

//     if VChan = 0 then outputdebugString(PChar(format('Fill DAC0 %.4g ',[DACVolts + Settings.VCommand[VChan].HoldingVoltage]))) ;

     j := DACPointer[VChan]*NumDACChannels + VChan ;
     for i := 1 to NumPoints do begin
         if j <= (NumDACPoints*NumDACChannels-1) then DACBuf[j] := DACValue ;
         j := j + NumDACChannels ;
         end ;

     DACPointer[VChan] := DACPointer[VChan] + NumPoints ;

     end ;


procedure TStimulator.FillDACBufNoise(
          VChan : Integer ;                    // Voltage O/P channel to be updated
          Time : Single ;                      // Time at voltage level
          DACVolts : Single ;                  // Voltage level
          DACBuf : PSmallIntArray    // Pointers to DAC buffers
          ) ;
// ---------------------------------------------------------
// Create pseudo-random binary sequence toggling between
// Holding Voltage + DACVolts and Holding Voltage - DACVolts
// ---------------------------------------------------------
var
   i,j : Integer ;
   DACValuePos,DACValueNeg : Integer ;
   DACScale : Single ;
   NumPoints : Integer ;
   DACBufLimit : Integer ;
begin

     if Time <= 0.0 then Exit ;

     // Set V->DAC scaling factor
     if Settings.VCommand[VChan].DivideFactor <= 0. then Settings.VCommand[VChan].DivideFactor := 1. ;
     DACScale := (Main.SESLabIO.DACMaxValue*Settings.VCommand[VChan].DivideFactor) / Main.SESLabIO.DACVoltageRange ;

     // Scaled pos/neg D/A voltage value (Note holding voltage for channel added)
     DACValuePos := Round(DACScale*(0.5*DACVolts + Settings.VCommand[VChan].HoldingVoltage)) ;
     DACValuePos := Max( Min( DACValuePos, Main.SESLabIO.DACMaxValue ),-Main.SESLabIO.DACMaxValue-1 ) ;
     DACValueNeg := Round(DACScale*(Settings.VCommand[VChan].HoldingVoltage - 0.5*DACVolts)) ;
     DACValueNeg := Max( Min( DACValueNeg, Main.SESLabIO.DACMaxValue ),-Main.SESLabIO.DACMaxValue-1 ) ;

     NumPoints := Max( Round(Time/Main.SESLabIO.DACUpdateInterval),1) ;

     // Ensure different random sequence every time
     Randomize ;

     // Create random sequence
     j := DACPointer[VChan]*NumDACChannels + VChan ;
     DACBufLimit := (NumDACPoints*NumDACChannels-1) ;
     for i := 1 to NumPoints do if j <= DACBufLimit then begin
         if Random > 0.5 then DACBuf[j] := DACValuePos
                         else DACBuf[j] := DACValueNeg ;
         j := j + NumDACChannels ;
         end ;

     DACPointer[VChan] := DACPointer[VChan] + NumPoints ;

     end ;


procedure TStimulator.FillDACBufRamp(
          VChan : Integer ;                    // Voltage O/P channel to be updated
          Time : Single ;                      // Time at voltage level
          DACVoltsStart : Single ;                  // Voltage level
          DACVoltsEnd : Single ;                  // Voltage level
          DACBuf : PSmallIntArray    // Pointers to DAC buffers
          ) ;
{ -------------------------------------------------------------
  Copy a series of NumPoints D/A values of amplitude Value into
  channel Chan of a D/A output buffer Buf, starting at point BufPointer
  ---------------------------------------------------------------------
  23/11/01 DAC buffer binary wrap-round error fixed}
var
   i,j : Integer ;
   DACValue,DACValueStart,DACValueEnd : Integer ;
   DACScale,DACValueStep : Single ;
   NumPoints,DACBufLimit : Integer ;
begin

     if Time <= 0.0 then Exit ;

     // Set V->DAC scaling factor
     if Settings.VCommand[VChan].DivideFactor <= 0. then Settings.VCommand[VChan].DivideFactor := 1. ;
     DACScale := (Main.SESLabIO.DACMaxValue*Settings.VCommand[VChan].DivideFactor) / Main.SESLabIO.DACVoltageRange ;

     NumPoints := Max( Round(Time/Main.SESLabIO.DACUpdateInterval),1) ;

     DACValueStart := Round( DACScale*( DACVoltsStart
                                        + Settings.VCommand[VChan].HoldingVoltage)) ;
     DACValueStart := Max( Min( DACValueStart, Main.SESLabIO.DACMaxValue ),
                           -Main.SESLabIO.DACMaxValue-1 ) ;
     DACValueEnd := Round( DACScale*( DACVoltsEnd
                                      + Settings.VCommand[VChan].HoldingVoltage)) ;
     DACValueEnd := Max( Min( DACValueEnd, Main.SESLabIO.DACMaxValue ),
                         -Main.SESLabIO.DACMaxValue-1 ) ;
     DACValueStep := (DACValueEnd - DACValueStart) / NumPoints ;
     j := DACPointer[VChan]*NumDACChannels + VChan ;
     DACBufLimit := NumDACPoints*NumDACChannels-1 ;
     for i := 0 to NumPoints-1 do begin
         DACValue := Round(DACValueStart + i*DACValueStep) ;
         if j <= DACBufLimit then DACBuf[j] := DACValue ;
         j := j + NumDACChannels ;
         end ;

     DACPointer[VChan] := DACPointer[VChan] + NumPoints ;

     end ;


procedure TStimulator.FillDACBufWave(
          VChan : Integer ;                    // Voltage O/P channel to be updated
          var Wave : Array of Single ;         // Voltage waveform to be added
          NumPoints : Integer ;                // No. of points in Wave
          DACBuf : PSmallIntArray    // Pointers to DAC buffers
          ) ;
// -----------------------
// Add waveform to buffer
// -----------------------
var
   i,j : Integer ;
   DACValue : Integer ;
   DACScale : Single ;
   DACBufLimit : Integer ;
begin

     // Set V->DAC scaling factor
     if Settings.VCommand[VChan].DivideFactor <= 0. then Settings.VCommand[VChan].DivideFactor := 1. ;
     DACScale := (Main.SESLabIO.DACMaxValue*Settings.VCommand[VChan].DivideFactor) / Main.SESLabIO.DACVoltageRange ;

     // Load waveform
     j := DACPointer[VChan]*NumDACChannels + VChan ;
     DACBufLimit := (NumDACPoints*NumDACChannels-1) ;
     for i := 0 to NumPoints-1 do if j <= DACBufLimit then begin
         DACValue := Round(DACScale*(Wave[i] + Settings.VCommand[VChan].HoldingVoltage)) ;
         DACValue := Max( Min( DACValue, Main.SESLabIO.DACMaxValue ),
                                        -Main.SESLabIO.DACMaxValue-1 ) ;
         DACBuf[j] := DACValue ;
         j := j + NumDACChannels ;
         end ;

     DACPointer[VChan] := DACPointer[VChan] + NumPoints ;

     end ;


procedure TStimulator.FillDigBuf(
          DigChannel : Integer ;
          Time : Single ;
          BitOn : Boolean ;
          DigBuf : PSmallIntArray    // Pointers to digital buffers
          ) ;
{ -------------------------------------------------------------
  Set or clear the bit defined by "BitMask" in the series of "NumPoints"
  16 bit integer in the array "Buf", starting at "BPointer"
  Note. If a bit-set operation is required, BitMask should have that bit
  set to 1 (all others 0). For bit-clear operations, the required bit is
  set to 0, (all other bits to 1).
  ---------------------------------------------------------------------}
var
   i : Integer ;
   Bit : SmallInt ;
   BitMask : SmallInt ;
   BitValue : SmallInt ;
   NumPoints : Integer ;
   BPointer : Integer ;
begin

     if Time <= 0.0 then Exit ;

     // Create bit and bit mask values for digital channel
     Bit := 1 ;
     for i := 0 to DigChannel-1 do Bit := Bit*2 ;

     // Set bit mask and bit value to be set
     BitMask := not Bit ;
     if BitOn then BitValue := Bit
              else BitValue := 0 ;

     NumPoints := Max( Trunc(Time/Main.SESLabIO.DACUpdateInterval ),0 ) ;

     { Clear the bit defined by BitMask, all other bits left untouched }
     BPointer := DigPointer[DigChannel] ;
     for i := 1 to NumPoints do begin
         if BPointer < NumDACPoints then
            DigBuf[BPointer] := (DigBuf[BPointer] and BitMask) or BitValue ;
         Inc(BPointer) ;
         end ;

    DigPointer[DigChannel] := BPointer ;

    end ;


procedure TStimulator.LoadProgram(
          const Name : string     { Voltage program file path }
          ) ;
{ --------------------------------
  Load a voltage program from file
  --------------------------------}
var
   FileHandle : Integer ;
   i : Integer ;
begin

    // Open stimulus protocol file
    FileName := ChangeFileExt( Name, '.sti' ) ;

    if not FileExists( FileName ) then begin
       ShowMessage('File - ' + FileName + ' does not exist!') ;
       Exit ;
       end ;

    Prog.FileName := FileName ;

    FileHandle := FileOpen(  FileName, fmOpenReadWrite ) ;
    if FileHandle < 0 then begin
       ShowMessage('Unable to open - ' + FileName ) ;
       Exit ;
       end ;

    // Load protocol file
    FileSeek( FileHandle, 0, 0 ) ;
    if FileRead(FileHandle,Prog,Sizeof(Prog)) < Sizeof(Prog) then
       ShowMessage('LoadProgram : Unable to read %s ' ) ;

    // Copy to new external number of sample points and disable old
    if Prog.ExtNumPointsOld >= 0 then begin
       Prog.ExtNumPoints := Prog.ExtNumPointsOld ;
       Prog.ExtNumPointsOld := -1 ;
       end ;

    // Allocate extended buffer
    if ExtVBuf <> Nil then FreeMem(ExtVBuf) ;
    GetMem( ExtVBuf, Prog.ExtNumPoints*4 ) ;

    if Prog.ExtNumPoints > MaxWaveBufPoints then begin
       // Load buffer from extended area at end of file
       FileRead(FileHandle,ExtVBuf^,Prog.ExtNumPoints*4) ;
       end
    else if Prog.ExtNumPoints > 0 then begin
        // Copy from existing ExtWaveData buffer
        for i := 0 to Prog.ExtNumPoints-1 do ExtVBuf^[i] := Prog.ExtWaveData[i] ;
        end ;

    // Close file
    if FileHandle >=0 then FileClose( FileHandle ) ;

    Prog.Saved := True ;

    { Determine if a digital channel is in use }
    Prog.DigitalInUse := False ;
    for i := 0 to High(Prog.Shape) do begin
        case Prog.Shape[i] of
             wvDigStep0 : Prog.DigitalInUse := True ;
             wvDigStep1 : Prog.DigitalInUse := True ;
             wvDigTrain : Prog.DigitalInUse := True ;
             end ;
        end ;

    end ;


procedure TStimulator.SaveProgram(
          const Name : string     { Voltage program file path }
          ) ;
{ --------------------------------
  Save voltage program to file
  --------------------------------}
var
   FileHandle : Integer ;
begin

    // Create file
    FileName := ChangeFileExt( Name, '.sti' ) ;
    Prog.FileName := FileName ;
    FileHandle := FileCreate( FileName ) ;

    // Ensure old no. external point is disabled
    Prog.ExtNumPointsOld := -1 ;

    if FileWrite(FileHandle,Prog,Sizeof(Prog)) <> Sizeof(Prog) then
       ShowMessage('SaveProgram : File Write - Failed ' ) ;

    // Write extended user-defined waveform data
    if Prog.ExtNumPoints >= MaxWaveBufPoints then begin
       FileWrite(FileHandle,ExtVBuf^,(Prog.ExtNumPoints)*4) ;
       end ;

    // Close file
    if FileHandle >=0 then FileClose( FileHandle ) ;

    Prog.Saved := True ;

    end ;


procedure TStimulator.CreateProgramList(
          var cbList : TComboBox
          ) ;
{ --------------------------------------------
  Compile a list of protocol files in the directory \winwcp\Stim
  and put the file names into a combo box
  --------------------------------------------------------------}
var
   SearchRec : TSearchRec ;
   First : Boolean ;
   FileFound : Integer ;
begin
     First := True ;
     cbList.Clear ;
     cbList.items.add( 'None' ) ;
     repeat
        { Find file }
        if First then
           FileFound := FindFirst( Settings.StimDirectory + '*.sti',
                                   faAnyFile,
                                   SearchRec )
        else
           FileFound := FindNext( SearchRec ) ;

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

     end ;


function TStimulator.ProtocolDuration : Single ;
{ --------------------------------------------
  Calculate total duration of voltage protocol
  --------------------------------------------}
const
  DigSumStart = 2 ;
var
   i,NumExtra : Integer ;
   TSum : Array[0..MaxProtocolShapes-1] of Single ;
   TMax : Single ;
   VSum : Array[0..MaxProtocolShapes-1] of Integer ;
   DigSum : Array[0..MaxProtocolShapes-1] of Integer ;
   ClearNumSteps : Boolean ;
begin

     // Ensure number of steps within protocol is one,
     // if no waveform component requires incrementing
     ClearNumSteps := True ;
     for i := 0 to High(Stimulator.Prog.Shape) do begin
         case Stimulator.Prog.Shape[i] of
              wvStep1 : ClearNumSteps := False ;
              wvStep2 : ClearNumSteps := False ;
              wvDigStep1 : ClearNumSteps := False ;
              end ;
         end ;
     if ClearNumSteps then Prog.NumSteps := 1 ;

     Result := Prog.NumSteps*Prog.RecordInterval ;
     if not Prog.RepeatedStimulus then Result := Result + 0.1 ;
     Exit ;

     // Define shape-> plot channel mappings
     for i := V0Start to V0End do VSum[i] := 0 ;
     for i := V1Start to V1End do VSum[i] := 1 ;
     for i := DigStart to DigEnd do DigSum[i] := i - DigSumStart ;

     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[VSum[i]] := TSum[VSum[i]]
                                   + Stimulator.Prog.Duration[i]
                                   + Stimulator.Prog.Delay[i] ;

                  end ;
              wvStep1 : begin
                  TSum[VSum[i]] := TSum[VSum[i]]
                                   + Stimulator.Prog.Duration[i]
                                   + Stimulator.Prog.Delay[i] ;
                  end ;
              wvStep2 : begin
                  TSum[VSum[i]] := TSum[VSum[i]]
                                    + Stimulator.Prog.Duration[i]
                                    + Stimulator.Prog.Delay[i]
                                    + Max(Stimulator.Prog.Increment[i]*NumExtra,0. ) ;
                  end ;
              wvRamp : begin
                  TSum[VSum[i]] := TSum[VSum[i]]
                                   + Stimulator.Prog.Duration[i]
                                   + Stimulator.Prog.Delay[i] ;
                  end ;
              wvPTrain : begin
                 TSum[VSum[i]] := TSum[VSum[i]]
                                  + (Stimulator.Prog.Delay[i] +
                                  + Stimulator.Prog.NumPulses[i]*Stimulator.Prog.PulseInterval[i]) ;
                 end ;
              wvNoise : begin
                  TSum[VSum[i]] := TSum[VSum[i]]
                                   + Stimulator.Prog.Duration[i]
                                   + Stimulator.Prog.Delay[i] ;
                 end ;
              wvWave : begin
                 TSum[VSum[i]] := TSum[VSum[i]]
                                  + (Stimulator.Prog.ExtNumPoints)*Main.SESLabIO.DACUpdateInterval ;
                 end ;

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

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

     Result := TMax ;

     end ;


procedure TStimulator.ClearWaveformElements ;
// ---------------------------
// Clear all waveform elements
// ---------------------------
var
    i : Integer ;
begin
     { Set DAC0 elements to none }
     for i := V0Start to V0End do begin
         Stimulator.Prog.Shape[i] := wvNone ;
         Stimulator.Prog.Delay[i] := 0. ;
         Stimulator.Prog.Duration[i] := 0. ;
         end ;

     { Set DAC1 elements to none }
     for i := V1Start to V1End do begin
         Stimulator.Prog.Shape[i] := wvNone ;
         Stimulator.Prog.Delay[i] := 0. ;
         Stimulator.Prog.Duration[i] := 0. ;
         end ;


     { Set digital elements to none }
     for i := DigStart to DigEnd do begin
         Stimulator.Prog.Shape[i] := wvDigNone ;
         Stimulator.Prog.Delay[i] := 0. ;
         Stimulator.Prog.Duration[i] := 0. ;
         end ;

     end ;

procedure TStimulator.DataModuleCreate(Sender: TObject);
// ----------------------------------
// Initialisation when module created
// ----------------------------------
var
    i : Integer ;
begin
     ExtVBuf := Nil ;
     Prog.ExtNumPoints := 0 ;
     Prog.DACdt := 0.001 ;
     for i := 0 to High(Prog.Shape) do Prog.Shape[i] := wvNone ;

     end;

end.
