unit StimModule;
{ =============================================
  WinWCP -Analogue & Digital Waveform Generator
  12/12/97 DAC update interval can now go down to 0.1 ms
  13/4/98 Bug which caused -10411 NIDAQ errors fixed
          by using faster DAC update intervals
  14/8/99 32 bit version
  23/11/01 Functions in Wavrun.pas moved to proper data module
           Wrap-round when DAC value exceed upper limit
           of DAC binary range fixed (FillDACBuf)
  18/3/02  Prog.RecordDuration fix added for Instrutech support
  19.7.02  Time shift of digital pulses after first sweep in a series
           with CED 1401 interfaces fixed (by ensuring that last point
           output to sync. channel is always Trigger Off)
  23.7.02  DAC update interval limited to 50 ms
           to avoid -10411 error with Lab-PC boards (23.7.02)
  29.11.02 DAC update interval adjusted to avoid DAC buffer overflow
           error with long-lasting pulse waveforms
           DAC buffer overflow error now appears in status bar
  05.02.04 DAC 1 Trigger pulse can now be inverted by
           Settings.DACInvertTriggerLevel flag.
           Stimulus waveforms can now be started by external trigger pulse
           when record interval is set to StimulusExtTriggerFlag = -1.0
  05.11.04 New CreateWaveform method 2 DAC channels supported
  19.07.05 Time course of user defined waveforms now adapted to to DAC update interval
  04.11.05 .NumDACPoints now kept within available buffer limits
  03.08.06 References to RawFh removed
           8000 point limit removed from protocol
  05.06.07 Update interval now set correctly when using digital outputs
           SetADCDACUpdateIntervals now called again
           DAC update interval adjusted to avoid exceeding DAC buffer
           DigitalOutputsInUse function added

  =============================================}

interface


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

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

     // DAC0 shape #s
     DAC0Start = 0 ;
     DAC0End = 9 ;
     // Digital shape #s
     DigStart = 10 ;
     DigEnd = 17 ;
     // DAC1 shape #s
     DAC1Start = 18 ;
     DAC1End = 27 ;
     NumDigChannels = 8 ;
     NumDACChannels = 2 ;
     DAC0 = 0 ;
     DAC1 = 1 ;

     //WaveformCreationTime = 0.05 ;

type

// Stimulator waveform types
TWaveShape = ( wvNone, wvStep0, wvStep1, wvStep2, wvRamp, wvpTrain, wvWave,
               wvDigStep0,wvDigStep1, wvDigTrain, wvDigNone ) ;
// Old waveform definition record (pre-V3.5.2)
TWaveformOld = packed record
          Shape : Array[0..17] of TWaveShape ;
          Amplitude :     Array[0..17] of Single ;
          Duration :      Array[0..17] of Single ;
          Increment :     Array[0..17] of Single ;
          RampStart :     Array[0..17] of Single ;
          RampEnd :       Array[0..17] of Single ;
          PulseInterval : Array[0..17] of Single ;
          NumPulses : Array[0..17] of LongInt ;
          Period :        Array[0..17] of Single ;
          Delay :         Array[0..17] of Single ;
          Invert :        Array[0..17] of Boolean ;
          HoldingVoltage : Single ;
          VMin : Single ;
          VMax : Single ;
          RecordInterval : Single ;
          RecordDuration : Single ;
          RecordDelay : Single ;
          RecordDelayIncrement : Single ;
          NumSteps : LongInt ;
          NumRepeats : LongInt ;
          dt : Single ;
          DACdt : Single ;
          FileName : string[255] ;
          ExtWaveData : Array[0..1023] of single ;
          ExtEndOfData : SmallInt ;
          ExtDACdt : single ;
          NextFile : string[255] ;
          StepCounter : LongInt ;
          RepeatCounter : LongInt ;
          NumDACPoints : LongInt ;
          NotInUse : LongInt ;
          LeakCounter : longint ;
          NumLeaks : LongInt ;
          DigitalInUse : Boolean ;
          DigitalPortValue : LongInt ;
          RecordingStart : LongInt ;
          InUse : Boolean ;
          Saved : Boolean ;
          RecordIntervalChanged : boolean ;
          LeakScale : LongInt ;
          NextProtocolFileName : string[255] ;
          end ;

// 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 ;
          NumRepeats : LongInt ;
          dt : Single ;
          DACdt : Single ;
          FileName : string[255] ;
          ExtWaveData : Array[0..1023] of single ;
          ExtNumPointsOld : SmallInt ;
          ExtDACdt : single ;
          NextFile : string[255] ;
          StepCounter : LongInt ;
          RepeatCounter : LongInt ;
          NumDACPoints : LongInt ;
          ExtNumPoints : LongInt ;
          LeakCounter : longint ;
          NumLeaks : LongInt ;
          DigitalInUse : Boolean ;
          DigitalPortValue : LongInt ;
          RecordingStart : LongInt ;
          InUse : Boolean ;
          Saved : Boolean ;
          RecordIntervalChanged : boolean ;
          LeakScale : LongInt ;
          NextProtocolFileName : string[255] ;
          end ;



  TStimulator = class(TDataModule)
  private
    { Private declarations }
    DACChannel : Array[0..MaxProtocolShapes-1] of Integer ;
    DACPointer : Array[0..NumDACChannels-1] of Integer ;
    DigChannel : Array[0..MaxProtocolShapes-1] of Integer ;
    DigPointer : Array[0..NumDigChannels-1] of Integer ;

    procedure FillDACBuf(
              Chan : Integer ;
              Time : Single ;
              DACVolts : Single ;
              var Buf : Array of SmallInt
              ) ;

    procedure FillDACBufWave(
              Chan : 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
              var Buf : Array of SmallInt ) ;      // Output buffer

    procedure FillDigBuf(
              DigChannel : Integer ;
              Time : Single ;
              BitOn : Boolean ;
              var Buf : Array of SmallInt
              ) ;


  public
    { Public declarations }
    FileName : String ;
    Prog : TWaveform ;
    ExtVBuf : PSingleArray ; // Pointer to extended user-defined waveform buffer

    function CreateWaveform(
             var Buf : Array of SmallInt ;
             var DigBuf : Array of SmallInt
             ) : Integer ;
    procedure SetADCDACUpdateIntervals(
              TProtocol : Single
               ) ;
    function CreateLeakWaveform(
             var Buf : Array of SmallInt
             ) : Integer ;
    procedure CreateProgramList(
              var cbList : TComboBox ) ;
    procedure LoadProgram(
              const Name : string ) ;
    procedure SaveProgram(
              const Name : string ) ;
    procedure ClearWaveformElements ;
    function ProtocolDuration : single ;
    function DigitalOutputsInUse : Boolean ;

  end;

var
  Stimulator: TStimulator;

implementation

uses Mdiform, shared, Wavgen ;

{$R *.DFM}


function TStimulator.CreateWaveform(
         var Buf : Array of SmallInt ;
         var DigBuf : Array of SmallInt
         ) : Integer ;
{ -----------------------------------------------------
  Create command voltage waveforms defined by protocol
  ----------------------------------------------------}
var
   TProtocol,V,dV : Single ;
   NumRamp,i,j,Shape,NumExtra,iEnd,iStep : Integer ;

   DACChan : Integer ;
   TInterval : Single ;
   BitWord : SmallInt ;
   BitMask : SmallInt ;
   DACValue : Integer ;
   DACScale : Single ;

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

begin

     // Define shape-> plot channel mappings
     for i := DAC0Start to DAC0End do DACChannel[i] := 0 ;
     for i := DAC1Start to DAC1End do DACChannel[i] := 1 ;
     for i := DigStart to DigEnd do DigChannel[i] := i - DigStart ;

     // Calculate total duration of voltage protocol
     TProtocol := ProtocolDuration ;

     { Determine if a digital channel is in use }
     Prog.DigitalInUse := DigitalOutputsInUse ;

     NumExtra := Prog.NumSteps - 1 ;

     // Calculate DAC update interval
     // (taking into account hardware constraints)
     SetADCDACUpdateIntervals( TProtocol ) ;

     // Determine number of points in protocol
     // (Keep within available buffer)
     Prog.NumDACPoints := Round(TProtocol/Prog.DACdt) ;
     if Prog.NumDACPoints > (Main.SESLabIO.DACBufferLimit div 2) then begin
        Prog.NumDACPoints := Main.SESLabIO.DACBufferLimit div 2 ;
        Main.StatusBar.SimpleText :=
        ' WARNING! D/A buffer overflow! Stimulus waveform may be truncated. '  ;
        WriteToLogFile( Main.StatusBar.SimpleText ) ;
        end ;

     { Create a digital D/A waveform from protocol list }

     // Fill DAC buffers with holding potential
     for DACChan := DAC0 to DAC1 do begin
         // Set V->DAC scaling factor
         if Settings.DAC[DACChan].DivideFactor <= 0. then Settings.DAC[DACChan].DivideFactor := 1. ;
         DACScale := (Main.SESLabIO.DACMaxValue*Settings.DAC[DACChan].DivideFactor) /
                      Main.SESLabIO.DACVoltageRange ;
         // Calculate DAC value
         DACValue := Round( DACScale*Prog.HoldingVoltage[DACChan] ) ;
         if DACValue > Main.SESLabIO.DACMaxValue then DACValue := Main.SESLabIO.DACMaxValue ;
         if DACValue < Main.SESLabIO.DACMinValue then DACValue := Main.SESLabIO.DACMinValue ;
         // Update buffer
         j := DACChan ;
         for i := 1 to Prog.NumDACPoints do begin
             Buf[j] := DACValue ;
             j := j + NumDACChannels ;
             end ;
         end ;

     { Set default digital output buffer bit settings}
     Prog.DigitalInUse := False ;
     BitMask := 1 ;
     BitWord := 0 ;
     for Shape := DigStart to DigEnd do begin
         if Prog.Invert[Shape] then BitWord := BitWord or BitMask ;
         BitMask := BitMask*2 ;
         end ;
     for i := 0 to High(DigBuf) do DigBuf[i] := BitWord ;

     // Initialise buffer pointers
     // (leaving first point always set to holding voltages)
     for i := 0 to High(DACPointer) do DACPointer[i] := 1 ;
     for i := 0 to High(DigPointer) do DigPointer[i] := 1 ;

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

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

         case Prog.Shape[Shape] of
              wvStep0 : Begin
                  { Fixed size step }
                  FillDACBuf( DACChannel[Shape],
                              Prog.Delay[Shape],
                              Prog.HoldingVoltage[DACChannel[Shape]],
                              Buf) ;
                  FillDACBuf( DACChannel[Shape],
                              Prog.Duration[Shape],
                              Prog.Amplitude[Shape] + Prog.HoldingVoltage[DACChannel[Shape]],
                              Buf) ;
                  end ;
              wvStep1 : Begin
                  { Family of steps incrementing in amplitude }
                  FillDACBuf( DACChannel[Shape],
                              Prog.Delay[Shape],
                              Prog.HoldingVoltage[DACChannel[Shape]],
                              Buf) ;
                  FillDACBuf( DACChannel[Shape],
                              Prog.Duration[Shape],
                              Prog.Amplitude[Shape] + Prog.HoldingVoltage[DACChannel[Shape]]
                              + Prog.Increment[Shape]*Prog.StepCounter,
                              Buf) ;
                  end ;
              wvStep2 : Begin
                  { Family of steps incrementing in duration }
                  FillDACBuf( DACChannel[Shape],
                              Prog.Delay[Shape],
                              Prog.HoldingVoltage[DACChannel[Shape]],
                              Buf) ;
                  FillDACBuf( DACChannel[Shape],
                              (Prog.Duration[Shape]
                              + Prog.Increment[Shape]*Prog.StepCounter ),
                              Prog.Amplitude[Shape]+Prog.HoldingVoltage[DACChannel[Shape]],
                              Buf) ;
                  end ;
              wvRamp : Begin
                  { Voltage ramp }
                  FillDACBuf( DACChannel[Shape],
                              Prog.Delay[Shape],
                              Prog.HoldingVoltage[DACChannel[Shape]],
                              Buf
                              ) ;

                  NumRamp := Max( Trunc( Prog.Duration[Shape] / Prog.DACdt ),1) ;
                  dV := (Prog.RampEnd[Shape] - Prog.RampStart[Shape] ) /(NumRamp) ;
                  V := Prog.RampStart[Shape] + Prog.HoldingVoltage[DACChannel[Shape]] ;
                  for i := 1 to NumRamp do begin
                      FillDACBuf( DACChannel[Shape],
                                  Prog.DACdt,
                                  V,
                                  Buf) ;
                      V := V + dV ;
                      end ;
                  end ;

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

              wvWave : Begin
                  { Waveform defined from an external file }
                  FillDACBuf( DACChannel[Shape],
                              Prog.Delay[Shape],
                              Prog.HoldingVoltage[DACChannel[Shape]],
                              Buf
                              ) ;
                  FillDACBufWave( DACChannel[Shape],
                                  ExtVBuf^,
                                  Prog.ExtNumPoints,
                                  Buf
                                  ) ;
                  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]*Prog.StepCounter)),
                              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 := Prog.DACdt ;
                      FillDigBuf( DigChannel[Shape],
                                  TInterval,
                                  Prog.Invert[Shape],
                                  DigBuf) ;
                      end ;
                  Prog.DigitalInUse := True ;
                  end ;
              end ;
         end ;

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

     Result := Prog.NumDACPoints ;
     end ;


function TStimulator.DigitalOutputsInUse : Boolean ;
// -------------------------------------------------
// Return TRUE if a digital outputs used in protocol
// -------------------------------------------------
var
    i : Integer ;
begin
     { Determine if a digital channel is in use }
     Result := False ;
     for i := 0 to High(Prog.Shape) do begin
         case Prog.Shape[i] of
              wvDigStep0 : Result := True ;
              wvDigStep1 : Result := True ;
              wvDigTrain : Result := True ;
              end ;
         end ;
     end ;


procedure TStimulator.SetADCDACUpdateIntervals(
          TProtocol : Single // Duration of protocol (s)
          ) ;
// -----------------------------
// Calculate DAC update interval
// -----------------------------
var
    i : Integer ;
    MinDACdt : Single ;
    MaxDACPoints : Integer ;
begin

     Prog.DigitalInUse := DigitalOutputsInUse ;

     Main.SESLabIO.DigitalStimulusEnabled := Prog.DigitalInUse ;
     if Prog.DigitalInUse then MinDACdt := Main.SESLabIO.DigMinUpdateInterval
                          else MinDACdt := Main.SESLabIO.DACMinUpdateInterval ;

     // Find an optimum D/A update interval
     MaxDACPoints := Main.SESLabIO.DACBufferLimit div 2 ;
     Prog.NumDACPoints := MaxDACPoints ;

     Prog.DACdt := Max( TProtocol/Prog.NumDACPoints,MinDACdt ) ;

     // Make it a multiple of the smallest interval
     Prog.DACdt := Max( Round(Prog.DACdt/MinDACdt)*MinDACdt,MinDACdt) ;

     // Ensure it doesn't exceed buffer limit
     if Round(TProtocol/Prog.DACdt) > MaxDACPoints then
        Prog.DACdt := Prog.DACdt*2.0 ;

     // Ensure update interval is no longer than 50 ms
     // to avoid -10411 error with Lab-PC boards (23.7.02)
     Prog.DACdt := Min(Prog.DACdt,0.05) ;

     { If an externally defined waveform is in use ... its DAC update
       interval over-rides the internal setting }
     for i := 0 to High(Prog.Shape) do if Prog.Shape[i] = wvWave then
         Prog.DACdt := Max(Prog.ExtDACdt,MinDACdt) ;

     { These calls made for the benefit of the Digidata 132X and similar
       interfaces where D/A update and A/D sampling intervals must be the same }
     Main.SESLabIO.ADCSamplingInterval := TProtocol / Settings.NumSamples ;
     Main.SESLabIO.DACUpdateInterval := Prog.DACdt ;
     Prog.DACdt := Main.SESLabIO.DACUpdateInterval ;

     end ;


function TStimulator.CreateLeakWaveform(
         var Buf : Array of SmallInt
          ) : Integer ;
{ -------------------------------------------------------------------------
  Create leak pulse waveform by dividing down test waveform by Prog.NumLeaks
  -------------------------------------------------------------------------}
var
   i,j : Integer ;
   iHold : Integer ;
   VScale : single ;
begin
     if Prog.NumLeaks <> 0 then begin
        iHold := Buf[0] ;
        if Prog.LeakScale <> 0 then VScale := 1. / Prog.LeakScale
                              else VScale := 1 ;
        j := DAC0 ;
        for i := 0 to Prog.NumDACPoints-1 do begin
            Buf[j] := Round((Buf[j] - iHold)*VScale) + iHold ;
            j := j + 2 ;
            end ;
        end ;
   Result := Prog.NumDACPoints ;
   end ;


procedure TStimulator.FillDACBuf(
          Chan : Integer ;
          Time : Single ;
          DACVolts : Single ;
          var Buf : Array of SmallInt
          ) ;
{ -------------------------------------------------------------
  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 ;
   jLimit : Integer ;
begin

     if Time <= 0.0 then Exit ;

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

     DACValue := Round( DACScale*DACVolts) ;
     if DACValue > Main.SESLabIO.DACMaxValue then DACValue := Main.SESLabIO.DACMaxValue ;
     if DACValue < Main.SESLabIO.DACMinValue then DACValue := Main.SESLabIO.DACMinValue ;

     NumPoints := Max( Round(Time/Prog.DACdt),1) ;

     jLimit := Main.SESLabIO.ADCBufferLimit ;
     j := DACPointer[Chan]*NumDACChannels + Chan ;
     for i := 1 to NumPoints do begin
         if j < jLimit then begin
            Buf[j] := DACValue ;
            Inc(DACPointer[Chan]) ;
            j := j + NumDACChannels ;
            end
         else begin
            Main.StatusBar.SimpleText :=
            ' WARNING! D/A buffer overflow! Stimulus waveform may be truncated. '  ;
            Break ;
            end ;
         end ;

     //DACPointer[Chan] := DACPointer[Chan] + NumPoints ;

     end ;


procedure TStimulator.FillDACBufWave(
          Chan : 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
          var Buf : Array of SmallInt          // Output buffer
          ) ;
// -----------------------
// Add waveform to buffer
// -----------------------
var
   i,j : Integer ;
   DACValue : Integer ;
   DACScale : Single ;
   DACBufLimit : Integer ;
begin

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

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

     end ;





procedure TStimulator.FillDigBuf(
          DigChannel : Integer ;
          Time : Single ;
          BitOn : Boolean ;
          var Buf : Array of SmallInt
          ) ;
{ -------------------------------------------------------------
  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 ;
   NumPoints : Integer ;
   EndOfBuf : 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 ;
     BitMask := not Bit ;

     NumPoints := Max( Trunc(Time/Prog.DACdt ),0 ) ;
     EndOfBuf := Main.SESLabIO.DACBufferLimit -1 ;

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

     // Set the bit
     if BitOn = True then begin
        BPointer := DigPointer[DigChannel] ;
        for i := 1 to NumPoints do if BPointer <= EndOfBuf then begin
            Buf[BPointer] := Buf[BPointer] or Bit ;
            Inc(BPointer) ;
            end ;
        end ;

    DigPointer[DigChannel] := BPointer ;

    end ;


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

    // Open stimulus protocol file
    FileName := ChangeFileExt( Name, '.vpr' ) ;
    Prog.FileName := FileName ;
    FileHandle := FileOpen(  FileName, fmOpenReadWrite ) ;

    NumBytes := FileSeek( FileHandle, 0, 2 ) ;

    if NumBytes >= SizeOf(Prog) then begin
       // Load protocol file
       // ------------------
       FileSeek( FileHandle, 0, 0 ) ;
       if FileRead(FileHandle,Prog,Sizeof(Prog)) <> Sizeof(Prog) then
          ShowMessage(format( 'LoadProgram : Unable to read %s ',[FileName])) ;

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

       end
    else begin
       // Load old format protocol file
       // -----------------------------
       FileSeek( FileHandle, 0, 0 ) ;
       if FileRead(FileHandle,OldProg,Sizeof(OldProg)) <> Sizeof(OldProg) then
          ShowMessage(format( 'LoadProgram : Unable to read %s ',[FileName])) ;

       // Clear all waveshapes
       ClearWaveformElements ;

       // Copy to new format
       for i := 0 to MaxOldWaveShapes-1 do begin
           // Swap wvDigNone for wvNone in digital elements
           Prog.Shape[i] := OldProg.Shape[i] ;
           if (i >= 10) and (OldProg.Shape[i] = wvNone) then
              Prog.Shape[i] := wvDigNone ;
           Prog.Amplitude[i] := OldProg.Amplitude[i] ;
           Prog.Duration[i] := OldProg.Duration[i] ;
           Prog.Increment[i] := OldProg.Increment[i] ;
           Prog.RampStart[i] := OldProg.RampStart[i] ;
           Prog.RampEnd[i] := OldProg.RampEnd[i] ;
           Prog.PulseInterval[i] := OldProg.PulseInterval[i] ;
           Prog.NumPulses[i] := OldProg.NumPulses[i] ;
           Prog.Period[i] := OldProg.Period[i] ;
           Prog.Delay[i] := OldProg.Delay[i] ;
           Prog.Invert[i] := OldProg.Invert[i] ;
           end ;
       Prog. HoldingVoltage[0] := OldProg. HoldingVoltage ;
       Prog. HoldingVoltage[1] := 0.0 ;
       Prog.RecordInterval := OldProg.RecordInterval ;
       Prog.RecordDuration := OldProg.RecordDuration ;
       Prog.NumSteps := OldProg.NumSteps ;
       Prog.NumRepeats := OldProg.NumRepeats ;
       Prog.dt := OldProg.dt ;
       Prog.DACdt := OldProg.DACdt ;

       for j := 0 to High(Prog.ExtWaveData) do
           Prog.ExtWaveData[j] := OldProg.ExtWaveData[j] ;
       Prog.ExtNumPoints := OldProg.ExtEndOfData ;
       Prog.ExtDACdt := OldProg.ExtDACdt ;
       Prog.FileName := OldProg.FileName ;
       Prog.NextFile := OldProg.NextFile ;
       Prog.NumDACPoints := OldProg.NumDACPoints ;
       Prog.ExtNumPoints := OldProg.NotInUse ;
       Prog.NumLeaks := OldProg.NumLeaks ;
       Prog.DigitalInUse := OldProg.DigitalInUse ;
       Prog.DigitalPortValue := OldProg.DigitalPortValue ;
       Prog.RecordingStart := OldProg.RecordingStart ;
       Prog.InUse := OldProg.InUse ;
       Prog.Saved := OldProg.Saved ;
       Prog.RecordIntervalChanged := OldProg.RecordIntervalChanged ;
       Prog.LeakScale := OldProg.LeakScale ;
       Prog.NextProtocolFileName := OldProg.NextProtocolFileName ;

       // Save as new format
       FileSeek( FileHandle, 0, 0 ) ;
       if FileWrite(FileHandle,OldProg,Sizeof(OldProg)) <> Sizeof(OldProg) then
          ShowMessage(format( 'LoadProgram : Unable to write to %s ',[FileName])) ;

       end ;

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

    { Initialise counters in preparation for use }
    Prog.StepCounter := 0 ;
    Prog.RepeatCounter := 0 ;
    Prog.LeakCounter := 0 ;

    Prog.Saved := True ;
    Prog.RecordIntervalChanged := False ;

    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, '.vpr' ) ;
    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 ;
    Prog.RecordIntervalChanged := False ;

    end ;


procedure TStimulator.CreateProgramList(
          var cbList : TComboBox
          ) ;
{ --------------------------------------------
  Compile a list of protocol files in the directory \winwcp\vprot
  and put the file names into a combo box
  --------------------------------------------------------------}
var
   SearchRec : TSearchRec ;
   First : Boolean ;
   FileFound : Integer ;
begin
     First := True ;
     cbList.Clear ;
     cbList.items.add( ' ' ) ;
     repeat
        { Find file }
        if First then
           FileFound := FindFirst( Settings.VProtDirectory + '*.vpr',
                                   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 ;
   DACSum : Array[0..MaxProtocolShapes-1] of Integer ;
   DigSum : Array[0..MaxProtocolShapes-1] of Integer ;

begin

     // Define shape-> plot channel mappings
     for i := DAC0Start to DAC0End do DACSum[i] := 0 ;
     for i := DAC1Start to DAC1End do DACSum[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[DACSum[i]] := TSum[DACSum[i]]
                                          + Stimulator.Prog.Duration[i]
                                          + Stimulator.Prog.Delay[i] ;
                  end ;
              wvStep1 : begin
                  TSum[DACSum[i]] := TSum[DACSum[i]]
                                          + Stimulator.Prog.Duration[i]
                                          + Stimulator.Prog.Delay[i] ;
                  end ;
              wvStep2 : begin
                  TSum[DACSum[i]] := TSum[DACSum[i]]
                                          + Stimulator.Prog.Duration[i]
                                          + Stimulator.Prog.Delay[i]
                                          + Max(Stimulator.Prog.Increment[i]*NumExtra,0. ) ;
                  end ;
              wvRamp : begin
                  TSum[DACSum[i]] := TSum[DACSum[i]]
                                          + Stimulator.Prog.Duration[i]
                                          + Stimulator.Prog.Delay[i] ;
                  end ;
              wvPTrain : begin
                 TSum[DACSum[i]] := TSum[DACSum[i]]
                                         + (Stimulator.Prog.Delay[i] +
                                         + Stimulator.Prog.NumPulses[i]*Stimulator.Prog.PulseInterval[i]) ;
                 end ;
              wvWave : begin
                 TSum[DACSum[i]] := TSum[DACSum[i]]
                                         + (Stimulator.Prog.ExtNumPoints)*Stimulator.Prog.ExtDACdt ;
                 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.RecordDuration ;
     for i := 0 to NumDisplayChannels-1 do if TMax < TSum[i] then TMax := TSum[i] ;

     // Bodge inserted for Instrutech interfaces
     if (Main.SESLabIO.LabInterfaceType = 7) or (Main.SESLabIO.LabInterfaceType = 10) then
        TMax := Prog.RecordDuration ;

     Result := TMax ;
     end ;


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

     { Set DAC1 elements to none }
     for i := DAC1Start to DAC1End do begin
         Stimulator.Prog.Shape[i] := wvNone ;
         Stimulator.Prog.Delay[i] := 0. ;
         Stimulator.Prog.Duration[i] := 0. ;
         Stimulator.Prog.NumPulses[i] := 1 ;
         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. ;
         Stimulator.Prog.NumPulses[i] := 1 ;
         end ;

     end ;

end.
