unit EventDetector;
// --------------------------------------------------
// Spontaneous Event Detector module
// (c) John Dempster, University of Strathclyde, 2002
// --------------------------------------------------
// 25/2/02
// 9/4/02 .... RH.ADCVoltageRange now correctly takes account of CDRFH.ADCAmplifierGain
// 14/4/02 ... MatchTemplate detection criterion now aligned with peak signal
// 29.5.02 ... Selection of channel in multi-channel files now works correctly
//             "t" label added to indicate detection threshold
// 30.7.02 ... Changes to detected event list now saved to file when form closed
// 31.7.02 ... Occasional misplacement of inserted events fixed
// 26.8.02 ... Rate over specified interval added
// 14.12.02 ... Area add decay time constant measurements added
//              Event filter added
// 12.6.03 .... Bug: Incorrect display of detection criteria trace when more than
//              one channel fixed
// 24.6.03 .... No. horizontal/vertical grid lines changeable
// 8.4.03 ..... NumEvents now restricted to limits of Events array
// 7.8.03 ..... Invalid NumEvents in .EVE now trapped
// 25.8.03 .... Event averaging page added
// 26.8.03 .... Bugs in Template matching fixed
// 8.9.03 ..... Noise at end of average fixed
// 19.11.03 ... MaxADCValue now obtained from data file value
// 23.02.04 ... Incomplete records are beginning/end of recording
//              now excluded from detected event average
//              FP Error with very small data files fixed
// 24.03.04 ... Time constants now computed from peak - 10% of peak
//              Average is re-computed when waveform measurement polarity changed
// 15.01.05 ... Pre-trigger zero level added as plottable measurement variable
// 02.02.05 ... Analysis window now indicated by cursors
//              Duration variable added
// 08.01.06 ... Detection channel, dead time and analysis window size now saved
//              Window duration now in ms rather than sample points.
// 11.01.06 ... Range of events exported can now be set by user
// 09.06.06 ... Threshold detector running mean now preset to 1s
// 07.06.06 ... Bug which limited export event range to 1-1 fixed
// 13.08.06 ... Count-matched (selected n larged events) averaging option added
// 18.11.06 ... Decay time % can now be set by user
//              Export to WCP files now works correctly
//              Analysis cursors removed. Analysis now on whole edit display
// 28.11.06 ... Event list file now updated when events added or deleted
// 21.12.06 ... WCPRecordHeader no longer used in WCP file export
// 31.01.07 ... Waveform analysis peak and area now restricted to region defined by a-a cursors
// 08.03.07 ... T.(x%) decay can now be relative to peak, mid-point of rise, or a0 cursor
//              Zero level can be at start of record or event
//              Decaying exponential fits added
//              Scaling factors of detected event exported to WCP files now correct
// 20.08.07 ... No. of points averaged to compute baseline can now be changed
//              and can be offset from baseline point.
//              Tau Decay now computed correctly
// 17.10.07 ... Event detection criterion signal now kept within min/max limits of
//              channel. Roll-over is now avoided
// 01.05.08 ... Buffer sizes now adjusted to size of display window to avoid
//              buffer overflow errors with large display windows
// 05.06.08 ... Control panels widened to avoid waveform measurements wrapping round
//              to multiple lines
// 20.08.08 ... Buffer overflow error when exporting large events fixed
//              Exported event record limited to 65536 sample points per channel
// 16.12.08 ... Exported event record limit icreased to 1048576 total sample points
//              Event list size increased to 1000000
//              Buffers allocated using GetMem rather than in stack
// 14.04.09 ... Event export bug where first record was duplicated in all exported records
//              fixed. All buffers now allocated with GetMem rather than New
// 29.04.09 ... Out of memory bug (introduced 14/4/9) which caused crash when edit display duration changed fixed
// 08.12.09 ... Event export range now adjusted when events add/deleted
// 22.12.09 ... Detected event analysis parameters now stored in EDR file header
// 11.01.10 ... Histogram bin width, range and no. bins can be set by user
//              Exponential fit to histogram added
// 27.01.10 ... Access violation when event detector opened with data files containing
//              more than one channel fixed.
// 28.01.10 ... Access violations when Subt. Baseline Trend selected fixed.
// 15.10.10 ... Detected events now aligned at mid-point of rising phase
//              Size of rising edge detection window can be set by user

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ValEdit, RangeEdit, ScopeDisplay, ComCtrls, global, maths, fileio,
  XYPlotDisplay, ExtCtrls, ValidatedEdit, CursorLabel, HTMLLabel,
  ADCDataFile, math, CurveFitter, StrUtils, seslabio ;

const
    EventFileExtension = '.EVF' ;
    WCPFileExtension = '.WCP' ;
    MaxExportSamples = 1048576 ;
    MaxEvents = 1000000 ;
    vEventNum = 0 ;
    vTime = 1 ;
    vInterval = 2 ;
    vFrequencyAvg = 3 ;
    vFrequencyInst = 4 ;
    vPeak = 5 ;
    vArea = 6 ;
    vTRise = 7 ;
    vTDecay = 8 ;
    vTauDecay = 9 ;
    vDuration = 10 ;
    vBaseline = 11 ;
    MaxVar = 12 ;

type

  TEventAnalysis = record     // Event waveform analysis record
    YBaseline : Integer ;
    Rate : Single ;
    Peak : Single ;
    Area : Single ;
    TRise : Single ;
    TDecay : Single ;
    TauDecay : Single ;
    Duration : Single ;
    end ;

  TFilter = record            // Filter criterion record
    Use : Boolean ;
    Variable : Integer ;
    LoLimit : Single ;
    HiLimit : Single ;
    end ;

  TCursors = record
    C0 : Integer ;
    C1 : Integer ;
    Read : Integer ;
    end ;

  TEventDetFrm = class(TForm)
    Page: TPageControl;
    DetectEventsPage: TTabSheet;
    DetectGrp: TGroupBox;
    bDetect: TButton;
    bAbort: TButton;
    GroupBox8: TGroupBox;
    rbAllRecords: TRadioButton;
    rbRange: TRadioButton;
    edRange: TRangeEdit;
    CriteriaGrp: TGroupBox;
    scDisplay: TScopeDisplay;
    sbDisplay: TScrollBar;
    scDetDisplay: TScopeDisplay;
    cbChannel: TComboBox;
    Label6: TLabel;
    GroupBox1: TGroupBox;
    rbRateOfRise: TRadioButton;
    rbPatternMatch: TRadioButton;
    EditEventsPage: TTabSheet;
    scEditDisplay: TScopeDisplay;
    EditEventsGrp: TGroupBox;
    sbEditDisplay: TScrollBar;
    edEvent: TRangeEdit;
    sbEvent: TScrollBar;
    editGrp: TGroupBox;
    bInsertEvent: TButton;
    bDeleteEvent: TButton;
    scMarkDisplay: TScopeDisplay;
    XYPlotPage: TTabSheet;
    XYPlotGrp: TGroupBox;
    GroupBox5: TGroupBox;
    Label9: TLabel;
    Label1: TLabel;
    cbPlotYVar: TComboBox;
    cbPlotXVar: TComboBox;
    bNewPlot: TButton;
    GroupBox6: TGroupBox;
    rbPlotAllEvents: TRadioButton;
    rbPlotRange: TRadioButton;
    plPlot: TXYPlotDisplay;
    ExportGrp: TGroupBox;
    bExportToWCPFile: TButton;
    bSetPlotAxes: TButton;
    GroupBox2: TGroupBox;
    edThreshold: TValidatedEdit;
    Label8: TLabel;
    edTimeThreshold: TValidatedEdit;
    lbTimeThreshold: TLabel;
    bSetThresholdTo4SD: TButton;
    ModePage: TNotebook;
    rbThreshold: TRadioButton;
    Label11: TLabel;
    GroupBox7: TGroupBox;
    edRunningMeanDuration: TValidatedEdit;
    GroupBox9: TGroupBox;
    Label10: TLabel;
    edTauRise: TValidatedEdit;
    edTauDecay: TValidatedEdit;
    Label12: TLabel;
    GroupBox10: TGroupBox;
    edDeadTime: TValidatedEdit;
    GroupBox11: TGroupBox;
    bExportNonEvents: TButton;
    HistPage: TTabSheet;
    plHist: TXYPlotDisplay;
    AnalysisGrp: TGroupBox;
    meResults: TMemo;
    GroupBox3: TGroupBox;
    rbPositive: TRadioButton;
    rbNegative: TRadioButton;
    EventFilterGrp: TGroupBox;
    GroupBox4: TGroupBox;
    cbVariable: TComboBox;
    GroupBox15: TGroupBox;
    Label15: TLabel;
    Label16: TLabel;
    edLoLimit: TValidatedEdit;
    edHiLimit: TValidatedEdit;
    EventListGrp: TGroupBox;
    bLoadEventList: TButton;
    bSaveEventList: TButton;
    SaveDialog: TSaveDialog;
    OpenDialog: TOpenDialog;
    GroupBox18: TGroupBox;
    meFilterSet: TMemo;
    GroupBox14: TGroupBox;
    rbAND: TRadioButton;
    rbOR: TRadioButton;
    bDelete: TButton;
    bApply: TButton;
    bAddFilter: TButton;
    Label14: TLabel;
    HistResultsGrp: TGroupBox;
    bHistFitCurve: TButton;
    cbHistEqn: TComboBox;
    AveragePage: TTabSheet;
    AverageGrp: TGroupBox;
    GroupBox19: TGroupBox;
    rbAverageAllEvents: TRadioButton;
    rbAverageRange: TRadioButton;
    edAverageRange: TRangeEdit;
    bDoAverage: TButton;
    scAverageDisplay: TScopeDisplay;
    AverageResultsGrp: TGroupBox;
    bAverageFitCurve: TButton;
    cbAverageEqn: TComboBox;
    GroupBox20: TGroupBox;
    meAverageResults: TMemo;
    WCPFile: TADCDataFile;
    EditDisplayWidthPanel: TPanel;
    edEditDisplayWidth: TValidatedEdit;
    lbEditDisplayPoints: TLabel;
    bPlotAbort: TButton;
    edExportRange: TRangeEdit;
    Label20: TLabel;
    bAbortExport: TButton;
    GroupBox16: TGroupBox;
    ckCountMatchedAvg: TCheckBox;
    edNumCountMatchedAvg: TValidatedEdit;
    Label21: TLabel;
    CVFit: TCurveFitter;
    lbAvgFitResults: THTMLLabel;
    lbHistResults: THTMLLabel;
    GroupBox23: TGroupBox;
    edTDecayPercentage: TValidatedEdit;
    Label4: TLabel;
    edPreTrigger: TValidatedEdit;
    bAbortAverage: TButton;
    GroupBox21: TGroupBox;
    ckSubtractBaseline: TCheckBox;
    rbBaselineAtStart: TRadioButton;
    rbBaselineAtEvent: TRadioButton;
    Label3: TLabel;
    Label5: TLabel;
    edZeroNumAvg: TValidatedEdit;
    edZeroGap: TValidatedEdit;
    cbDecayFrom: TComboBox;
    Label7: TLabel;
    HistGrp: TGroupBox;
    GroupBox12: TGroupBox;
    Label17: TLabel;
    cbHistVar: TComboBox;
    ckPercentage: TCheckBox;
    ckCumulative: TCheckBox;
    edNumBins: TValidatedEdit;
    bNewHistogram: TButton;
    BinRangePanel: TPanel;
    Label13: TLabel;
    Label19: TLabel;
    Label22: TLabel;
    edBinWidth: TValidatedEdit;
    edBinsLower: TValidatedEdit;
    edBinsUpper: TValidatedEdit;
    bSetHistAxes: TButton;
    GroupBox13: TGroupBox;
    rbHistAllEvents: TRadioButton;
    rbHistRange: TRadioButton;
    edHistRange: TRangeEdit;
    meHistResults: TMemo;
    DetDisplayPanel: TPanel;
    edDetDisplayWidth: TValidatedEdit;
    bTDisplayDouble: TButton;
    bTDisplayHalf: TButton;
    Label18: TLabel;
    bDoubleEditDisplayWidth: TButton;
    bEditDisplayWidthHalve: TButton;
    Label23: TLabel;
    edRisingEdgeWindow: TValidatedEdit;
    Label24: TLabel;
    cktCursorAtDetectionPoint: TCheckBox;
    FrequencyAvgPan: TPanel;
    edAverageInterval: TValidatedEdit;
    Label2: TLabel;
    edPlotTimeRange: TRangeEdit;
    EventRangePan: TPanel;
    edPlotEventRange: TRangeEdit;
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormResize(Sender: TObject);
    procedure sbDisplayChange(Sender: TObject);
    procedure bDetectClick(Sender: TObject);
    procedure scDetDisplayCursorChange(Sender: TObject);
    procedure bAbortClick(Sender: TObject);
    procedure sbEditDisplayChange(Sender: TObject);
    procedure PageChange(Sender: TObject);
    procedure sbEventChange(Sender: TObject);
    procedure edEventKeyPress(Sender: TObject; var Key: Char);
    procedure bInsertEventClick(Sender: TObject);
    procedure bDeleteEventClick(Sender: TObject);
    procedure scEditDisplayCursorChange(Sender: TObject);
    procedure bNewPlotClick(Sender: TObject);
    procedure bExportToWCPFileClick(Sender: TObject);
    procedure scDisplayCursorChange(Sender: TObject);
    procedure bSetThresholdTo4SDClick(Sender: TObject);
    procedure bSetPlotAxesClick(Sender: TObject);
    procedure rbThresholdClick(Sender: TObject);
    procedure edTauRiseKeyPress(Sender: TObject; var Key: Char);
    procedure edThresholdKeyPress(Sender: TObject; var Key: Char);
    procedure bExportNonEventsClick(Sender: TObject);
    procedure edEditDisplayWidthKeyPress(Sender: TObject; var Key: Char);
    procedure cbChannelChange(Sender: TObject);
    procedure bNewHistogramClick(Sender: TObject);
    procedure bAddFilterClick(Sender: TObject);
    procedure bDeleteClick(Sender: TObject);
    procedure bApplyClick(Sender: TObject);
    procedure cbHistVarChange(Sender: TObject);
    procedure bLoadEventListClick(Sender: TObject);
    procedure bSaveEventListClick(Sender: TObject);
    procedure cbVariableChange(Sender: TObject);
    procedure edLoLimitKeyPress(Sender: TObject; var Key: Char);
    procedure edHiLimitKeyPress(Sender: TObject; var Key: Char);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure edAnalysisWindowKeyPress(Sender: TObject; var Key: Char);
    procedure edDeadTimeKeyPress(Sender: TObject; var Key: Char);
    procedure edRunningMeanDurationKeyPress(Sender: TObject;
      var Key: Char);
    procedure rbANDClick(Sender: TObject);
    procedure rbPositiveClick(Sender: TObject);
    procedure bHistFitCurveClick(Sender: TObject);
    procedure bSetHistAxesClick(Sender: TObject);
    procedure bDoAverageClick(Sender: TObject);
    procedure scAverageDisplayCursorChange(Sender: TObject);
    procedure rbAveragePositiveClick(Sender: TObject);
    procedure rbAverageNegativeClick(Sender: TObject);
    procedure ckSubtractBaselineClick(Sender: TObject);
    procedure bPlotAbortClick(Sender: TObject);
    procedure bAbortExportClick(Sender: TObject);
    procedure edDetDisplayWidthKeyPress(Sender: TObject; var Key: Char);
    procedure bAverageFitCurveClick(Sender: TObject);
    procedure edPreTriggerKeyPress(Sender: TObject; var Key: Char);
    procedure edTDecayPercentageKeyPress(Sender: TObject; var Key: Char);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure bAbortAverageClick(Sender: TObject);
    procedure scEditDisplayMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure scAverageDisplayMouseUp(Sender: TObject;
      Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure rbBaselineAtStartClick(Sender: TObject);
    procedure rbBaselineAtEventClick(Sender: TObject);
    procedure rbTDecayFromPeakClick(Sender: TObject);
    procedure rbTDecayFromMidRiseClick(Sender: TObject);
    procedure rbTDecayFromC0Click(Sender: TObject);
    procedure edZeroNumAvgKeyPress(Sender: TObject; var Key: Char);
    procedure edZeroGapKeyPress(Sender: TObject; var Key: Char);
    procedure cbDecayFromChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure bTDisplayDoubleClick(Sender: TObject);
    procedure bTDisplayHalfClick(Sender: TObject);
    procedure bEditDisplayWidthHalveClick(Sender: TObject);
    procedure bDoubleEditDisplayWidthClick(Sender: TObject);
    procedure edNumBinsKeyPress(Sender: TObject; var Key: Char);
    procedure edBinWidthKeyPress(Sender: TObject; var Key: Char);
    procedure edBinsLowerKeyPress(Sender: TObject; var Key: Char);
    procedure edBinsUpperKeyPress(Sender: TObject; var Key: Char);
    procedure edRisingEdgeWindowKeyPress(Sender: TObject; var Key: Char);
    procedure cbPlotXVarChange(Sender: TObject);
    procedure cbPlotYVarChange(Sender: TObject);
    procedure edAverageIntervalKeyPress(Sender: TObject; var Key: Char);
  private
    { Private declarations }


    ADC : PSmallIntArrayDyn ;      // A/D sample buffer
    DetBuf : PSmallIntArrayDyn ;   // Detection criterion buffer
    EditBuf : PSmallIntArrayDyn ;   // Edit display buffer
    AvgBuf : PSmallIntArrayDyn ;   // Averaged event buffer
    NumEventsAveraged : Integer ;
    BaselineCursor : Integer ;
    ThresholdCursor : Integer ;
    DisplayCursor : Integer ;
    EditCursor : Integer ;
    EditC0Cursor : Integer ;
    EditC1Cursor : Integer ;
    OldEditC0CursorPos : Integer ;
    OldEditC1CursorPos : Integer ;
    AverageCursor : Integer ;
    AverageT0Cursor : Integer ;
    AverageC0Cursor : Integer ;
    AverageC1Cursor : Integer ;
    AvgStartEvent : Integer ;
    AvgEndEvent : Integer ;
    AvgEventScan : Integer ;
    AvgNumScans : Integer ;
    AvgPreScans : Integer ;
    OldAvgC0CursorPos : Integer ;
    OldAvgC1CursorPos : Integer ;

    BuffersAllocated : Boolean ;
    PlotAvailable : Boolean  ;             // X-Y plot available
    HistAvailable : Boolean  ;             // Histogram available
    xUnits : string ;
    yUnits : string ;

    Hist : THistogram ;         // Histogram data structure
    HistResults : TStringList ; // Histogram fitting results
    HistCurs : TCursors ;       // Histogram cursors

    Events : Array[0..MaxEvents-1] of Integer ;        // Event list
    YBaseline : Array[0..MaxEvents-1] of Integer ; // Event baselines
    YBaseline2 : Array[0..MaxEvents-1] of Single ; // Event baselines
    BaselineSplinesAvailable : Boolean ;

    NumEvents : Integer ;                        // No. of events in list
    EventAnalysisFile : Integer ;                // Event analysis file handle
    EventAnalysisFileUpdateRequired : Boolean ;  // Update required flag
    VarNames : Array[0..MaxVar] of string ;

    Filters : Array[0..MaxVar] of TFilter ;

    // Running mean variables used by "threshold" detection method
    NumSamplesInRunningMean : Single ;
    RunningMean : Single ;
    AbortFlag : Boolean ;
    ComputationInProgress : Boolean ;

    procedure HeapBuffers( Operation : THeapBufferOp ) ;
    procedure SetupDetector ;
    function RateofRise(
              var InBuf : Array of SmallInt ;
              StartAtSample : Integer ;
              NumPoints : Integer ;
              var OutBuf : Array of SmallInt
              ) : Integer ;

    function Threshold(
              var InBuf : Array of SmallInt ;
              StartAtSample : Integer ;
              NumPoints : Integer ;
              var InitialiseRunningMean : Boolean ;
              var OutBuf : Array of SmallInt ) : Integer ;

    function  MatchTemplate(
              var InBuf : Array of SmallInt ;
              StartAtSample : Integer ;
              NumPoints : Integer ;
              var OutBuf : Array of SmallInt
              ) : Integer ;

    function  FindMidPointOfRise(
              iSample : Integer
              ) : Integer ;

    procedure DisplayEvent;
    procedure LoadEventList ;
    procedure SaveEventList ;

    procedure DisplayEditRecord ;

    procedure AnalyseEvent(
              iEvent : Integer ;          // Event # to be analysed [IN]
              var Event : TEventAnalysis  // Analysis results record [OUT]
              ) ;
    procedure AnalyseAverage ;

    function PreEventBaselineLevel(
             var Buf : Array of SmallInt ; // Data buffer
             EventScan : Integer ;     // Scan # of start of event in Buf
             NumScans : Integer      // No. of scans in Buf
             ) : Integer ;

    procedure SubtractBaseline(
              iEvent : Integer  ;
              var Buf : Array of SmallInt ;
              BufStartScan : Integer ;
              NumScans : Integer
              ) ;
    procedure CalculateBaselineSplines ;

    procedure UpdateEventAnalysisFile ;
    function CalculateVariables(
          var iEvent : Integer ;                // Event number to be analysed [In]
          XVarType : Integer ;              // Variable selected for X axis [IN]
          YVarType : Integer ;              // Variable selected for Y axis [IN]
          var X : Single ;                  // X axis variable value
          var Y : Single                    // Y axis variable value
          ) : Boolean ;                     // TRUE = valid variable returned

function CalculateVariablesInInterval(
          StartOfInterval : Integer ;       // Sample at which interval ends [IN]
          EndOfInterval : Integer ;         // Sample at which interval ends [IN]
          XVarType : Integer ;              // Variable selected for X axis [IN]
          YVarType : Integer ;              // Variable selected for Y axis [IN]
          var iEvent : Integer ;            // Event number [OUT]
          var X : Single ;                  // X axis variable value
          var Y : Single                    // Y axis variable value
          ) : Boolean ;                     // TRUE = valid variable returned


     procedure CreateAmplitudeSortedEventList(
               StartEvent : Integer ;
               EndEvent : Integer ;
               var SortedEvents : Array of Integer ;
               var NumSortedEvents : Integer
               ) ;

    function GetVariableUnits( VarNum : Integer ) : String ;


    procedure UpdateFilterSet( Filters : Array of TFilter ) ;
    procedure UpdateEditEventPage ;

  public
    { Public declarations }
    procedure NewFile ;
    procedure DisplayRecord ;
    function IsClipboardDataAvailable : Boolean ;
    procedure CopyDataToClipboard ;
    procedure CopyImageToClipboard ;
    procedure PrintDisplay ;
    procedure ZoomOutAll ;
    procedure ZoomIn( Chan : Integer ) ;
    procedure ZoomOut( Chan : Integer ) ;
    procedure ChangeDisplayGrid ;

  end;

var
  EventDetFrm: TEventDetFrm;

implementation

uses Mdiform, shared, Printrec, Printgra, Setaxes , Setfitpa,
  SetFitPars1Unit;

{$R *.DFM}
const
    cTDecayFromPeak = 0 ;
    cTDecayFromMidRise = 1 ;
    cTDecayFromA0 = 2 ;


procedure TEventDetFrm.HeapBuffers( Operation : THeapBufferOp ) ;
{ -----------------------------------------------
  Allocate/deallocation dynamic buffers from heap
  -----------------------------------------------}
var
    TempName,TempPath : Array[0..255] of Char ;
begin
     case Operation of
          Allocate : begin
             if not BuffersAllocated then begin
                Hist := THistogram.Create ;
                HistResults := TStringList.Create ;

                // Get path to temporary file directory }
                GetTempPath( High(TempPath), TempPath ) ;
                // Create a temporary file name
                GetTempFileName( TempPath, PChar('EDR'), 0, TempName ) ;
                EventAnalysisFile := FileCreate( String(TempName) ) ;
                if EventAnalysisFile < 0 then
                   ShowMessage('Cannot open detected events file!') ;
                BuffersAllocated := True ;
                end ;
             end ;
          Deallocate : begin
             if BuffersAllocated then begin
                Hist.Free ;
                HistResults.Free ;
                if EventAnalysisFile >= 0 then FileClose(EventAnalysisFile) ;
                BuffersAllocated := False ;
                end ;
             end ;
          end ;
     end ;


procedure TEventDetFrm.FormShow(Sender: TObject);
{ --------------------------------------
  Initialisations when form is displayed
  --------------------------------------}
var
   ch,i : Integer ;
begin

     ClientWidth := Page.Left + Page.Width + 10 ;
     ClientHeight := Page.Top + Page.Height + 10 ;

     // Ensure form opens on Detect Events page
     Page.ActivePage := DetectEventsPage ;

     HeapBuffers( Allocate ) ;

     { Set block of CDR file to be scanned }
     edRange.LoLimit := 0.0 ;
     edRange.LoValue := 0.0 ;
     edRange.HiValue := CdrFH.RecordDuration ;
     edRange.HiLimit := CdrFH.RecordDuration ;

     { Fill channel selection list }
     cbChannel.Clear ;
     for ch := 0 to CdrFH.NumChannels-1 do
          cbChannel.items.add( format('Ch.%d %s',[ch,Channel[ch].ADCName]) ) ;
     cbChannel.ItemIndex := Min(Max(Settings.EventDetector.Channel,0),CdrFH.NumChannels-1) ;

     VarNames[vEventNum] := 'Event No.' ;
     VarNames[vTime] := 'Time' ;
     VarNames[vInterval] := 'Interval ' ;
     VarNames[vFrequencyAvg] := 'Frequency (avg.)' ;
     VarNames[vFrequencyInst] := 'Frequency (inst.)' ;
     VarNames[vPeak] := 'Peak' ;
     VarNames[vArea] := 'Area' ;
     VarNames[vTRise] := 'T.rise' ;
     VarNames[vTDecay] := 'T(90%)' ;
     VarNames[vTauDecay] := 'Tau(decay)' ;
     VarNames[vDuration] := 'Duration' ;
     VarNames[vBaseline] := 'Zero Baseline' ;

     // X/Y Plot variables
     cbPlotYVar.Clear ;
     cbPlotYVar.Items.AddObject( VarNames[vEventNum], TObject(vEventNum) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vTime], TObject(vTime) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vInterval], TObject(vInterval) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vFrequencyAvg], TObject(vFrequencyAvg) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vFrequencyInst], TObject(vFrequencyInst) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vPeak], TObject(vPeak) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vArea], TObject(vArea) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vTRise], TObject(vTRise) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vTDecay], TObject(vTDecay) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vTauDecay], TObject(vTauDecay) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vDuration], TObject(vDuration) ) ;
     cbPlotYVar.Items.AddObject( VarNames[vBaseline], TObject(vBaseline) ) ;
     cbPlotYVar.ItemIndex := 2 ;

     // X/Y Plot variables
     cbPlotXVar.Clear ;
     cbPlotXVar.Items.AddObject( VarNames[vEventNum], TObject(vEventNum) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vTime], TObject(vTime) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vInterval], TObject(vInterval) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vPeak], TObject(vPeak) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vArea], TObject(vArea) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vTRise], TObject(vTRise) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vTDecay], TObject(vTDecay) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vTauDecay], TObject(vTauDecay) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vDuration], TObject(vDuration) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vBaseline], TObject(vBaseline) ) ;
     cbPlotXVar.ItemIndex := 0 ;


     // Event filter criterion variable list
     cbVariable.Clear ;
     cbVariable.Items.AddObject( VarNames[vTime], TObject(vTime) ) ;
     cbVariable.Items.AddObject( VarNames[vInterval], TObject(vInterval) ) ;
     cbVariable.Items.AddObject( VarNames[vPeak], TObject(vPeak) ) ;
     cbVariable.Items.AddObject( VarNames[vArea], TObject(vArea) ) ;
     cbVariable.Items.AddObject( VarNames[vTRise], TObject(vTRise) ) ;
     cbVariable.Items.AddObject( VarNames[vTDecay], TObject(vTDecay) ) ;
     cbVariable.Items.AddObject( VarNames[vTauDecay], TObject(vTauDecay) ) ;
     cbVariable.Items.AddObject( VarNames[vDuration], TObject(vDuration) ) ;
     cbVariable.Items.AddObject( VarNames[vBaseline], TObject(vBaseline) ) ;
     cbVariable.ItemIndex := 0 ;

     // Histogram variables
     cbHistVar.Clear ;
     cbHistVar.Items.AddObject( VarNames[vInterval], TObject(vInterval) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vFrequencyAvg], TObject(vFrequencyAvg) ) ;
     cbPlotXVar.Items.AddObject( VarNames[vFrequencyInst], TObject(vFrequencyInst) ) ;
     cbHistVar.Items.AddObject( VarNames[vPeak], TObject(vPeak) ) ;
     cbHistVar.Items.AddObject( VarNames[vArea], TObject(vArea) ) ;
     cbHistVar.Items.AddObject( VarNames[vTRise], TObject(vTRise) ) ;
     cbHistVar.Items.AddObject( VarNames[vTDecay], TObject(vTDecay) ) ;
     cbHistVar.Items.AddObject( VarNames[vTauDecay], TObject(vTauDecay) ) ;
     cbHistVar.Items.AddObject( VarNames[vDuration], TObject(vDuration) ) ;
     cbHistVar.Items.AddObject( VarNames[vBaseline], TObject(vBaseline) ) ;
     cbHistVar.ItemIndex := 0 ;

     { Initialise histogram display cursors }
     plHist.ClearVerticalCursors ;
     HistCurs.C0 := plHist.AddVerticalCursor( clGray,'' ) ;
     HistCurs.C1 := plHist.AddVerticalCursor( clGray,'' ) ;
     plHist.LinkVerticalCursors( HistCurs.C0, HistCurs.C1 );

     // Create list of curves that can be fitted to histogram
     cbHistEqn.Clear ;
     cbHistEqn.Items.AddObject( 'None', TObject(None)) ;
     cbHistEqn.Items.AddObject( 'Gaussian', TObject(Gaussian)) ;
     cbHistEqn.Items.AddObject( '2 Gaussians', TObject(Gaussian2)) ;
     cbHistEqn.Items.AddObject( '3 Gaussians', TObject(Gaussian3)) ;
     cbHistEqn.Items.AddObject( 'Exponential', TObject(DecayingExp)) ;
     cbHistEqn.Items.AddObject( '2 Exponentials', TObject(DecayingExp2)) ;

     { Set initial  equation to None }
     cbHistEqn.ItemIndex := 0 ;
     lbHistResults.Caption := '' ;
     HistCurs.Read := plHist.AddVerticalCursor( clGreen, '?r') ;

     // Create list of curves that can be fitted to average
     cbAverageEqn.Clear ;
     cbAverageEqn.Items.AddObject( 'None', TObject(None)) ;
     cbAverageEqn.Items.AddObject( 'Linear', TObject(Linear)) ;
     cbAverageEqn.Items.AddObject( 'Exponential', TObject(Exponential)) ;
     cbAverageEqn.Items.AddObject( '2 Exponentials', TObject(Exponential2)) ;
     cbAverageEqn.Items.AddObject( '3 Exponentials', TObject(Exponential3)) ;
     cbAverageEqn.Items.AddObject( 'Decay Exp.', TObject(DecayingExp)) ;
     cbAverageEqn.Items.AddObject( '2 Decay Exps.', TObject(DecayingExp2)) ;
     cbAverageEqn.Items.AddObject( '3 Decay Exps.', TObject(DecayingExp3)) ;
     { Set initial  equation to None }
     cbAverageEqn.ItemIndex := 0 ;
     lbAvgFitResults.Caption := '' ;

     // Disable all selection filters
     for i := 0 to High(Filters) do Filters[i].Use := False ;
     UpdateFilterSet( Filters ) ;

     // Default no. of points in display
     edDetDisplayWidth.Scale := CDRFH.dt ;
     edDetDisplayWidth.Value := Round(5000.0) ;
     edEditDisplayWidth.Scale := edDetDisplayWidth.Scale ;
     edEditDisplayWidth.Value := edDetDisplayWidth.Value ;

     { Initialise continuous and detected event displays }
     NewFile ;

     { Force a re-size to get controls in right/size place }
     ReSize ;

     { Display records }
     DisplayRecord ;

     end;


procedure TEventDetFrm.NewFile ;
{ ------------------------------------------------
  Initialise display when data file changed
  ------------------------------------------------}
var
   i,ch,Temp : Integer ;
   NPAvgBuf : Integer ;
begin

     // Analysis window duration
     if Settings.EventDetector.AnalysisWindow <= 0 then Settings.EventDetector.AnalysisWindow := 512 ;
     edEditDisplayWidth.Value := Settings.EventDetector.AnalysisWindow ;

     edPreTrigger.Value := Settings.EventDetector.PreTriggerFraction ;

     // Dead time
     edDeadTime.Value := Settings.EventDetector.DeadTime ;

     // Rising edge window
     edRisingEdgeWindow.Scale := 1000.0*CDRFH.dt ; // Scale from samples -> ms
     edRisingEdgeWindow.Units := 'ms' ;
     edRisingEdgeWindow.Value := Settings.EventDetector.RisingEdgeWindow ;

     // Detection channel
     cbChannel.ItemIndex := Max(Min(Settings.EventDetector.Channel,CDRFH.NumChannels-1),0) ;

     // Analysis polarity
     rbPositive.Checked := Settings.EventDetector.PositivePeaks ;
     rbNegative.Checked := not Settings.EventDetector.PositivePeaks ;

     rbBaselineAtStart.Checked := Settings.EventDetector.BaselineAtStart ;
     rbBaselineAtEvent.Checked := not Settings.EventDetector.BaselineAtStart ;
     ckSubtractBaseline.Checked := Settings.EventDetector.SubtractBaseline ;
     edZeroNumAvg.Value := Max(Settings.EventDetector.NumBaselinePoints,2) ;
     edZeroGap.Value := Max(Settings.EventDetector.NumBaselineGap,0) ;
     edTDecayPercentage.Value := Min(Max(Settings.EventDetector.TDecayPercent,0.0),100.0) ;
     cbDecayFrom.ItemIndex := Min(Max(Settings.EventDetector.TDecayFrom,0),
                              cbDecayFrom.Items.Count-1) ;

     Temp := Round(edDetDisplayWidth.Value) ;
     edDetDisplayWidth.Scale := CDRFH.dt ;
     edDetDisplayWidth.Value := Temp ;

     { Continuous record display channel }
     scDisplay.MaxADCValue := Channel[0].ADCMaxValue ;
     scDisplay.MinADCValue := -Channel[0].ADCMaxValue - 1 ;
     scDisplay.MaxPoints := Round(edDetDisplayWidth.Value) ;
     scDisplay.NumPoints := scDisplay.MaxPoints ;
     scDisplay.NumChannels := CdrFH.NumChannels ;
     scDisplay.xMin := 0 ;
     scDisplay.xMax := scDisplay.NumPoints - 1  ;
     scDisplay.DisableChannelVisibilityButton := True ;

     if ADC <> Nil then FreeMem(ADC) ;
     GetMem( ADC, scDisplay.MaxPoints*scDisplay.NumChannels*4 ) ;
     scDisplay.SetDataBuf( ADC ) ;

     { Set display scaling information }
     for ch := 0 to scDisplay.NumChannels-1 do begin
         scDisplay.ChanUnits[ch] := Channel[ch].ADCUnits ;
         scDisplay.ChanName[ch] := Channel[ch].ADCName ;
         scDisplay.yMin[ch] := Channel[ch].yMin ;
         scDisplay.yMax[ch] := Channel[ch].yMax ;
         scDisplay.ChanScale[ch] := Channel[ch].ADCScale ;
         scDisplay.ChanUnits[ch] := Channel[ch].ADCUnits ;
         scDisplay.ChanZero[ch] := Channel[ch].ADCZero ;
         scDisplay.ChanOffsets[ch] := Channel[ch].ChannelOffset ;
         scDisplay.ChanColor[ch] := clBlue ;
         if ch = cbChannel.ItemIndex then scDisplay.ChanVisible[ch] := True
                                     else scDisplay.ChanVisible[ch] := False ;
         end ;
     scDisplay.TScale := CdrFH.dt*Settings.TScale ;
     scDisplay.TFormat := ' %.6g ' + Settings.TUnits ;

     { Create display cursors }
     scDisplay.ClearHorizontalCursors ;
     BaselineCursor := scDisplay.AddHorizontalCursor( cbChannel.ItemIndex,
                                                      clgray,
                                                      True,
                                                      'z' ) ;
     scDisplay.HorizontalCursors[0] := Channel[cbChannel.ItemIndex].ADCZero ;

     scDisplay.ClearVerticalCursors ;

     // Set upper limit of display slider bar range
     DisplayCursor := scDisplay.AddVerticalCursor( -1, clGreen, '?r' ) ;
     sbDisplay.Max := (CdrFH.NumSamplesInFile div CdrFH.NumChannels) -1 ;
     sbDisplay.LargeChange := scDisplay.MaxPoints div 4 ;

     { Event detection function display channel }
     scDetDisplay.MaxADCValue := Channel[0].ADCMaxValue ;
     scDetDisplay.MinADCValue := -Channel[0].ADCMaxValue - 1 ;
     scDetDisplay.MaxPoints := scDisplay.MaxPoints ;
     scDetDisplay.NumPoints := scDetDisplay.MaxPoints ;
     scDetDisplay.NumChannels := 1 ;
     scDetDisplay.xMin := 0 ;
     scDetDisplay.xMax := scDetDisplay.NumPoints - 1  ;
     scDetDisplay.DisableChannelVisibilityButton := True ;

     // Allocate detection buffer
     if DetBuf <> Nil then FreeMem( DetBuf ) ;
     GetMem( DetBuf, scDetDisplay.MaxPoints*2 ) ;
     scDetDisplay.SetDataBuf( DetBuf ) ;

     { Set display scaling information }
     scDetDisplay.ChanUnits[0] := Channel[cbChannel.ItemIndex].ADCUnits ;
     scDetDisplay.ChanName[0] := 'Det. Criterion' ;
     scDetDisplay.yMin[0] := Channel[cbChannel.ItemIndex].yMin ;
     scDetDisplay.yMax[0] := Channel[cbChannel.ItemIndex].yMax ;
     scDetDisplay.ChanScale[0] := Channel[cbChannel.ItemIndex].ADCScale ;
     scDetDisplay.ChanUnits[0] := Channel[cbChannel.ItemIndex].ADCUnits ;
     scDetDisplay.ChanZero[0] := Channel[cbChannel.ItemIndex].ADCZero ;
     scDetDisplay.ChanOffsets[0] := 0 ;
     scDetDisplay.ChanColor[0] := clBlue ;
     scDetDisplay.ChanVisible[0] := True ;
     scDetDisplay.TScale := CdrFH.dt*Settings.TScale ;
     scDetDisplay.TFormat := ' %.6g ' + Settings.TUnits ;

     { Create detection threshold cursor }
     scDetDisplay.ClearHorizontalCursors ;
     ThresholdCursor := scDetDisplay.AddHorizontalCursor( 0,clgray,True,'threshold' ) ;

     scDetDisplay.ClearVerticalCursors ;
     DisplayCursor := scDetDisplay.AddVerticalCursor( -1, clGReen, '?r' ) ;

     // Set up display on Edit Events panel
     // -----------------------------------

     Temp := Round(edEditDisplayWidth.Value) ;
     edEditDisplayWidth.Scale := 1000.*CDRFH.dt ;
     edEditDisplayWidth.Value := Temp ;

     scEditDisplay.MaxADCValue := Channel[0].ADCMaxValue ;
     scEditDisplay.MinADCValue := -Channel[0].ADCMaxValue - 1 ;
     scEditDisplay.MaxPoints := Round(edEditDisplayWidth.Value) ;
     scEditDisplay.NumPoints := scEditDisplay.MaxPoints ;
     scEditDisplay.NumChannels := CdrFH.NumChannels ;
     scEditDisplay.xMin := 0 ;
     scEditDisplay.xMax := scEditDisplay.NumPoints - 1  ;
     scEditDisplay.DisableChannelVisibilityButton := True ;

     if EditBuf <> Nil then FreeMem( EditBuf ) ;
     GetMem( EditBuf, scEditDisplay.MaxPoints*scEditDisplay.NumChannels*2 ) ;
     scEditDisplay.SetDataBuf( EditBuf ) ;

     { Set display scaling information }
     for ch := 0 to scEditDisplay.NumChannels-1 do begin
         scEditDisplay.ChanUnits[ch] := Channel[ch].ADCUnits ;
         scEditDisplay.ChanName[ch] := Channel[ch].ADCName ;
         scEditDisplay.yMin[ch] := Channel[ch].yMin ;
         scEditDisplay.yMax[ch] := Channel[ch].yMax ;
         scEditDisplay.ChanScale[ch] := Channel[ch].ADCScale ;
         scEditDisplay.ChanUnits[ch] := Channel[ch].ADCUnits ;
         scEditDisplay.ChanZero[ch] := Channel[ch].ADCZero ;
         scEditDisplay.ChanOffsets[ch] := Channel[ch].ChannelOffset ;
         scEditDisplay.ChanColor[ch] := clBlue ;
         if ch = cbChannel.ItemIndex then scEditDisplay.ChanVisible[ch] := True
                                     else scEditDisplay.ChanVisible[ch] := False ;
         end ;
     scEditDisplay.TScale := CdrFH.dt*Settings.TScale ;
     scEditDisplay.TFormat := ' %.6g ' + Settings.TUnits ;

     { Create display cursors }
     scEditDisplay.ClearVerticalCursors ;
     // Readout cursor
     EditC0Cursor := scEditDisplay.AddVerticalCursor( -1, clGray, 'a0' ) ;
     EditC1Cursor := scEditDisplay.AddVerticalCursor( -1, clGray, 'a1' ) ;
     scEditDisplay.LinkVerticalCursors( EditC0Cursor, EditC1Cursor );
     scEditDisplay.VerticalCursors[EditC0Cursor] := 2 ;
     scEditDisplay.VerticalCursors[EditC1Cursor] := scEditDisplay.NumPoints-2 ;
     EditCursor := scEditDisplay.AddVerticalCursor( -1, clGreen, '?r' ) ;

     scEditDisplay.ClearHorizontalCursors ;
     // Baseline cursor
     scEditDisplay.AddHorizontalCursor( cbChannel.ItemIndex, clgray, True, 'z' ) ;

     { Set display scaling information }
     scMarkDisplay.MaxADCValue := 1 ; //Channel[0].ADCMaxValue ;
     scMarkDisplay.MinADCValue := 0 ;//-Channel[0].ADCMaxValue - 1 ;
     scMarkDisplay.MaxPoints := Round(edEditDisplayWidth.Value) ;
     scMarkDisplay.NumPoints := scMarkDisplay.MaxPoints ;
     scMarkDisplay.xMin := 0 ;
     scMarkDisplay.xMax := scMarkDisplay.NumPoints - 1  ;
     scMarkDisplay.NumChannels := 1 ;
     scMarkDisplay.ChanUnits[0] := '' ;
     scMarkDisplay.ChanName[0] := 'Det.' ;
     scMarkDisplay.yMin[0] := 0 ;
     scMarkDisplay.yMax[0] := 1 ;
     scMarkDisplay.ChanScale[0] := 1.0 ;
     scMarkDisplay.ChanZero[0] := 0 ;
     scMarkDisplay.ChanOffsets[0] := 0 ;
     scMarkDisplay.ChanColor[0] := clBlue ;
     scMarkDisplay.ChanVisible[0] := True ;
     scMarkDisplay.TScale := CdrFH.dt*Settings.TScale ;
     scMarkDisplay.TFormat := ' %.6g ' + Settings.TUnits ;
     scMarkDisplay.DisableChannelVisibilityButton := True ;

     // Set upper limit of slider bar
     sbEditDisplay.Max := sbDisplay.Max ;
     sbEditDisplay.LargeChange := scEditDisplay.MaxPoints div 4 ;

     edTauRise.LoLimit := CDRFH.dt ;
     edTauDecay.LoLimit := CDRFH.dt ;

     // Open an existing event list list or create a new one
     LoadEventList ;

     // Set up event detector controls
     SetupDetector ;

     // Set X/Y plot available flag to no plot
     { Create X/Y plot cursor }
     plPlot.ClearVerticalCursors ;
     plPlot.AddVerticalCursor( clGreen, '?r' ) ;
     PlotAvailable := False ;

     // Set histogram available flag to none
     { Create X/Y plot cursor }
     HistAvailable := False ;

     { Averaged record display channel }
     scAverageDisplay.MaxADCValue := Channel[0].ADCMaxValue ;
     scAverageDisplay.MinADCValue := -Channel[0].ADCMaxValue - 1 ;
     scAverageDisplay.MaxPoints := Round(edEditDisplayWidth.Value) ;
     scAverageDisplay.NumPoints := scAverageDisplay.MaxPoints ;
     scAverageDisplay.NumChannels := CdrFH.NumChannels ;
     scAverageDisplay.xMin := 0 ;
     scAverageDisplay.xMax := scAverageDisplay.NumPoints - 1  ;
     scAverageDisplay.DisableChannelVisibilityButton := True ;

     // Create averages buffer
     if AvgBuf <> Nil then FreeMem(AvgBuf) ;
     NPAvgBuf := scAverageDisplay.MaxPoints*scAverageDisplay.NumChannels ;
     GetMem( AvgBuf, NPAvgBuf*2 ) ;
     // Clear average buffer
     for i := 0 to NPAvgBuf-1 do AvgBuf^[i] := 0 ;
     NumEventsAveraged := 0 ;

     scAverageDisplay.SetDataBuf( AvgBuf ) ;

     { Set display scaling information }
     for ch := 0 to scAverageDisplay.NumChannels-1 do begin
         scAverageDisplay.ChanUnits[ch] := Channel[ch].ADCUnits ;
         scAverageDisplay.ChanName[ch] := Channel[ch].ADCName ;
         scAverageDisplay.yMin[ch] := Channel[ch].yMin ;
         scAverageDisplay.yMax[ch] := Channel[ch].yMax ;
         scAverageDisplay.ChanScale[ch] := Channel[ch].ADCScale ;
         scAverageDisplay.ChanUnits[ch] := Channel[ch].ADCUnits ;
         scAverageDisplay.ChanZero[ch] := Channel[ch].ADCZero ;
         scAverageDisplay.ChanOffsets[ch] := Channel[ch].ChannelOffset ;
         scAverageDisplay.ChanColor[ch] := clBlue ;
         if ch = cbChannel.ItemIndex then scAverageDisplay.ChanVisible[ch] := True
                                     else scAverageDisplay.ChanVisible[ch] := False ;
         end ;
     scAverageDisplay.TScale := CdrFH.dt*Settings.TScale ;
     scAverageDisplay.TFormat := ' %.6g ' + Settings.TUnits ;

     { Create display cursors }
     // Readout cursor
     scAverageDisplay.ClearVerticalCursors ;

     AverageT0Cursor := scAverageDisplay.AddVerticalCursor( -1, clGray, 't0' ) ;
     AverageC0Cursor := scAverageDisplay.AddVerticalCursor( -1, clGray, 'a0' ) ;
     AverageC1Cursor := scAverageDisplay.AddVerticalCursor( -1, clGray, 'a1' ) ;
     scAverageDisplay.LinkVerticalCursors( AverageC0Cursor, AverageC1Cursor );
     AverageCursor := scAverageDisplay.AddVerticalCursor( -1, clGreen, '?r' ) ;
     // Baseline cursor
     scAverageDisplay.ClearHorizontalCursors ;
     scAverageDisplay.AddHorizontalCursor( cbChannel.ItemIndex, clgray, True, 'z' ) ;

     scAverageDisplay.VerticalCursors[AverageCursor] := scAverageDisplay.NumPoints div 2 ;
     scAverageDisplay.VerticalCursors[AverageT0Cursor] := Round(scAverageDisplay.NumPoints*edPreTrigger.Value) ;
     scAverageDisplay.VerticalCursors[AverageC0Cursor] := scAverageDisplay.VerticalCursors[AverageT0Cursor]
                                                          + 1 ;
     scAverageDisplay.VerticalCursors[AverageC1Cursor] := scAverageDisplay.NumPoints - 3 ;

     // Event counting interval for average frequency plot
     edAverageInterval.Value := Settings.EventDetector.AvgFrequencyInterval ;

     // Set display calibration grid
     ChangeDisplayGrid ;

     // Indicate that event waveform analysis file is out of date
     EventAnalysisFileUpdateRequired := True ;
     BaselineSplinesAvailable := False ;
     AbortFlag := False ;
     ComputationInProgress := False ;
     BinRangePanel.Visible := False ;

     end ;


procedure TEventDetFrm.DisplayRecord ;
{ ---------------------------------------------
  Display currently selected block of data file
  ---------------------------------------------}
var
   InitialiseRunningMean : Boolean ;
begin

   if ADC = Nil then Exit ;
   if DetBuf = Nil then Exit ;

   scDisplay.xOffset := sbDisplay.Position ;

   if rbThreshold.Checked then begin
      InitialiseRunningMean := True ;
      scDisplay.NumPoints := Threshold( ADC^,
                                        sbDisplay.Position,
                                        scDisplay.MaxPoints,
                                        InitialiseRunningMean,
                                        DetBuf^ )
      end
   else if rbRateOfRise.Checked then begin
      scDisplay.NumPoints := RateOfRise( ADC^,
                  sbDisplay.Position,
                  scDisplay.MaxPoints,
                  DetBuf^ ) ;
      end
   else begin
      scDisplay.NumPoints := MatchTemplate( ADC^,
                                            sbDisplay.Position,
                                            scDisplay.MaxPoints,
                                            DetBuf^ ) ;
      end ;

   scDisplay.SetDataBuf( ADC ) ;
   scDisplay.Invalidate ;

   scDetDisplay.SetDataBuf( DetBuf ) ;
   scDetDisplay.NumPoints := scDisplay.NumPoints ;
   scDetDisplay.Invalidate ;

   end;


function  TEventDetFrm.RateofRise(
          var InBuf : Array of SmallInt ;  // Input buffer
          StartAtSample : Integer ;        // Sample to start at
          NumPoints : Integer ;            // Number of A/D sample points
          var OutBuf : Array of SmallInt   // Rate
          ) : Integer ;
{ ---------------------------------
  Calculate rate of rise of signal
  ---------------------------------}
const
   jLow = -2 ;
   jHigh = 2 ;
var
   i,j,k,iOffset,PreStartAtSample : Integer ;
   A : Array[jLow..jHigh] of Single ;
   Diff : Single ;
   ChannelOffset : Integer ;
begin

   // Read A/D sample data from file (with pre-start samples)
   PreStartAtSample := Max( StartAtSample + jLow,0 ) ;
   ReadCDRBuffer(CdrFH,PreStartAtSample,InBuf,NumPoints-jLow+jHigh) ;

   for j := jLow to jHigh do A[j] := j ;

   iOffset := (StartAtSample - PreStartAtSample) ;
   ChannelOffset := Channel[cbChannel.ItemIndex].ChannelOffset ;
   for i := iOffset to iOffset + (NumPoints-1) do begin
       Diff := 0.0 ;
       for j := jLow to jHigh do begin
           k := Min(Max(i+j,0),iOffset+NumPoints-1+jHigh)*CdrFH.NumChannels +
                ChannelOffset ;
           Diff := Diff + A[j]*InBuf[k] ;
           end ;
       OutBuf[i-iOffset] := Round(Min(Max(
                                  Diff,-Channel[0].ADCMaxValue-1),Channel[0].ADCMaxValue)) ;
       end ;

   // Read A/D sample data to be displayed
   Result := ReadCDRBuffer(CdrFH,StartAtSample,InBuf,NumPoints) ;

   end ;


function  TEventDetFrm.Threshold(
          var InBuf : Array of SmallInt ;  // Input buffer
          StartAtSample : Integer ;        // Sample to start at
          NumPoints : Integer ;            // Number of A/D sample points
          var InitialiseRunningMean : Boolean ;//
          var OutBuf : Array of SmallInt   // Rate
          ) : Integer ;
{ ------------------------------
  Calculate detection threshold
  ------------------------------}
var
   i,j : Integer ;
begin

   NumSamplesInRunningMean := edRunningMeanDuration.Value / CdrFH.dt ;

   // Read A/D sample data from file
   Result := ReadCDRBuffer(CdrFH,StartAtSample,ADC^,NumPoints) ;

   // Initialise running mean (if required)
   if InitialiseRunningMean then begin
      RunningMean := ADC^[Channel[cbChannel.ItemIndex].ChannelOffset] ;
      InitialiseRunningMean := False ;
      end ;

   j := Channel[cbChannel.ItemIndex].ChannelOffset ;
   for i := 0 to NumPoints-1 do begin

       // Difference between signal and running mean zero level
       OutBuf[i] := Round(Min(Max( (ADC^[j] - RunningMean),
                    -Channel[0].ADCMaxValue-1),Channel[0].ADCMaxValue)) ;

       // Update running mean baseline
       RunningMean := ((NumSamplesInRunningMean*RunningMean)+ ADC^[j])
                               /(NumSamplesInRunningMean + 1.0 ) ;

       j := j + CdrFH.NumChannels ;

       end ;
   end ;


function  TEventDetFrm.MatchTemplate(
          var InBuf : Array of SmallInt ;  // Input buffer
          StartAtSample : Integer ;        // Sample to start at
          NumPoints : Integer ;            // Number of A/D sample points
          var OutBuf : Array of SmallInt   // Rate
          ) : Integer ;
{ ------------------------------------------------
  Calculate template matching detection criterion
  ------------------------------------------------}
var
   i,j,k,PreStartAtSample : Integer ;
   FirstTemplateSample,LastTemplateSample,NumTemplateSamples : Integer ;
   t : Double ;
   NumRead : Integer ;
   Y,SumY,SumYSq,SumTemplate,SumTemplateSq,SumYxTemplate : Double ;
   TauRise,TauDecay,Amplitude,Scale,Offset,SSE,StandardError : Double ;
   InitialiseTemplate : Boolean ;
   Template : Array[-1024..2048] of Single ;
   NumScansPerTau : Integer ;
   ChannelOffset : Integer ;
   iIn,iOut : Integer ;
   Denom : Double ;
begin

   // Rise and decay time constants of EPSC template
   TauRise := edTauRise.Value ;
   TauDecay := edTauDecay.Value ;

   // Set last 75% of template to be 3 x decay time constant
   NumScansPerTau := Max( Round(TauDecay/CdrFH.dt),1 ) ;
   LastTemplateSample := Min( NumScansPerTau*3,High(Template)) ;
   // Set first 25% of template as pre-event baseline
   FirstTemplateSample := Max(-NumScansPerTau,Low(Template)) ;
   // If insufficient sample data for pre-trigger set it to zero
   if (StartAtSample + FirstTemplateSample) < 0 then FirstTemplateSample := 0 ;
   // Total number of samples in template
   NumTemplateSamples := LastTemplateSample - FirstTemplateSample + 1 ;
   Scale := 1.0/ edThreshold.Scale ;

   // Create template
   SumTemplate := 0.0 ;
   SumTemplateSq := 0.0 ;
   for i := FirstTemplateSample to LastTemplateSample do begin
       if i >= 0 then begin
          t := i*CdrFH.dt ;
          Template[i] := (1.0 - exp(-t/TauRise))*exp(-t/TauDecay) ;
          end
       else Template[i] := 0.0 ;
       SumTemplate := SumTemplate + Template[i] ;
       SumTemplateSq := SumTemplateSq + (Template[i]*Template[i]) ;
       end ;

   // Read A/D sample buffer to be scanned
   PreStartAtSample := Max( StartAtSample+FirstTemplateSample,0 ) ;
   for iIn := 0 to ((NumPoints*2)*CDRFH.NumChannels)-1 do InBuf[iIn] := 0 ;
   NumRead := ReadCDRBuffer(CdrFH,PreStartAtSample,InBuf,NumPoints*2) ;

   iOut := 0 ;
   for iIn := -FirstTemplateSample to -FirstTemplateSample + NumPoints-1 do begin

      // Compute sums
      SumY := 0.0 ;
      SumYSq := 0.0 ;
      SumYxTemplate := 0.0 ;
      ChannelOffset := Channel[cbChannel.ItemIndex].ChannelOffset ;
      for k := FirstTemplateSample to LastTemplateSample do begin
          j := (iIn+k)*CdrFH.NumChannels + ChannelOffset ;
          Y := InBuf[j] ;
          SumY := SumY + Y ;
          SumYSq := SumYSq + Y*Y ;
          SumYxTemplate := SumYxTemplate + (Y*Template[k]) ;
          end ;

       // Calculate best fit template amplitude
       Denom := (SumTemplateSq - ((SumTemplate*SumTemplate)/NumTemplateSamples)) ;
       if Denom <> 0.0 then
          Amplitude := (SumYxTemplate - ((SumTemplate*SumY)/NumTemplateSamples)) / Denom 
       else Amplitude := 0.0 ;

       // Calculate best fit template offset
       Offset := (SumY - Amplitude*SumTemplate)/NumTemplateSamples ;

       // Calculate sum of squared errors

       SSE := SumYSq
              + (Amplitude*Amplitude*SumTemplateSq)
              + (NumTemplateSamples*Offset*Offset)
              - 2.0*(Amplitude*SumYxTemplate
                     + Offset*SumY
                     - (Amplitude*Offset*SumTemplate)) ;

       StandardError := Sqrt(Abs(SSE)/(NumTemplateSamples-1)) ;

       // Detection criterion
       if StandardError > 0.0 then begin
          OutBuf[iOut] := Round(Min(Max( (Scale*Amplitude)/StandardError,
                    -Channel[0].ADCMaxValue-1),Channel[0].ADCMaxValue)) ;
          end
       else OutBuf[iOut] := 0 ;
       Inc(iOut) ;

       end ;

   // Read A/D samples to be displayed (without pre-start samples)
   Result := ReadCDRBuffer(CdrFH,StartAtSample,InBuf,NumPoints) ;

   end ;


function  TEventDetFrm.FindMidPointOfRise(
          iSample : Integer
          ) : Integer ;
// ------------------------------------------------
// Find mid-point of rising edge of detected event
// -----------------------------------------------
var
    i,j,NPBuf,iEnd,iStart,iDetectedAt,iMidPointAt : Integer ;
    yMin,yMax,yMid,yMinAt,yMaxAt : Integer ;
    Buf : PSmallIntArray ;
begin

   NPBuf := Round(edRisingEdgeWindow.Value) ;

   // Exit if no window
   if NPBuf <= 0 then begin
      Result := iSample ;
      Exit ;
      end ;

   // Ensure buffer contains odd number of samples
   if (NPBuf mod 2) = 0 then Inc(NPBuf) ;
   // Initial detection point in buffer
   iDetectedAt := NPBuf div 2 ;

   // Get signal around detection point
   iStart := Max(iSample - iDetectedAt,0) ;
   iDetectedAt := iSample - iStart ;
   iEnd := Min(iStart + NPBuf -1,CDRFH.NumSamplesInFile-1) ;
   NPBuf := iEnd - iStart + 1 ;

   // Allocate buffer
   GetMem( Buf, NPBuf*CDRFH.NumChannels*2 ) ;
   // Read data
   ReadCDRBuffer(CdrFH,iStart,Buf^,NPBuf) ;

   // Determine upper and lower limits

   yMin := High(yMax) ;
   yMax := Low(yMax) ;
   yMinAt := 0 ;
   yMaxat := NPBuf ;
   for i := 0 to NPBuf-1 do begin
      j := i*CDRFH.NumChannels + Channel[cbChannel.ItemIndex].ChannelOffset ;
      if yMax < Buf^[j] then begin
         yMax := Buf^[j] ;
         yMaxAt := i ;
         end ;
      if yMin > Buf^[j] then begin
         yMin := Buf^[j] ;
         yMinAt := i ;
         end ;
      end ;

   // Mid-point
   yMid := Round((yMax + yMin)*0.5) ;

   // Find near sample to mid-point

   iMidPointAt := iDetectedAt ;
   if yMinAt <= yMaxAt then begin
      // Rising edge
      for i := yMinAt to yMaxAt do begin
          j := i*CDRFH.NumChannels + Channel[cbChannel.ItemIndex].ChannelOffset ;
          if Buf^[j] >= YMid then begin
             iMidPointAt := i ;
             Break
             end ;
          end ;
      end
   else begin
      // Falling edge
      for i := yMaxAt to yMinAt do begin
          j := i*CDRFH.NumChannels + Channel[cbChannel.ItemIndex].ChannelOffset ;
          if Buf^[j] <= YMid then begin
             iMidPointAt := i ;
             Break
             end ;
          end ;
      end ;

    Result := iSample + iMidPointAt - iDetectedAt ;

    FreeMem(Buf) ;

    end ;


procedure TEventDetFrm.FormClose(Sender: TObject;
  var Action: TCloseAction);
{ -------------------------
  Close and dispose of form
  -------------------------}
begin

     AbortFlag := True ;
     Application.ProcessMessages ;

     if AvgBuf <> Nil then FreeMem( AvgBuf ) ;
     if DetBuf <> Nil then FreeMem( DetBuf ) ;
     if EditBuf <> Nil then FreeMem( EditBuf ) ;
     if ADC <> Nil then FreeMem( ADC ) ;

     HeapBuffers( Deallocate ) ;
     Main.mnDetectEvent2.Enabled := True ;

     // Save detected event list
     SaveEventList ;

     { Update EDR file header }
     SaveCDRHeader( CDRfH ) ;

     { Disable copy and print menus }
     Main.CopyAndPrintMenus( False, False ) ;

     Action := caFree ;

     end;

procedure TEventDetFrm.FormResize(Sender: TObject);
// ----------------------------------------
// Adjust size/position of controls on form
// ----------------------------------------
begin
     // Adjust size of tabs page to form size

     Page.Height := Max(ClientHeight - Page.Top - 5,2) ;
     Page.Width := Max(ClientWidth - Page.Left - 5,2) ;

     // Set sizes of controls on Detect Events page

     DetectGrp.Height := Max(DetectEventsPage.Height -  DetectGrp.Top - 2,2) ;

     DetDisplayPanel.Top := DetectEventsPage.ClientHeight - DetDisplayPanel.Height - 5 ;

     sbDisplay.Top := DetDisplayPanel.Top - sbDisplay.Height - 2 ;

     scDisplay.Width := Max( DetectEventsPage.Width - scDisplay.Left - 2,2) ;
     scDetDisplay.Width := scDisplay.Width ;
     sbDisplay.Width := scDisplay.Width ;

     scDisplay.Height := Max( (2*(sbDisplay.Top - scDisplay.Top)) div 3,2) ;
     scDetDisplay.Top := scDisplay.Top + scDisplay.Height + 2 ;
     scDetDisplay.Height := Max( sbDisplay.Top - scDetDisplay.Top - 1,2) ;

     scDetDisplay.Left := scDisplay.Left ;
     sbDisplay.Left := scDisplay.Left ;

     // Display points label and box
     DetDisplayPanel.Left := sbDisplay.Left + sbDisplay.Width - DetDisplayPanel.Width ;

     // Set vertical positions of controls on edit page
     EventFilterGrp.Top := EditEventsPage.Height - EventFilterGrp.Height - 2 ;
     EventFilterGrp.Width := scDisplay.Width ;

     EditDisplayWidthPanel.Top := EventFilterGrp.Top - EditDisplayWidthPanel.Height -4 ;

     sbEditDisplay.Top := EditDisplayWidthPanel.Top - sbEditDisplay.Height - 1 ;

     scMarkDisplay.Height := Max( (sbEditDisplay.Top - scEditDisplay.Top) div 5,2) ;
     scMarkDisplay.Top := sbEditDisplay.Top - scMarkDisplay.Height ;
     scEditDisplay.Height := Max( scMarkDisplay.Top - scEditDisplay.Top - 2, 2) ;

     // Set horizontal positions/sizes of controls
     scEditDisplay.Width := Max(DetectEventsPage.Width - scEditDisplay.Left - 2,2) ;
     scMarkDisplay.Width := scEditDisplay.Width ;
     sbEditDisplay.Width := scEditDisplay.Width ;
     EditDisplayWidthPanel.Left := sbEditDisplay.Left + sbEditDisplay.Width
                                 - EditDisplayWidthPanel.Width ;

     EditEventsGrp.Height := DetectEventsPage.Height -  EditEventsGrp.Top - 2 ;

     // Set sizes of controls on X/Y Plot page

     XYPlotGrp.Height := Max( DetectEventsPage.Height -  XYPlotGrp.Top - 2,2) ;
     plPlot.Width := Max( XYPlotPage.ClientWidth - plPlot.Left - 5,2) ;
     plPlot.Height := Max( XYPlotPage.ClientHeight - plPlot.Top - 5,2) ;

     // Set sizes of controls on histogram page
     HistGrp.Height := Max( HistPage.Height -  HistGrp.Top - 2,2) ;
     meHistResults.Height := Max( HistGrp.Height - meHistResults.Top - 5,2) ;

     HistResultsGrp.Top := HistPage.Height - HistResultsGrp.Height - 2 ;
     HistResultsGrp.Width := Max( HistPage.Width - HistResultsGrp.Left - 5,2) ;
     lbHistResults.Width := Max( HistResultsGrp.Width - lbHistResults.Left - 5,2) ;

     plHist.Width := Max( HistPage.Width - plHist.Left - 2,2) ;

     // Histogram display area height
     plHist.Height := Max( HistResultsGrp.Top - plHist.Top - 5,2) ;

     // Set sizes of controls on average page

     AverageGrp.Height := Max( AveragePage.ClientHeight -  AverageGrp.Top - 2,2) ;

     AverageResultsGrp.Top := AveragePage.ClientHeight - AverageResultsGrp.Height - 2 ;
     AverageResultsGrp.Width := Max(AveragePage.ClientWidth - AverageResultsGrp.Left - 5,2) ;
     lbAvgFitResults.Width := Max(AverageResultsGrp.Width - lbAvgFitResults.Left - 5,2) ;

     scAverageDisplay.Height := Max(AverageResultsGrp.Top - scAverageDisplay.Top - 2,2) ;
     scAverageDisplay.Width := Max(AveragePage.ClientWidth - scAverageDisplay.Left - 5,2) ;

     end;


procedure TEventDetFrm.sbDisplayChange(Sender: TObject);
// -------------------------------
// Event detection display changed
// -------------------------------
begin
    DisplayRecord ;
    end;


procedure TEventDetFrm.bDetectClick(Sender: TObject);
// -------------
// Detect events
// -------------
var
    iEnd,iSample,y,StartAtSample,EndAtSample,iEventSample : Integer ;
    DeadSamples : Integer ;
    i,OverThresholdCount,TimeThreshold,ThresholdLevel, Polarity : Integer ;
    Done,NewBufferNeeded, UpdateStatusBar, InitialiseRunningMean : Boolean ;
begin

     { Let user clear event list }
     if NumEvents > 0 then begin
        if MessageDlg('Clear existing events in list',mtConfirmation,
           [mbYes,mbNo], 0 ) = mrYes then
           begin
           // Delete any existing event list files
           DeleteFile( PChar(ChangeFileExt(CdrFH.FileName,EventFileExtension))) ;
           NumEvents := 0 ;
           end
        else Exit ;
        end ;

     bDetect.Enabled := False ;
     bAbort.Enabled := True ;
     ComputationInProgress := True ;
     AbortFlag := False ;

     // Save detection channel
     Settings.EventDetector.Channel := cbChannel.ItemIndex ;

     // Number of samples to skip after an event has been detected
     DeadSamples := Round( edDeadTime.Value / CdrFH.dt ) ;
     Settings.EventDetector.DeadTime := edDeadTime.Value ;

     // Rising edge window
     Settings.EventDetector.RisingEdgeWindow := Round(edRisingEdgeWindow.Value) ;

     { Range of samples to be scanned for events }
     if rbAllRecords.Checked then begin
        StartAtSample := 0 ;
        EndAtSample := CdrFH.NumSamplesInFile div CdrFH.NumChannels ;
        end
     else begin
        StartAtSample := Round( edRange.LoValue/CdrFH.dt ) ;
        EndAtSample :=   Round( edRange.HiValue/CdrFH.dt ) ;
        end ;

    // Loop initialisations
    Done := False ;
    NewBufferNeeded := True ;
    UpdateStatusBar := False ;
    iSample := StartAtSample ;

    // Initialise time threshold
    ThresholdLevel := Round(edThreshold.Value) ;
    if  ThresholdLevel > 0 then Polarity := 1
                           else Polarity := -1 ;
    OverThresholdCount := 0 ;
    if rbThreshold.Checked then begin
       TimeThreshold := Round( edTimeThreshold.Value/CdrFH.dt ) ;
       InitialiseRunningMean := True ;
       end
    else TimeThreshold := 0 ;

    // Detect events loop
    // ------------------
    i := 0 ;
    iEnd := 0 ;
    while not Done do begin

        if NewBufferNeeded then begin
           // Load and display new buffer of A/D samples
           // ------------------------------------------
           sbDisplay.Position := iSample ;

           // Start/end of ADC buffer indices
           iEnd := scDisplay.MaxPoints - 1 ;

           if rbRateOfRise.Checked then begin
              // Detect using initial rate of rise of signal
              RateOfRise( ADC^,iSample,scDisplay.MaxPoints,DetBuf^ )
              end
           else if rbThreshold.Checked then begin
              // Detect using amplitude threshold
              Threshold( ADC^,iSample,scDisplay.MaxPoints,InitialiseRunningMean,DetBuf^ ) ;
              end
           else if rbPatternMatch.Checked then begin
              // Detect by matching EPSC template
              MatchTemplate( ADC^,iSample,scDisplay.MaxPoints,DetBuf^ ) ;
              end ;

           // Display it
           scDisplay.xOffset := sbDisplay.Position ;
           scDisplay.SetDataBuf( ADC ) ;
           scDisplay.Invalidate ;

           scDetDisplay.xOffset := 0 ;
           scDetDisplay.SetDataBuf( DetBuf ) ;
           scDetDisplay.Invalidate ;

           { Initialise detected event line }
           scDetDisplay.CreateLine( 0, clRed, psSolid, 1 ) ;
           scDetDisplay.AddPointToLine( 0, 0 ) ;

           NewBufferNeeded := False ;
           i := 0 ;

           UpdateStatusBar := True ;
           Application.ProcessMessages ;
           end ;

        scDetDisplay.AddPointToLine( i, 0 ) ;
        // Does detection criterion exceed threshold?
        if (Polarity*DetBuf^[i]) > (Polarity*ThresholdLevel) then begin

           // Increment threshold exceeded counter
           Inc(OverThresholdCount) ;

           // If threshold has been exceeded for a sufficiently long time accept detection
           if OverThresholdCount >= TimeThreshold then begin
              y := Round(edThreshold.Value)*2 ;

              // Save location in event in event list
              if NumEvents <= High(Events) then begin
                 iEventSample := FindMidPointOfRise(iSample) ;
                 Events[NumEvents] := Max(iEventSample - TimeThreshold,0) ;
                 Inc(NumEvents) ;
                 end ;

              { Draw detected event marker }
              scDetDisplay.AddPointToLine( i, y ) ;
              scDetDisplay.AddPointToLine( i, 0 ) ;
              iSample := iSample + DeadSamples ;
              i := i + DeadSamples ;
              OverThresholdCount := 0 ;
              UpdateStatusBar := True ;
              end ;
           end
        else begin
           // Cancel over-threshold count if signal falls  below threshold
           OverThresholdCount := 0 ;
           end ;

        // Increment counters
        Inc(iSample) ;
        Inc(i) ;
        if (i > iEnd) then NewBufferNeeded := True ;

        if (iSample > EndAtSample) or (not bAbort.Enabled) then Done := True ;

        // Update status bar
        if UpdateStatusBar then begin
           Main.StatusBar.SimpleText := format(
                                        ' Detect Events : %.2f/%.2f s (%d events detected)',
                                          [iSample*CdrFH.dt,
                                           EndAtSample*CdrFH.dt,
                                           NumEvents] ) ;
           UpdateStatusBar := False ;
           end ;

        if AbortFlag then Break ;

        end ;

    // Save list of events in file
    SaveEventList ;
    Main.StatusBar.SimpleText := format(' Detect Events : %d events detected.',
                                          [NumEvents] ) ;
    WriteToLogFile(Main.StatusBar.SimpleText) ;

    // Remove final detection flag line
    scDetDisplay.CreateLine( 0, clRed, psSolid, 1 ) ;

    // Set upper limit of plot and histogram event ranges to 1-1
    // to force an update of ranges (see PageChange)
    edHistRange.HiValue := 1 ;
    edPlotEventRange.HiValue := 1 ;

    // Re-enable detect button
    bAbort.Enabled := False ;
    bDetect.Enabled := True ;
    EventAnalysisFileUpdateRequired := True ;
    BaselineSplinesAvailable := False ;
    ComputationInProgress := False ;

    edAverageRange.HiLimit := NumEvents ;
    edAverageRange.LoValue := 1 ;
    edAverageRange.HiValue := NumEvents ;

    sbEvent.Position := 1 ;

    end ;


procedure TEventDetFrm.DisplayEditRecord ;
{ -------------------------------------------------------------
  Display currently selected block of data file on edit display
  ------------------------------------------------------------- }
var
   i,iStart,iEnd,iEvent,Step : Integer ;
begin

   if EditBuf = Nil then Exit ;

   scEditDisplay.xOffset := sbEditDisplay.Position ;

   scEditDisplay.NumPoints := ReadCDRBuffer( CdrFH,
                                             sbEditDisplay.Position,
                                             EditBuf^,
                                             scEditDisplay.MaxPoints) ;

   if ckSubtractBaseline.Checked then begin
      SubtractBaseline( sbEvent.Position-1,
                        EditBuf^,
                        sbEditDisplay.Position,
                        scEditDisplay.MaxPoints ) ;
      end ;

   scEditDisplay.SetDataBuf( EditBuf ) ;

   // Find
   iStart :=  scEditDisplay.xOffset ;
   iEnd := iStart + scEditDisplay.MaxPoints - 1 ;

   // Find nearest event occuring within or before display

   iEvent := NumEvents div 2 ;
   Step :=  NumEvents div 2 ;
   while Step > 0 do begin
       if Events[iEvent] < iEnd then begin
          iEvent := Min(iEvent + Step,NumEvents-1) ;
          end
       else begin
          iEvent := Max(iEvent - Step,0) ;
          end ;
       Step := Step div 2 ;
       end ;
   // Work back to first event within display
   While (Events[iEvent] > iStart) and (iEvent >=0) do Dec(iEvent) ;
   iEvent := Min( iEvent+1,NumEvents-1 ) ;

   { Initialise detected event line }
   scMarkDisplay.xOffset := 0 ;
   scMarkDisplay.CreateLine( 0, clRed, psSolid, 1 ) ;
   scMarkDisplay.AddPointToLine( 0, scMarkDisplay.MinADCValue ) ;
   for i := 0 to scMarkDisplay.MaxPoints-1 do begin
       if ((iStart+i) >= Events[iEvent]) and (iEvent < NumEvents) then begin
          Inc(iEvent) ;
          scMarkDisplay.AddPointToLine( i, scMarkDisplay.MinADCValue ) ;
          scMarkDisplay.AddPointToLine( i, scMarkDisplay.MaxADCValue ) ;
          scMarkDisplay.AddPointToLine( i, scMarkDisplay.MinADCValue ) ;
          end ;
       end ;

   scMarkDisplay.AddPointToLine( scMarkDisplay.MaxPoints-1, scMarkDisplay.MinADCValue ) ;

   scEditDisplay.Invalidate ;
   scMarkDisplay.Invalidate ;

   end;


procedure TEventDetFrm.LoadEventList ;
{ ---------------------------------------
  Load list of detected events from file
  --------------------------------------- }
var
     FileName : string ;
     FileHandle : Integer ;
begin

     // Initialise no. of events
     NumEvents := 0 ;

     // Add event list file extension to existing data file name
     FileName := ChangeFileExt( CdrFH.FileName, EventFileExtension ) ;

     if not FileExists( FileName ) then Exit ;

     // Open an existing event list file
     FileHandle := FileOpen(FileName, fmOpenReadWrite ) ;
     if FileHandle < 0 then begin
        ShowMessage('Unable to open ' + FileName ) ;
        Exit ;
        end ;

     // Read number of events from start of file
     FileSeek(FileHandle,0,0) ;
     FileRead(FileHandle,NumEvents,SizeOf(NumEvents)) ;
     NumEvents :=  Max( Min( NumEvents,High(Events)+1 ),0) ;

     // Read List of events
     if NumEvents > 0 then begin
        if FileRead(FileHandle,Events,NumEvents*SizeOf(Integer))
           <> NumEvents*SizeOf(Integer) then
           ShowMessage('Error reading Event file') ;
        end ;

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

     edAverageRange.HiLimit := NumEvents ;
     edAverageRange.LoValue := 1 ;
     edAverageRange.HiValue := NumEvents ;

     end ;


procedure TEventDetFrm.SaveEventList ;
{ ------------------------------------
  Save list of detected events to file
  ------------------------------------ }
var
     FileName : string ;
     FileHandle : Integer ;
     OK : Boolean ;
begin

     // Add event list file extension to existing data file name
     FileName := ChangeFileExt( CdrFH.FileName, EventFileExtension ) ;

     OK := True ;

     // Open file
     if FileExists(FileName) then FileHandle := FileOpen(FileName, fmOpenReadWrite)
                             else FileHandle := FileCreate(FileName) ;
     if FileHandle < 0 then OK := False ;

     if OK then begin

        // Write number of events from start of file
        FileSeek(FileHandle,0,0) ;
        if FileWrite(FileHandle,NumEvents,SizeOf(NumEvents))
           <> SizeOf(NumEvents) then OK := False ;

        // Write List of events
        if OK and (NumEvents > 0) then
           if FileWrite(FileHandle,Events,NumEvents*SizeOf(Integer))
              <> NumEvents*SizeOf(Integer) then OK := False ;

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

        // Set export events range
        edExportRange.LoLimit := edEvent.LoLimit ;
        edExportRange.LoValue := edEvent.LoLimit ;
        edExportRange.HiLimit := edEvent.HiLimit ;
        edExportRange.HiValue := edEvent.HiLimit ;

        end ;

     if not OK then ShowMessage('Error writing Event file') ;

     end ;


procedure TEventDetFrm.scDetDisplayCursorChange(Sender: TObject);
// -------------------------------------------------
// Cursor on detection criterion display has changed
// -------------------------------------------------
begin
     edThreshold.Value := scDetDisplay.HorizontalCursors[ThresholdCursor] ;

     // Align detection display cursor
     if scDetDisplay.VerticalCursors[DisplayCursor]
        <> scDisplay.VerticalCursors[DisplayCursor] then
        scDisplay.VerticalCursors[DisplayCursor] :=
          scDetDisplay.VerticalCursors[DisplayCursor] ;


     end;


procedure TEventDetFrm.bAbortClick(Sender: TObject);
// -----------------------------------------
// Request that detection process be aborted
// -----------------------------------------
begin
     bAbort.Enabled := False ;
     bDetect.Enabled := True ;
     AbortFlag := True ;
     end;


procedure TEventDetFrm.sbEditDisplayChange(Sender: TObject);
begin
    DisplayEditRecord ;
    end;


procedure TEventDetFrm.PageChange(Sender: TObject);
// ------------------------
// Display page has changed
// ------------------------
var
    i,iKeep : Integer ;
begin

     if Page.ActivePage = DetectEventsPage then begin
        // Update Detect events page
        DisplayRecord ;
        end
     else if Page.ActivePage = EditEventsPage then begin

        // Update controls on Edit Events page
        UpdateEditEventPage ;

        end
     else if Page.ActivePage = XYPlotPage then begin
        // Update controls on X/Y plot page
        edPlotEventRange.HiLimit := NumEvents ;
        if edPlotEventRange.HiValue = 1 then edPlotEventRange.HiValue := edPlotEventRange.HiLimit ;
        edPlotEventRange.LoValue := Min( edPlotEventRange.LoValue, NumEvents ) ;
        edPlotEventRange.HiValue := Min( edPlotEventRange.HiValue, NumEvents ) ;
        edPlotTimeRange.HiLimit := CDRFH.NumSamplesInFile*CDRFH.dt ;
        edPlotTimeRange.LoValue := 0.0 ;
        edPlotTimeRange.HiValue := edPlotTimeRange.HiLimit ;
        bSetPlotAxes.Enabled := PlotAvailable ;

        iKeep := cbPlotXVar.ItemIndex ;
        i := cbPlotYVar.Items.IndexOfObject(TObject(vTDecay)) ;
        cbPlotXVar.Items.Strings[i] := format(
                                       'T.%d%%',
                                       [Round(edTDecayPercentage.Value)]) ;
        cbPlotXVar.ItemIndex := iKeep ;

        iKeep := cbPlotYVar.ItemIndex ;
        i := cbPlotYVar.Items.IndexOfObject(TObject(vTDecay)) ;
        cbPlotYVar.Items.Strings[i] := format(
                                       'T.%d%%',
                                       [Round(edTDecayPercentage.Value)]) ;
        cbPlotYVar.ItemIndex := iKeep ;

        if NumEvents > 0 then bNewPlot.Enabled := True
                         else bNewPlot.Enabled := False ;

        end
     else if Page.ActivePage = HistPage then begin
        // Update controls on histogram page
        edHistRange.HiLimit := NumEvents ;
        if edHistRange.HiValue = 1 then edHistRange.HiValue := edHistRange.HiLimit ;
        edHistRange.LoValue := MinFlt( [edHistRange.LoValue, NumEvents] ) ;
        edHistRange.HiValue := MinFlt( [edHistRange.HiValue, NumEvents] ) ;
        bSetHistAxes.Enabled := HistAvailable ;
        i := cbPlotXVar.Items.IndexOfObject(TObject(vTDecay)) ;
        cbHistVar.Items.Strings[i] := format(
                                       'T.%d%%',
                                       [Round(edTDecayPercentage.Value)]) ;

        if NumEvents > 0 then begin
           bNewHistogram.Enabled := True ;
           end
        else begin
           bNewHistogram.Enabled := False ;
           end

        end
     else if Page.ActivePage = AveragePage then begin
        // Update controls on average page
        if NumEvents > 0 then bDoAverage.Enabled := True
                         else bDoAverage.Enabled := False ;
        if NumEventsAveraged > 0 then AnalyseAverage ;
        end ;
     end;


procedure TEventDetFrm.UpdateEditEventPage ;
// -----------------------------------------
// Update edit events page with new settings
// -----------------------------------------
begin

    sbEvent.Max := Max(NumEvents,1) ;
    if NumEvents > 0 then begin
       sbEvent.Enabled := True ;
       bExportToWCPFile.Enabled := True ;
       end
    else begin
       sbEvent.Enabled := False ;
       bExportToWCPFile.Enabled := False ;
       end ;
    edEvent.LoLimit := 1 ;
    edEvent.HiLimit := NumEvents ;
    edEvent.LoValue := sbEvent.Position ;
    edEvent.HiValue := edEvent.HiLimit ;

    // Set export events range
    edExportRange.LoLimit := edEvent.LoLimit ;
    edExportRange.LoValue := edEvent.LoLimit ;
    edExportRange.HiLimit := edEvent.HiLimit ;
    edExportRange.HiValue := edEvent.HiLimit ;

    bExportToWCPFile.Enabled := True ;
    bAbortExport.Enabled := False ;
    bExportNonEvents.Enabled := True ;

    // Display selected event
    if NumEvents > 0 then DisplayEvent
    else begin
       sbEditDisplay.Position := 0 ;
       DisplayEditRecord ;
       end ;
    end ;


procedure TEventDetFrm.sbEventChange(Sender: TObject);
// ---------------------------
// Event selection bar changed
// ---------------------------
begin
     DisplayEvent ;
     end ;


procedure TEventDetFrm.DisplayEvent;
// ---------------------------------
// Display currently  selected event
// ---------------------------------
const
     nAvg = 20 ;
var
     Event : TEventAnalysis ; // Event waveform analysis record
begin

     // Move edit display to selected event
     sbEditDisplay.Position := Max( Events[sbEvent.Position-1]
                                    - Round(scEditDisplay.MaxPoints*edPreTrigger.Value), 0) ;

     // Display event
     DisplayEditRecord ;

     // Calculate event waveform measurements
     AnalyseEvent( sbEvent.Position-1, Event ) ;

     scEditDisplay.HorizontalCursors[BaseLineCursor] := Event.YBaseline ;
     scEditDisplay.ChanZero[cbChannel.ItemIndex] := Event.YBaseline ;

     meResults.Clear ;
     meResults.Lines.Add( format( 'Detected at %.7g s',
                                [Events[sbEvent.Position-1]*CdrFH.dt] )) ;

     meResults.Lines.Add( format( 'Peak (a-a)= %.5g %s',
                                  [Event.Peak,
                                   Channel[cbChannel.ItemIndex].ADCUnits]));

     meResults.Lines.Add( format( 'Area (a-a)= %.5g %s.ms',
                                  [Event.Area,
                                   Channel[cbChannel.ItemIndex].ADCUnits]));

     meResults.Lines.Add( format( 'T(rise)= %.5g ms',[Event.TRise]));

     meResults.Lines.Add( format( 'T(%.0f%%)= %.5g ms',
                          [edTDecayPercentage.Value,Event.TDecay]));

     meResults.Lines.Add( format( 'Tau(decay)= %.5g ms',[Event.TauDecay]));

     meResults.Lines.Add( format( 'Duration= %.5g ms',[Event.Duration]));

     scEditDisplay.HorizontalCursors[0] := Event.YBaseline ;

     if cktCursorAtDetectionPoint.Checked then begin
        scEditDisplay.VerticalCursors[EditCursor] := Events[sbEvent.Position-1]
                                                     - scEditDisplay.xOffset ;
        end
     else begin
        // Place cursor over event
        if (scEditDisplay.VerticalCursors[EditCursor] < 0) or
           (scEditDisplay.VerticalCursors[EditCursor] > scEditDisplay.NumPoints) then
           scEditDisplay.VerticalCursors[EditCursor] := scEditDisplay.NumPoints div 2 ;
           end ;

     // Update event number display
     edEvent.LoValue := sbEvent.Position ;

     // Store analysis area cursor positions
     OldEditC0CursorPos := scEditDisplay.VerticalCursors[EditC0Cursor] ;
     OldEditC1CursorPos := scEditDisplay.VerticalCursors[EditC1Cursor] ;

     end;


procedure TEventDetFrm.AnalyseEvent(
          iEvent : Integer ;          // Event # to be analysed [IN]
          var Event : TEventAnalysis   // Analysis results record [OUT]
          ) ;
// -----------------------------------
// Calculate event waveform parameters
// -----------------------------------
var
     PreScans : Integer ;   // No. of scans before event detection point
     PostScans : Integer ;  // No. of scans after event detection point
     StartScan : Integer ;  // Start of analysis block
     EndScan : Integer ;    // End of analysis block
     NumScans : Integer ;   // No. of scans in analysis block
     EventScan : Integer ;  // Scan (within Buf) where event occurs
     AnalysisStart : Integer ; // First time point range to be analysed
     AnalysisEnd : Integer ;   // Last time point in range to be analysed
     i,j : Integer ;        // Counters

     Polarity : Integer ;   // Event polarity (1=positive-going,-1=negative-)
     Sum : Single ;         // Summation variable
     NumAvg : Integer ;     // No. samples averaged for baseline
     Y : Single ;           // A/D sample value
     YMax : Single ;        // Maximum value within block
     PeakAt : Integer ;     // Scan at which peak occurs
     MidPointOfRiseAt : Integer ; // Scan at mid-point of event rise
     YDecay : Single ;      // x% of peak
     Y90 : Single ;         // 90% of peak
     Y50 : Single ;         // 50% of peak
     Y10 : Single ;         // 10% of peak
     Num10to90PercentPeak : Integer ;    // No. of scans within event rising phase
     Num10toPeak : Integer ;             // No. rising phase scans from 10% to peak
     T : Single ;          //
     SumT : Single ;        // Summation variables for time constant fit
     SumT2 : Single ;         //
     SumY : Single ;         //
     SumYT : Single ;         //
     nPoints : Integer ;
     Slope : Single ;

     Buf : ^TSmallIntArray ;
     Done : Boolean ;
begin

   // Get first and last scans of analysis window
   NumScans := Round(edEditDisplayWidth.Value) ;
   PreScans := Round(NumScans*edPreTrigger.Value) ;
   Settings.EventDetector.PreTriggerFraction := edPreTrigger.Value ;
   PostScans := NumScans - PreScans ;

   // First multi-channel scan in analysis block
   StartScan := Max( Events[iEvent] - PreScans, 0) ;
   if iEvent > 0 then StartScan := Max( Events[iEvent-1]+1,StartScan) ;
   EndScan := Min( Events[iEvent] + PostScans,
                   CDRFH.NumSamplesInFile div CDRFH.NumChannels ) ;

   NumScans := EndScan - StartScan + 1 ;
   EventScan := Events[iEvent] - StartScan ;

   // Set positive/negative polarity of event to be measured
   if rbPositive.Checked then Polarity := 1
                         else Polarity := -1 ;

   // Allocate buffer
   GetMem( Buf, NumScans*CDRFH.NumChannels*2 ) ;

   // Read A/D sample data from file
   ReadCDRBuffer(CdrFH,StartScan,Buf^,NumScans) ;

   // Subtract baseline trend
   if ckSubtractBaseline.Checked then begin
      SubtractBaseline( iEvent, Buf^, StartScan, NumScans ) ;
      end ;

   // Find baseline level
   Event.YBaseline := PreEventBaselineLevel( Buf^, EventScan, NumScans ) ;

   // Get range of points to be analysed from cursors
   AnalysisStart := Min( scEditDisplay.VerticalCursors[EditC0Cursor],
                         scEditDisplay.VerticalCursors[EditC1Cursor] ) ;
   AnalysisEnd := Max( scEditDisplay.VerticalCursors[EditC0Cursor],
                       scEditDisplay.VerticalCursors[EditC1Cursor] ) ;

   // Prevent range exceeding data buffer
   AnalysisStart := Min( Max( AnalysisStart,0), NumScans-2) ;
   AnalysisEnd := Min( Max(AnalysisEnd,AnalysisStart+1),NumScans-1 ) ;

   // Find peak amplitude and area under curve
   YMax := -1E30 ;
   SumY := 0.0 ;
   PeakAt := AnalysisStart ;
   for i := AnalysisStart to AnalysisEnd do begin
       j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
       Y := Polarity*(Buf[j] - Event.YBaseline) ;
       if Y >= YMax then begin
          YMax := Y ;
          PeakAt := i ;
          end ;
       SumY := SumY + Y ;
       end ;
   Event.Peak := Polarity*YMax*Channel[cbChannel.ItemIndex].ADCScale ;
   Event.Area := Polarity*SumY*Channel[cbChannel.ItemIndex].ADCScale*CDRFH.dt*SecsToMs ;

   // Find rise time
   i := PeakAt ;
   MidPointOfRiseAt := PeakAt ;
   Y10 := YMax / 10.0 ;
   Y50 := YMax / 2.0 ;
   Y90 := YMax - Y10 ;
   Num10to90PercentPeak := 0 ;
   Num10toPeak := 0 ;
   Done := False ;
   while not Done do begin
       j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
       Y := Polarity*(Buf[j] - Event.YBaseline) ;
       if Y <= Y90 then Inc(Num10to90PercentPeak) ;
       if Y >= Y50 then MidPointOfRiseAt := i ;
       Inc(Num10toPeak) ;
       Dec(i) ;
       if (Y < Y10) or (i<=0) then Done := True ;
       end ;
   Event.TRise := Num10to90PercentPeak*CDRFH.dt*SecsToMs ;

   // Find time from peak to X% of peak decay
   i := PeakAt ;
   YDecay := YMax*(1.0 - (0.01*edTDecayPercentage.Value)) ;
   Done := False ;
   while not Done do begin
       j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
       Y := Polarity*(Buf[j] - Event.YBaseline) ;
       Inc(i) ;
       if (Y < YDecay) or (i >= NumScans) then Done := True ;
       end ;

   if cbDecayFrom.ItemIndex = cTDecayFromPeak then begin
      // Decay time from peak
      Event.TDecay := (i-PeakAt)*CDRFH.dt*SecsToMs ;
      end
   else if cbDecayFrom.ItemIndex = cTDecayFromMidRise then begin
      // Decay time from mid-point of rising phase
      Event.TDecay := (i-MidPointOfRiseAt)*CDRFH.dt*SecsToMs ;
      end
   else begin
      // Decay time from first analysis cursor
      Event.TDecay := (i-AnalysisStart)*CDRFH.dt*SecsToMs ;
      end ;

   // Event duration (10% of rising phase to 90% decay after peak)
   i := PeakAt ;
   YDecay := YMax*0.1 ;
   Done := False ;
   while not Done do begin
       j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
       Y := Polarity*(Buf[j] - Event.YBaseline) ;
       Inc(i) ;
       if (Y < YDecay) or (i >= NumScans) then Done := True ;
       end ;
   Event.Duration :=  ((i-PeakAt) + Num10toPeak)*CDRFH.dt*SecsToMs ;

   // Calculate decay time constant
   i := PeakAt ;
   T := 0.0 ;
   SumT := 0.0 ;
   SumT2 := 0.0 ;
   SumY := 0.0 ;
   SumYT := 0.0 ;
   nPoints := 0 ;
   Done := False ;
   while not Done do begin
      j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
      Y := Polarity*(Buf[j] - Event.YBaseline) ;
      if Y > Y10 then begin
         Y := Ln(Y) ;
         SumT := SumT + T ;
         SumT2 := SumT2 + T*T ;
         SumY := SumY + Y ;
         SumYT := SumYT + Y*T ;
         Inc(nPoints) ;
         end
      else Done := True ;
      Inc(i) ;
      T := T + CDRFH.dt ;
      if i >= AnalysisEnd then Done := True ;
      end ;
   if nPoints > 1 then begin
      Slope := ((nPoints*SumYT) - (SumT*SumY)) /
               ((nPoints*SumT2) - (SumT*SumT)) ;
      if Slope < 0.0 then Event.TauDecay := (-1.0/Slope)*SecsToMs
                     else Event.TauDecay := 0.0 ;
      end
   else Event.TauDecay := 0.0 ;

   FreeMem(Buf) ;

   end ;

function TEventDetFrm.PreEventBaselineLevel(
         var Buf : Array of SmallInt ; // Data buffer
         EventScan : Integer ;     // Scan # of start of event in Buf
         NumScans : Integer       // No. of scans in Buf
         ) : Integer ;
// ----------------------------------
// Calculate pre-event baseline level
// ----------------------------------
var
    i,j : Integer ;
    Polarity : Integer ;
    Sum : Single ;
    Y : Single ;
    NumAvg : Integer ;
    iStart,iEnd : Integer ;
    NumAvgRequired : Integer ;
    iZeroGap : Integer ;
begin

   NumAvgRequired := Round( edZeroNumAvg.Value) ;
   iZeroGap := Round( edZeroGap.Value) ;

   if rbBaselineAtStart.Checked then begin
      // Baseline at start of event window
      iStart := iZeroGap ;
      iEnd := NumAvgRequired-1 ;
      end
   else begin
      // Baseline at start of event

      // Set positive/negative polarity of event to be measured
      if rbPositive.Checked then Polarity := 1
                            else Polarity := -1 ;

      // Find start of rising phase of event
      i := EventScan ;
      Repeat
          j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
          Y := Polarity*(Buf[j] - Buf[j-CDRFH.NumChannels]);
          iEnd := i ;
          Dec(i) ;
          Until (i = 0) or (Y <= 0.0) ;

      iEnd := Max(iEnd - iZeroGap,0) ;
      iStart := Max(iEnd - NumAvgRequired + 1,0) ;
      end ;

   // Calculate baseline average
   Sum := 0.0 ;
   NumAvg := 0 ;
   for i := iStart to iEnd do begin
      j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
      Sum := Sum + Buf[j] ;
      Inc(NumAvg) ;
      end ;

   // Determine pre-event baseline level

   if NumAvg > 0 then begin
      // If an average available use it for baseline
      Result := Round( Sum / NumAvg ) ;
      end
   else begin
      // If no average available use first sample
      Result := Buf[Channel[cbChannel.ItemIndex].ChannelOffset] ;
      end ;
   end ;


procedure TEventDetFrm.SubtractBaseline(
          iEvent : Integer  ;       // Event #
          var Buf : Array of SmallInt ; // Sample buffer
          BufStartScan : Integer ;
          NumScans : Integer
          ) ;
// --------------------------------------
// Subtract pre-event baseline from event
// --------------------------------------
const
     NumAvgRequired = 10 ;

var
     iScan,klo,khi,j,i : Integer ;
     A,B,H,Y : Single ;
begin



    // Calculate cublic splines for baseline interpolation
    if not BaselineSplinesAvailable then begin
       CalculateBaselineSplines ;
       end ;

    for i := 0 to NumScans-1 do begin

        iScan :=  BufStartScan + i ;

        if iScan < Events[iEvent] then begin
           KLo := Max(iEvent-1,0) ;
           KHi := iEvent ;
           end
        else begin
           KLo := iEvent ;
           KHi := Min(iEvent+1,NumEvents-1) ;
           end ;

        H := Events[KHi] - Events[KLo] ;

        if H <> 0.0 then begin
           A := (Events[KHi] - iScan)/H ;
           B := (iScan - Events[KLo])/H ;
           Y := A*YBaseline[KLo] + B*YBaseline[KHi] +
                ((Power(A,3)-A)*YBaseline2[KLo] +
                (Power(B,3)-B)*YBaseline2[KHi])*(H*H)/6.0 ;
           end
        else begin
           Y := YBaseline[KHi] ;
           end ;

        j := i*CDRFH.NumChannels  + cbChannel.ItemIndex ;
        Buf[j] := Buf[j] - Round(Y) + YBaseline[0] ;

        end ;

    end ;

procedure TEventDetFrm.CalculateBaselineSplines ;
// -------------------------------------------------
// Calculate cublic splines for baseline subtraction
// -------------------------------------------------
const
     NumAvgRequired = 10 ;

var
     PreScans : Integer ;   // No. of scans before event detection point
     PostScans : Integer ;  // No. of scans after event detection point
     StartScan : Integer ;  // Start of analysis block
     EndScan : Integer ;    // End of analysis block
     NScans : Integer ;   // No. of scans in analysis block
     i : Integer ;
     Buf : ^TSmallIntArray ;
     U : PSingleArray ;
     Sig : Single ;
     P,qn,un : Single ;
begin

    GetMem( U, Max(NumEvents,1)*4 ) ;

    Screen.Cursor := crHourGlass ;

    // Get first and last scans of analysis window
    NScans := Round(edEditDisplayWidth.Value) ;
    PreScans := Round(NScans*edPreTrigger.Value) ;
    PostScans := NScans - PreScans ;

   // Allocate buffer
   GetMem( Buf, NScans*CDRFH.NumChannels*2 ) ;

    // Calculate pre-event baselines

    for i := 0 to NumEvents-1 do begin

        // Find limits of event
        StartScan := Max( Events[i] - PreScans,0) ;
        if i > 0 then StartScan := Max( Events[i-1]+1,StartScan) ;
        EndScan := Min( Events[i] + PostScans -1,
                        CDRFH.NumSamplesInFile div CDRFH.NumChannels ) ;
        if i < (NumEvents-1) then EndScan := Min( Events[i+1]-1,EndScan) ;
        NScans := Min(EndScan - StartScan + 1, NScans) ;

        // Read A/D sample data from file
        ReadCDRBuffer(CdrFH,StartScan,Buf^,NScans) ;

        // Pre-event baseline
        YBaseline[i] := PreEventBaselineLevel( Buf^,
                                               Events[i] - StartScan,
                                               NScans ) ;

        end ;

    // Calculate cubic spline

    YBaseline2[0] := 0.0 ;
    U^[0] := 0.0 ;
    for i := 1 to NumEvents-2 do begin
        sig := (Events[i] - Events[i-1])/(Events[i+1] - Events[i-1]) ;
        P := sig*YBaseline2[i-1] + 2.0 ;
        YBaseline2[i] := (sig - 1.0) / P ;
        U^[i] := ( 6.0*( (YBaseline[i+1] - YBaseline[i])/(Events[i+1] - Events[i])
                      - (YBaseline[i] - YBaseline[i-1])/(Events[i] - Events[i-1]))
                      / (Events[i+1] - Events[i-1]) - sig*U^[i-1])/P ;
        end ;

    qn := 0.0 ;
    un := 0.0 ;
    YBaseline2[NumEvents-1] := (un - qn*U^[NumEvents-2]) /
                               (qn*YBaseline2[NumEvents-2] + 1.0 ) ;
    for i := NumEvents-2 downto 0 do begin
        YBaseline2[i] := YBaseline2[i]*YBaseline2[i+1] + U^[i] ;
        end ;

    BaselineSplinesAvailable := True ;
    Screen.Cursor := crDefault ;
    FreeMem(Buf) ;
    FreeMem(U) ;

    end ;


procedure TEventDetFrm.edEventKeyPress(Sender: TObject; var Key: Char);
// -------------------------------
// Key pressed in edited event box
// -------------------------------
begin

     if key = #13 then begin
        sbEvent.Position := Round(edEvent.LoValue) ;
        edEvent.HiValue := NumEvents ;
        end ;

     end;


procedure TEventDetFrm.bInsertEventClick(Sender: TObject);
// ------------------------------------------
// Insert an event at current cursor position
// ------------------------------------------
var
   i,iEvent,NewEventAt : Integer ;
   AddEvent : Boolean ;
begin

   // Sample position of new event
   NewEventAt := scEditDisplay.VerticalCursors[EditCursor]
                 + scEditDisplay.xOffset ;

   // Find next higher event in list
   iEvent := 0 ;
   While (Events[iEvent] <= NewEventAt) and
         (iEvent < NumEvents) do Inc(iEvent) ;

   // Make space for new event in list (only if it doesn't exist already)
   if ((NumEvents = 0) or (Events[iEvent] <> NewEventAt)) and
      (NumEvents <= High(Events)) then begin
      AddEvent := True ;
      Inc(NumEvents) ;
      for i := NumEvents-1 downto iEvent+1 do Events[i] := Events[i-1] ;
      end
   else AddEvent := False ;

   if AddEvent then begin

      Events[iEvent] := NewEventAt ;

      sbEvent.Max := NumEvents ;
      edEvent.HiLimit := NumEvents ;
      edEvent.HiValue := edEvent.HiLimit ;
      edEvent.LoValue := iEvent+1 ;
      sbEvent.Position := iEvent+1 ;

      // Move edit display to selected event
      sbEditDisplay.Position := Max( Events[sbEvent.Position-1]
                                     - scEditDisplay.MaxPoints div 5, 0) ;

      // Enable/disable controls
      if NumEvents > 0 then begin
         sbEvent.Enabled := True ;
         bExportToWCPFile.Enabled := True ;
         end
      else begin
         sbEvent.Enabled := False ;
         bExportToWCPFile.Enabled := False ;
         end ;

      DisplayEvent ;
      end ;

   // Update event list file
   SaveEventList ;

   EventAnalysisFileUpdateRequired := True ;
   BaselineSplinesAvailable := False ;
   
   end;


procedure TEventDetFrm.bDeleteEventClick(Sender: TObject);
// -----------------------------------------
// Delete currently selected event from list
// -----------------------------------------
var
   iEvent,i : Integer ;
begin

   // Delete current event from list
   iEvent := sbEvent.Position - 1 ;
   for i := iEvent to NumEvents-1 do Events[i] := Events[i+1] ;
   Dec(NumEvents) ;

   sbEvent.Max := Max(NumEvents,1) ;
   edEvent.HiLimit := NumEvents ;
   edEvent.HiValue := edEvent.HiLimit ;
   edEvent.LoValue := iEvent+1 ;
   sbEvent.Position := iEvent+1 ;

   // Move edit display to selected event
   sbEditDisplay.Position := Max( Events[sbEvent.Position-1]
                                  - scEditDisplay.MaxPoints div 5, 0) ;

   // Enable/disable controls
   if NumEvents > 0 then begin
      sbEvent.Enabled := True ;
      bExportToWCPFile.Enabled := True ;
      end
   else begin
      sbEvent.Enabled := False ;
      bExportToWCPFile.Enabled := False ;
      end ;

   // Update event list file
   SaveEventList ;

   DisplayEvent ;
   EventAnalysisFileUpdateRequired := True ;
   BaselineSplinesAvailable := False ;
   end ;


procedure TEventDetFrm.scEditDisplayCursorChange(Sender: TObject);
// ----------------------------------
// Edit display window cursor changed
// ----------------------------------
var
     ch : Integer ;
     CursorAtSample : Integer ;
begin

      // Sample position of new event
      CursorAtSample := scEditDisplay.VerticalCursors[EditCursor]
                        + scEditDisplay.xOffset ;

      // Enable/disable Insert Event and Delete Event buttons
      // depending upon whether cursor is over an event
      if CursorAtSample = Events[sbEvent.Position-1] then begin
         bDeleteEvent.Enabled := True ;
         bInsertEvent.Enabled := False ;
         end
      else begin
         bDeleteEvent.Enabled := True ;
         bInsertEvent.Enabled := True ;
         end ;

     for ch := 0 to scEditDisplay.NumChannels-1 do
         if scEditDisplay.ChanVisible[ch] then begin
         { Get signal baseline cursor }
         Channel[ch].yMin := scEditDisplay.yMin[ch] ;
         Channel[ch].yMax := scEditDisplay.yMax[ch] ;
         end ;

     end;


procedure TEventDetFrm.bNewPlotClick(Sender: TObject);
// ------------------------------------------
// Plot selected event variables on X/Y graph
// ------------------------------------------
var
   iEvent, StartAtEvent, EndAtEvent : Integer ;
   StartAtSample, EndAtSample : Integer ;
   StartofInterval, EndOfInterval : Integer ;
   XVarType, YVarType : Integer ;
   IntervalWidth : Integer ;
   xMin,xMax, x,y : Single ;
   OK : Boolean ;
   Done : Boolean ;
begin

   bPlotAbort.Enabled := True ;
   bNewPlot.Enabled := False ;
   ComputationInProgress := True ;
   AbortFlag := False ;

   Main.StatusBar.SimpleText :=
   'Event Detector : WAIT ... Plotting X/Y Graph' ;


   if rbPlotAllEvents.Checked then begin
      { Use all Events }
      StartAtEvent := 0 ;
      EndAtEvent := NumEvents - 1 ;
      StartAtSample := 0 ;
      EndAtSample := CDRFH.NumSamplesInFile-1 ;
      end
   else begin
      { Use selected range of records }
       StartAtEvent := Round(edPlotEventRange.LoValue)-1 ;
       EndAtEvent := Round(edPlotEventRange.HiValue)-1 ;
       StartAtSample := Round(edPlotTimeRange.LoValue/CDRFH.dt) ;
       EndAtSample := Round(edPlotTimeRange.HiValue/CDRFH.dt) ;
       end ;

   // X axis variable
   XVarType := Integer(cbPlotXVar.Items.Objects[cbPlotXVar.ItemIndex]) ;
   xUnits := GetVariableUnits( XVarType ) ;

   // Y axis variable
   YVarType := Integer(cbPlotYVar.Items.Objects[cbPlotYVar.ItemIndex]) ;
   yUnits := GetVariableUnits( YVarType ) ;

   { Plot graph of currently selected variables }
   plPlot.xAxisAutoRange := False ;
   plPlot.yAxisAutoRange := True ;
   plPlot.xAxisLabel := cbPlotXVar.text ;
   if xUnits <> '' then plPlot.xAxisLabel := plPlot.xAxisLabel + '(' + xUnits + ')' ;
   plPlot.yAxisLabel := cbPlotYVar.text ;
   if yUnits <> '' then plPlot.yAxisLabel := plPlot.yAxisLabel + '(' + yUnits + ')' ;

   { Clear data points line }
   plPlot.ClearAllLines ;
   plPlot.MaxPointsPerLine := NumEvents*2 ;
   plPlot.CreateLine( 0 , clBlue, msOpenSquare, psSolid ) ;

     { Clear any fitted line }
   //  plVarPlot.CreateLine( VarFitLine , clRed, msNone, psSolid ) ;

   xMin := 1E30 ;
   xMax := -xMin ;
   iEvent := StartAtEvent ;
   Settings.EventDetector.AvgFrequencyInterval := edAverageInterval.Value ;
   IntervalWidth := Max(Round(edAverageInterval.Value/CdrFH.dt),1) ;
   StartOfInterval := StartAtSample ;
   EndOfInterval := StartOfInterval + IntervalWidth ;
   AbortFlag := False ;
   Done := False ;
   While (Not Done) and (not AbortFlag) do begin

         if (XVarType = vFrequencyAvg) or (YVarType = vFrequencyAvg) then begin
            // Calculate average event frequency
            OK :=CalculateVariablesInInterval( StartOfInterval,
                                               EndOfInterval,
                                               xVarType,
                                               yVarType,
                                               iEvent,
                                               X,
                                               Y ) ;
            StartOfInterval := StartOfInterval + IntervalWidth ;
            EndOfInterval := EndOfInterval + IntervalWidth ;
            if EndOfInterval > EndAtSample then Done := True ;
            end
         else begin
            // Calculate variables and instantaneous frequency
            OK := CalculateVariables( iEvent,
                                      xVarType,
                                      yVarType,
                                      X,
                                      Y ) ;
            if iEvent > EndAtEvent then Done := True ;
            end ;

         // Add point to X/Y graph
         if OK then begin
            plPlot.AddPoint( 0, X, Y ) ;
            if xMin > X then xMin := X ;
            if xMax < X then xMax := X ;
            end ;

         end ;

     //plPlot.SortByX( 0 ) ;

   plPlot.XAxisMin := xMin ;
   plPlot.XAxisMax := xMax ;
   if plPlot.XAxisMin = plPlot.XAxisMax then plPlot.XAxisMax := plPlot.XAxisMin + 1.0 ;
   plPlot.XAxisTick := (xMax - xMin)*0.1 ;

   { Create and set Histogram cursors to initial positions }
   plPlot.ClearVerticalCursors ;
   plPlot.AddVerticalCursor( clGReen, '?r' ) ;
   plPlot.VerticalCursors[0] := (xMin + xMax)*0.5 ;

   PlotAvailable := True ;
   bSetPlotAxes.Enabled := True ;
   bPlotAbort.Enabled := False ;
   bNewPlot.Enabled := True ;

   Main.StatusBar.SimpleText := '' ;
   ComputationInProgress := False ;

   end;


procedure TEventDetFrm.UpdateEventAnalysisFile ;
// -------------------------------------------
// Update event waveform analysis results file
// -------------------------------------------
var
     iEvent : Integer ;      // Event No.
     Event : TEventAnalysis ; // Event waveform measurements
begin

     Screen.Cursor := crHourGlass ;

     // Position file pointer at start of file
     FileSeek( EventAnalysisFile, 0, 0 ) ;

     // Write waveform measurement record
     AbortFlag := False ;
     for iEvent := 0 to NumEvents-1 do begin
         // Analyse event waveform
         AnalyseEvent( iEvent, Event ) ;
         // Write to file
         FileWrite( EventAnalysisFile, Event, SizeOf(Event)) ;
         // Report progress
         Main.StatusBar.SimpleText := format(
         ' Event Detector : Calculating waveform measurements record %d/%d',
         [iEvent+1,NumEvents]) ;

         if (iEvent mod 20) = 0 then Application.ProcessMessages ;
         if AbortFlag then Break ;

         end ;

     if not AbortFlag then EventAnalysisFileUpdateRequired := False ;
     Screen.Cursor := crDefault ;
     Main.StatusBar.SimpleText := ' ' ;

     end ;


function TEventDetFrm.CalculateVariables(
          var iEvent : Integer ;                // Event number to be analysed [In]
          XVarType : Integer ;              // Variable selected for X axis [IN]
          YVarType : Integer ;              // Variable selected for Y axis [IN]
          var X : Single ;                  // X axis variable value
          var Y : Single                    // Y axis variable value
          ) : Boolean ;                     // TRUE = valid variable returned
// -----------------------------------------
// Calculate selected X and Y plot variables
// -----------------------------------------
var
     Time, Interval, EventNum : Single ;
     Event : TEventAnalysis ;  // Event waveform measurements record
begin

     // Update event waveform analysis file if required
     if EventAnalysisFileUpdateRequired then UpdateEventAnalysisFile ;

     // Read event results from file
     FileSeek( EventAnalysisFile, iEvent*SizeOf(Event), 0) ;
     FileRead( EventAnalysisFile, Event, SizeOf(Event)) ;

     // Calculate inter-event intervals/instantaneous frequency

     EventNum := iEvent + 1 ;

     // Time of detection
     Time := Events[iEvent]*CdrFH.dt  ;

     // Time interval since previous event
     if iEvent > 0 then Interval := (Events[iEvent] - Events[iEvent-1])*CdrFH.dt
                   else Interval := -1.0 ;

     Inc(iEvent) ;

     // X axis variable
     Result := True ;
     Case XVarType of
          vBaseline : X := Channel[cbChannel.ItemIndex].ADCScale*
                          (Event.YBaseline - Channel[cbChannel.ItemIndex].ADCZero) ;
          vEventNum :  X := EventNum ;
          vTime :  X := Time ;
          vPeak : X := Event.Peak ;
          vArea : X := Event.Area ;
          vTRise : X := Event.TRise ;
          vTDecay : X := Event.TDecay ;
          vTauDecay : X := Event.TauDecay ;
          vDuration : X := Event.Duration ;
          vInterval : begin
                      X := Interval ;
                      if X < 0.0 then Result := False ;
                      end ;
          vFrequencyInst : begin
                      if Interval > 0.0 then X := 1.0/Interval
                                        else Result := False ;
                      end ;
          end ;

     // Y axis variable
     Case YVarType of
          vBaseline : Y := Channel[cbChannel.ItemIndex].ADCScale*
                          (Event.YBaseline - Channel[cbChannel.ItemIndex].ADCZero) ;
          vEventNum :  Y := EventNum ;
          vTime :  Y := Time ;
          vPeak : Y := Event.Peak ;
          vArea : Y := Event.Area ;
          vTRise : Y := Event.TRise ;
          vTDecay : Y := Event.TDecay ;
          vTauDecay : Y := Event.TauDecay ;
          vDuration : Y := Event.Duration ;
          vInterval : begin
                Y := Interval ;
                if Y < 0.0 then Result := False ;
                end ;
          vFrequencyInst : begin
                if Interval > 0.0 then Y := 1.0/Interval
                                  else Result := False ;
                end ;
          end ;

     end ;


function TEventDetFrm.CalculateVariablesInInterval(
          StartOfInterval : Integer ;       // Sample at which interval ends [IN]
          EndOfInterval : Integer ;         // Sample at which interval ends [IN]
          XVarType : Integer ;              // Variable selected for X axis [IN]
          YVarType : Integer ;              // Variable selected for Y axis [IN]
          var iEvent : Integer ;            // Event number [OUT]
          var X : Single ;                  // X axis variable value
          var Y : Single                    // Y axis variable value
          ) : Boolean ;                     // TRUE = valid variable returned
// -----------------------------------------
// Calculate selected X and Y plot variables
// -----------------------------------------
var
     Time, Interval, TInterval, EventNum : Single ;
     Event : TEventAnalysis ;  // Event waveform measurements record
     NumEventsInInterval : Integer  ;
begin

     Result := True ;

     // Update event waveform analysis file if required
     if EventAnalysisFileUpdateRequired then UpdateEventAnalysisFile ;

     // Clear measuresments to avoid spurious values being set back if
     // no events in interval

     Event.YBaseline := 0 ;
     EventNum := iEvent + 1 ;
     Event.Peak := 0 ;
     Event.Area := 0 ;
     Event.TRise := 0 ;
     Event.TDecay := 0 ;
     Event.TauDecay  := 0 ;
     Event.Duration := 0 ;


     Time := (StartOfInterval + EndOfInterval)*0.5*CDRFH.dt ;
     TInterval := (EndOfInterval - StartOfInterval)*CDRFH.dt ;
     NumEventsInInterval := 0 ;

     // Move event index to first event in interval
     while (Events[iEvent] < StartofInterval) and
           (iEvent < NumEvents) do begin
           Inc(iEvent) ;
           end ;

     while (Events[iEvent] < EndofInterval) and (iEvent < NumEvents) do begin

         // Read event results from file
         FileSeek( EventAnalysisFile, iEvent*SizeOf(Event), 0) ;
         FileRead( EventAnalysisFile, Event, SizeOf(Event)) ;

         EventNum := iEvent + 1 ;

         // Time interval since previous event
         if iEvent > 0 then Interval := (Events[iEvent] - Events[iEvent-1])*CdrFH.dt
                       else Interval := -1.0 ;

         Inc(iEvent) ;

         Inc(NumEventsInInterval) ; // Increment event count

         end ;

     // X axis variable
     Case XVarType of
          vBaseline : X := Channel[cbChannel.ItemIndex].ADCScale*
                          (Event.YBaseline - Channel[cbChannel.ItemIndex].ADCZero) ;
          vEventNum :  X := EventNum ;
          vTime :  X := Time ;
          vPeak : X := Event.Peak ;
          vArea : X := Event.Area ;
          vTRise : X := Event.TRise ;
          vTDecay : X := Event.TDecay ;
          vTauDecay : X := Event.TauDecay ;
          vDuration : X := Event.Duration ;
          vInterval : X := Interval ;
          vFrequencyAvg : X := NumEventsInInterval/TInterval ;
          end ;

     // Y axis variable
     Case YVarType of
             vBaseline : Y := Channel[cbChannel.ItemIndex].ADCScale*
                           (Event.YBaseline - Channel[cbChannel.ItemIndex].ADCZero) ;
             vEventNum :  Y := EventNum ;
             vTime :  Y := Time ;
             vPeak : Y := Event.Peak ;
             vArea : Y := Event.Area ;
             vTRise : Y := Event.TRise ;
             vTDecay : Y := Event.TDecay ;
             vTauDecay : Y := Event.TauDecay ;
             vDuration : Y := Event.Duration ;
             vInterval : Y := Interval ;
             vFrequencyAvg : Y := NumEventsInInterval/TInterval ;
             end ;

     end ;


procedure TEventDetFrm.bExportToWCPFileClick(Sender: TObject);
// -----------------------------------------
// Export detected events to .WCP data file
// -----------------------------------------
var
     i,ch, iStart, NumPretrigger : Integer ;
     NumRecords : Integer ;
     Buf : PSmallIntArrayDyn ;
begin

     // Present user with standard Save File dialog box
     Main.SaveDialog.options := [ofOverwritePrompt,ofHideReadOnly,ofPathMustExist] ;
     Main.SaveDialog.DefaultExt := WCPFileExtension ;

     Main.SaveDialog.FileName := AnsiReplaceStr(
                                 LowerCase(ExtractFileName(CdrFH.FileName)),
                                 '.edr',
                                 format( ' %d-%d.wcp',
                                         [Round(edExportRange.LoValue),
                                          Round(edExportRange.HiValue)] ))
                                           ;
     Main.SaveDialog.Filter := format( ' WCP Files (*%s)|*%s',
                                  [WCPFileExtension,WCPFileExtension]) ;
     Main.SaveDialog.Title := 'Export to WCP File' ;

     { Create new data file }
     if not Main.SaveDialog.execute then Exit ;
     if not WCPFile.CreateDataFile( Main.SaveDialog.FileName, ftWCP ) then Exit ;

     bExportToWCPFile.Enabled := False ;
     bAbortExport.Enabled := True ;
     bExportNonEvents.Enabled := False ;

     WCPFile.NumScansPerRecord := Min(Round(edEditDisplayWidth.Value),
                                      MaxExportSamples div CdrFH.NumChannels ) ;
     WCPFile.NumScansPerRecord := Max(256*(WCPFile.NumScansPerRecord div 256),256) ;
     WCPFile.NumChannelsPerScan := CdrFH.NumChannels ;
     WCPFile.WCPNumZeroAvg := 20 ;
     WCPFile.ScanInterval := CDRfH.dt ;
     WCPFile.MaxADCValue := Channel[0].ADCMaxValue ;

     if WCPFile.NumScansPerRecord < (Round(edEditDisplayWidth.Value)-256) then begin
        ShowMessage('EXPORT EVENTS: Warning! Exported record duration truncated.') ;
        end ;

     // Allocate export buffer
     GetMem( Buf, WCPFile.NumScansPerRecord*WCPFile.NumChannelsPerScan*2 ) ;

     for ch := 0 to WCPFile.NumChannelsPerScan-1 do begin
         WCPFile.ChannelName[ch] := Channel[ch].ADCName ;
         WCPFile.ChannelUnits[ch] := Channel[ch].ADCUnits ;
         WCPFile.ChannelOffset[ch] := Channel[ch].ChannelOffset ;
         WCPFile.ChannelCalibrationFactor[ch] := Channel[ch].ADCCalibrationFactor ;
         WCPFile.ChannelGain[ch] := Channel[ch].ADCAmplifierGain ;
         WCPFile.ChannelScale[ch] := Channel[ch].ADCScale ;
         WCPFile.ChannelZeroAt[ch] := 1 ;
         WCPFile.ChannelADCVoltageRange[ch]:= CdrFH.ADCVoltageRange ;
         end ;

     NumPretrigger := Round(WCPFile.NumScansPerRecord*edPreTrigger.Value) ;

     NumRecords := 0  ;
     AbortFlag := False ;
     for i := Round(edExportRange.LoValue-1)
              to Round(edExportRange.HiValue-1) do begin

         // Include pre-trigger samples
         iStart := Events[i] - NumPretrigger ;

         // Extract and save to WCP data file
         if ReadCDRBuffer(CdrFH,iStart,Buf^,WCPFile.NumScansPerRecord)
            = WCPFile.NumScansPerRecord then begin

            // Subtract baseline (if required)
            if ckSubtractBaseline.Checked then begin
               SubtractBaseline( i,
                                 Buf^,
                                 iStart,
                                 WCPFile.NumScansPerRecord ) ;
               end ;

            Inc( NumRecords ) ;

            WCPFile.RecordNum := NumRecords ;

            WCPFile.WCPNumZeroAvg := 20 ;
            WCPFile.WCPRecordAccepted := True ;
            WCPFile.WCPRecordType := 'TEST' ;
            WCPFile.WCPRecordNumber := NumRecords ; ;
            WCPFile.WCPRecordTime := Events[i]*CdrFH.dt ;

            WCPFile.SaveADCBuffer( 0, WCPFile.NumScansPerRecord, Buf^ ) ;

            Main.StatusBar.SimpleText := format(
                                         ' Detect Events : Event %d/%d written to %s',
                                           [i+1,NumEvents,WCPFile.FileName] ) ;

            Application.ProcessMessages ;

            end ;

         if AbortFlag then Break ;

         end ;

     // Close WCP data file
     WCPFile.CloseDataFile ;

     // Release buffer
     FreeMem(Buf) ;

     Main.StatusBar.SimpleText := format(
                                  ' Detect Events : %d events written to %s',
                                    [NumEvents,WCPFile.FileName] ) ;
     WriteToLogFile( Main.StatusBar.SimpleText ) ;

     bExportToWCPFile.Enabled := True ;
     bAbortExport.Enabled := False ;
     bExportNonEvents.Enabled := True ;

     end;


procedure TEventDetFrm.CopyDataToClipboard ;
// ----------------------------------
// Copy data on display to clipboard
// ----------------------------------
begin

     // Copy data from appropriate display component depending upon
     // which page is on display

     if Page.ActivePage = DetectEventsPage then scDisplay.CopyDataToClipboard
     else if Page.ActivePage = EditEventsPage then scEditDisplay.CopyDataToClipboard
     else if Page.ActivePage = XYPlotPage then plPlot.CopyDataToClipboard
     else if Page.ActivePage = HistPage then plHist.CopyDataToClipboard
     else if Page.ActivePage = AveragePage then scAverageDisplay.CopyDataToClipboard ;
     end ;


procedure TEventDetFrm.CopyImageToClipboard ;
{ -------------------------------------------
  Copy display image to clipboard as metafile
  -------------------------------------------}
begin

     if Page.ActivePage = DetectEventsPage then begin
        // Detect events display
        PrintRecFrm.Destination := deClipboard ;
        PrintRecFrm.Display := scDisplay ;
        PrintRecFrm.ShowModal ;
        if PrintRecFrm.ModalResult = mrOK then scDisplay.CopyImageToClipboard ;
        end
     else if Page.ActivePage = EditEventsPage then begin
        // Edit events display
        PrintRecFrm.Destination := deClipboard ;
        PrintRecFrm.Display := scEditDisplay ;
        PrintRecFrm.ShowModal ;
        if PrintRecFrm.ModalResult = mrOK then scEditDisplay.CopyImageToClipboard ;
        end
     else if Page.ActivePage = XYPlotPage then begin
        // X-Y plot display
       PrintGraphFrm.Plot := plPlot ;
       PrintGraphFrm.ToPrinter := False ;
       PrintGraphFrm.ShowModal ;
       if PrintGraphFrm.ModalResult = mrOK then plPlot.CopyImageToClipboard ;
       end
     else if Page.ActivePage = HistPage then begin
        // X-Y plot display
       PrintGraphFrm.Plot := plHist ;
       PrintGraphFrm.ToPrinter := False ;
       PrintGraphFrm.ShowModal ;
       if PrintGraphFrm.ModalResult = mrOK then plHist.CopyImageToClipboard ;
       end
     else if Page.ActivePage = AveragePage then begin
        // Average events display
        PrintRecFrm.Destination := deClipboard ;
        PrintRecFrm.Display := scAverageDisplay ;
        PrintRecFrm.ShowModal ;
        if PrintRecFrm.ModalResult = mrOK then scAverageDisplay.CopyImageToClipboard ;
        end

     end ;


function TEventDetFrm.IsClipboardDataAvailable : Boolean ;
// ----------------------------------------------------------
// Is data (data or image) available for copying to clipboard?
// ----------------------------------------------------------
begin
     Result := False ;
     if Page.ActivePage = DetectEventsPage then begin
        Result := True ;
        end
     else if Page.ActivePage = EditEventsPage then begin
        if NumEvents > 0 then Result := True ;
        end
     else if Page.ActivePage = XYPlotPage then begin
        Result := PlotAvailable ;
        end
     else if Page.ActivePage = HistPage then begin
        Result := HistAvailable ;
        end
     else if Page.ActivePage = AveragePage then begin
        if NumEvents > 0 then Result := True ;
        end ;

     end ;


procedure TEventDetFrm.PrintDisplay ;
{ ------------------------------------
  Print record graph or histogram on display
  ------------------------------------ }
begin

     if Page.ActivePage = DetectEventsPage then begin
        // Print detect events display
        PrintRecFrm.Destination := dePrinter ;
        PrintRecFrm.Display := scDisplay ;
        PrintRecFrm.ShowModal ;
        if PrintRecFrm.ModalResult = mrOK then begin
           scDisplay.ClearPrinterTitle ;
           scDisplay.AddPrinterTitleLine('File : ' + cdrFH.FileName ) ;
           scDisplay.AddPrinterTitleLine( CdrFH.IdentLine ) ;
           scDisplay.Print ;
          end ;
        end
     else if Page.ActivePage = EditEventsPage then begin
        // Print edit events display
        PrintRecFrm.Destination := dePrinter ;
        PrintRecFrm.Display := scEditDisplay ;
        PrintRecFrm.ShowModal ;
        if PrintRecFrm.ModalResult = mrOK then begin
           PrintRecFrm.Display.ClearPrinterTitle ;
           PrintRecFrm.Display.AddPrinterTitleLine('File : ' + cdrFH.FileName ) ;
           PrintRecFrm.Display.AddPrinterTitleLine( CdrFH.IdentLine ) ;
           PrintRecFrm.Display.Print ;
          end ;
        end
     else if Page.ActivePage = XYPlotPage then begin
        // Print X-Y plot
       PrintGraphFrm.Plot := plPlot ;
       PrintGraphFrm.ToPrinter := True ;
       PrintGraphFrm.ShowModal ;
       if PrintGraphFrm.ModalResult = mrOK then begin
          plPlot.ClearPrinterTitle ;
          plPlot.AddPrinterTitleLine( 'File ... ' + cdrFH.FileName ) ;
          plPlot.AddPrinterTitleLine( CdrFH.IdentLine ) ;
          plPlot.Print ;
          end ;
       end
     else if Page.ActivePage = HistPage then begin
        // Print Histogram
       PrintGraphFrm.Plot := plHist ;
       PrintGraphFrm.ToPrinter := True ;
       PrintGraphFrm.ShowModal ;
       if PrintGraphFrm.ModalResult = mrOK then begin
          plHist.ClearPrinterTitle ;
          plHist.AddPrinterTitleLine( 'File ... ' + cdrFH.FileName ) ;
          plHist.AddPrinterTitleLine( CdrFH.IdentLine ) ;
          plHist.Print ;
          end ;
       end
     else if Page.ActivePage = AveragePage then begin
        // Print average events display
        PrintRecFrm.Destination := dePrinter ;
        PrintRecFrm.Display := scAverageDisplay ;
        PrintRecFrm.ShowModal ;
        if PrintRecFrm.ModalResult = mrOK then begin
           PrintRecFrm.Display.ClearPrinterTitle ;
           PrintRecFrm.Display.AddPrinterTitleLine(
                                                'File : ' + cdrFH.FileName ) ;
           PrintRecFrm.Display.AddPrinterTitleLine( CdrFH.IdentLine ) ;
           PrintRecFrm.Display.Print ;
          end ;
        end ;

    end ;


procedure TEventDetFrm.scDisplayCursorChange(Sender: TObject);
// -------------------------------
// Display cursor position changed
// -------------------------------
var
     iCursorPos,ch : Integer ;
begin

     // Align detection display cursor
     iCursorPos := scDisplay.VerticalCursors[DisplayCursor] ;
     if scDetDisplay.VerticalCursors[DisplayCursor] <> iCursorPos then
        scDetDisplay.VerticalCursors[DisplayCursor] := iCursorPos ;

     for ch := 0 to scDisplay.NumChannels-1 do if scDisplay.ChanVisible[ch] then begin
         { Get signal baseline cursor }
         Channel[ch].ADCZero := scDisplay.HorizontalCursors[BaseLineCursor] ;
         Channel[ch].yMin := scDisplay.yMin[ch] ;
         Channel[ch].yMax := scDisplay.yMax[ch] ;
         end ;

     end;


procedure TEventDetFrm.bSetThresholdTo4SDClick(Sender: TObject);
// -------------------------------------------------------
// Set detection threshold to 4 x S.D. of background noise
// -------------------------------------------------------
var
    i,j : Integer ;
    Sum,Mean,SD : Single ;
begin

    // Calculate mean value
    Sum := 0.0 ;
    j := Channel[cbChannel.ItemIndex].ChannelOffset ;
    for i := 0 to scDetDisplay.MaxPoints-1 do begin
        Sum := Sum + DetBuf^[j] ;
        j := j + CdrFH.NumChannels ;
        end ;
    Mean := Sum / scDetDisplay.MaxPoints ;

    // Calculate sum of squares
    Sum := 0.0 ;
    j := Channel[cbChannel.ItemIndex].ChannelOffset ;
    for i := 0 to scDetDisplay.MaxPoints-1 do begin
        Sum := Sum + (DetBuf^[j] - Mean)*(DetBuf^[j] - Mean) ;
        j := j + CdrFH.NumChannels ;
        end ;

    // Standard deviation
    SD := Sqrt( Sum/(scDetDisplay.MaxPoints-1) ) ;

    // Set threshold cursor
    edThreshold.Value := SD*4.0 ;
    scDetDisplay.HorizontalCursors[0] := Round(edThreshold.Value) ;

    end;


procedure TEventDetFrm.bSetPlotAxesClick(Sender: TObject);
{ ------------------------------
  Set plot axes range/law/labels
  ------------------------------}
begin
     SetAxesFrm.Plot := plPlot ;
     SetAxesFrm.Histogram := False ;
     SetAxesFrm.ShowModal ;
     end;


procedure TEventDetFrm.SetupDetector ;
// -------------------------------
// Set up event detection controls
// -------------------------------
begin

     if rbThreshold.Checked then begin
        // Base-line tracking threshold detection
        edThreshold.Units := Channel[cbChannel.ItemIndex].ADCUnits ;
        edThreshold.Scale := Channel[cbChannel.ItemIndex].ADCScale ;
        edTimeThreshold.Visible := True ;
        lbTimeThreshold.Visible := True ;
        ModePage.ActivePage := 'Threshold' ;
        end
     else if rbRateOfRise.Checked then begin
        // Rate of rise detection
        edThreshold.Units := Channel[cbChannel.ItemIndex].ADCUnits
                             + '/' + Settings.TUnits ;
        edThreshold.Scale := 0.1*Channel[cbChannel.ItemIndex].ADCScale /
                             (CdrFH.dt*Settings.TScale) ;
        edTimeThreshold.Visible := False ;
        lbTimeThreshold.Visible := False ;
        ModePage.ActivePage := 'RateOfRise' ;

        end
     else if rbPatternMatch.Checked then begin
        // Pattern match detection
        edThreshold.Units := ' ' ;
        edThreshold.Scale := 0.01 ;
        edTimeThreshold.Visible := False ;
        lbTimeThreshold.Visible := False ;
        ModePage.ActivePage := 'Template' ;
        end ;

     DisplayRecord ;

     end ;


procedure TEventDetFrm.rbThresholdClick(Sender: TObject);
// ----------------------------
// Event detection mode changed
// ----------------------------
begin
     // Set up event detection controls
     SetupDetector ;
     end;


procedure TEventDetFrm.edTauRiseKeyPress(Sender: TObject; var Key: Char);
// -------------------------------------------------------
// Set time constant of rising phase of detection template
// -------------------------------------------------------
begin
     if key = #13 then DisplayRecord ;
     end;


procedure TEventDetFrm.edThresholdKeyPress(Sender: TObject; var Key: Char);

begin
     if key = #13 then begin
        scDetDisplay.HorizontalCursors[ThresholdCursor] := Round(edThreshold.Value) ;
        end ;
     end;


procedure TEventDetFrm.bExportNonEventsClick(Sender: TObject);
// -------------------------------------------------------
// Export A/D sample data between events to .EDR data file
// -------------------------------------------------------
const
     NumScansPerBuffer = 256 ;
var
     i,j,ch, NumPretrigger : Integer ;
     RdScan,WrScan,RdEnd,WrEnd : Integer ;
     NumScansInFile,NumScansPerEvent : Integer ;
     iEvent,EventStart : Integer ;
     ExpFH : TCDRFileHeader ;
     InBuf,OutBuf : Array[0..NumScansPerBuffer*(ChannelLimit+1)-1] of SmallInt ;
     YLast,YOffset : Array[0..EDRChannelLimit] of SmallInt ;
     NewYOffsetNeeded : Boolean ;
begin

     bExportToWCPFile.Enabled := False ;
     bAbortExport.Enabled := True ;
     bExportNonEvents.Enabled := False ;

     // Present user with standard Save File dialog box
     Main.SaveDialog.options := [ofOverwritePrompt,ofHideReadOnly,ofPathMustExist] ;
     Main.SaveDialog.DefaultExt := DataFileExtension ;
     Main.SaveDialog.FileName := ExtractFileName(CdrFH.FileName)
                                 + '(-events)' ;
     Main.SaveDialog.Filter := format( ' EDR Files (*%s)|*%s',
                                  [DataFileExtension,DataFileExtension]) ;
     Main.SaveDialog.Title := 'Export to EDR File' ;

     { Create new data file }
     if Main.SaveDialog.execute then begin

        // Copy EDR file header data
        ExpFH := CDRFH ;

        // Create an empty EDR file to hold extracted data
        ExpFH.FileHandle := FileCreate( Main.SaveDialog.FileName ) ;
        ExpFH.FileName := Main.SaveDialog.FileName ;
        if ExpFH.FileHandle < 0 then ShowMessage( 'Error creating '
                                     + Main.SaveDialog.FileName ) ;

        // Initialise extraction loop

        NumScansInFile := CDRFH.NumSamplesInFile div CDRFH.NumChannels ;
        NumScansPerEvent := scEditDisplay.MaxPoints ;
        NumPretrigger := Round(NumScansPerEvent*edPreTrigger.Value) ;
        RdScan := 0 ;
        RdEnd := 0 ;
        WrScan := 0 ;
        WrEnd := NumScansPerBuffer ;
        j := 0 ;
        i := 0 ;
        iEvent := Round(edExportRange.LoValue-1) ;
        if iEvent < Round(edExportRange.HiValue) then
           EventStart := Events[iEvent] - NumPreTrigger
        else EventStart := NumScansInFile ;

        for ch := 0 to CdrFH.NumChannels-1 do YOffset[ch] := 0 ;
        NewYOffsetNeeded := False ;

        // Extract A/D samples between events

        while RdScan < NumScansInFile do begin

            // Read A/D samples from file
            if (RdScan >= RdEnd) then begin
               ReadCDRBuffer(CdrFH,RdScan,InBuf,NumScansPerBuffer) ;
               RdEnd := RdScan + NumScansPerBuffer ;
               i := 0 ;
               // Report progress
               Main.StatusBar.SimpleText := format(
               ' Exporting inter-event gaps %.0f/%.0fs to %s',
               [rdScan*CdrFH.dt,NumScansInFile*CdrFH.dt,ExpFH.FileName] ) ;

               Application.ProcessMessages ;

               end ;

            if RdScan >= EventStart then begin
               // Skip A/D samples containing detected event
               RdScan := RdScan + NumScansPerEvent ;
               Inc(iEvent) ;
               if iEvent <= NumEvents then EventStart := Events[iEvent] - NumPreTrigger
                                      else EventStart := NumScansInFile ;
               NewYOffsetNeeded := True ;
               end
            else begin
               // Copy A/D samples not containing event to O/P buffer
               if NewYOffsetNeeded then begin
                  for ch := 0 to CdrFH.NumChannels-1 do begin
                      YOffset[ch] := {YLast[ch] - InBuf[i+ch]} 0 ;
                      end ;
                  NewYOffsetNeeded := False ;
                  end ;
               for ch := 0 to CdrFH.NumChannels-1 do begin
                  OutBuf[j] := InBuf[i] + YOffset[ch] ;
                  YLast[ch] := OutBuf[j] ;
                  Inc(i) ;
                  Inc(j) ;
                  end ;
               Inc(RdScan) ;
               Inc(WrScan) ;
               end ;

            // Write A/D samples to export file
            if WrScan >= WrEnd then begin
               WriteCDRBuffer(ExpFH,WrScan-NumScansPerBuffer,OutBuf,NumScansPerBuffer) ;
               j := 0 ;
               WrEnd := WrScan + NumScansPerBuffer ;
               end ;


            end ;

        // Update file header
        ExpFH.NumSamplesInFile := (WrEnd-NumScansPerBuffer)*CdrFH.NumChannels ;
        SaveCDRHeader( ExpFH ) ;

        // Close EDR export file
        if ExpFH.FileHandle >= 0 then FileClose( ExpFH.FileHandle ) ;

        // Final report
        Main.StatusBar.SimpleText := format(
        ' Export Gaps : %.3g%% of file exported to %s',
        [(100.0*ExpFH.NumSamplesInFile)/CdrFH.NumSamplesInFile,
         ExpFH.FileName] ) ;
        WriteToLogFile( Main.StatusBar.SimpleText ) ;

        end ;

     bExportToWCPFile.Enabled := True ;
     bAbortExport.Enabled := False ;
     bExportNonEvents.Enabled := True ;

     end;


procedure TEventDetFrm.edEditDisplayWidthKeyPress(Sender: TObject;
  var Key: Char);
// ---------------------------------------
// Update no. sample scans in edit display
// ---------------------------------------
begin
     if key = #13 then begin
        Settings.EventDetector.AnalysisWindow := edEditDisplayWidth.Value ;
        NewFile ;
        DisplayEvent ;
        end ;
     end;


procedure TEventDetFrm.cbChannelChange(Sender: TObject);
// ----------------------------
// Change channel to be scanned
// ----------------------------
begin
     Settings.EventDetector.Channel := cbChannel.ItemIndex ;
     NewFile ;
     end;


procedure TEventDetFrm.bNewHistogramClick(Sender: TObject);
{ -------------------------------------
  Request a new Histogram to be plotted
  -------------------------------------}
var
   i,n,VarType,iEvent,StartAtEvent,EndAtEvent,EndOfRateInterval,XNum : Integer ;
   X,XMin,XMax,XMean,XSum,Y, HScale,HLowerLimit,Temp : Single ;
   XUnits : string ;
   OK : Boolean ;
   XBuf : PSingleArray ;
begin

     GetMem( XBuf, Max(NumEvents,1)*4 ) ;

     bNewHistogram.Enabled := false ;
     ComputationInProgress := True ;
     AbortFlag := False ;


     Main.StatusBar.SimpleText := 'Event Detector : WAIT ... Plotting Histogram ' ;

     if rbHistAllEvents.Checked then begin
        { Use all Events }
        StartAtEvent := 0 ;
        EndAtEvent := NumEvents - 1 ;
        end
     else begin
        { Use selected range of records }
        StartAtEvent := Round(edHistRange.LoValue)-1 ;
        EndAtEvent := Round(edHistRange.HiValue)-1 ;
        end ;

     // X axis variable
     VarType := Integer(cbHistVar.Items.Objects[cbHistVar.ItemIndex]) ;
     xUnits := GetVariableUnits( VarType ) ;

     // Read measurement into XBuf and determine min. - max. limits

     iEvent := StartAtEvent ;
     XNum :=  0 ;
     Hist.RangeLo := 1E30 ;
     Hist.RangeHi := -1E30 ;
     while iEvent < EndAtEvent do begin

          OK := CalculateVariables( iEvent,
                                    VarType,
                                    VarType,
                                    X,
                                    Y ) ;
          if OK then begin
             if X < Hist.RangeLo then Hist.RangeLo := X ;
             if X > Hist.RangeHi then Hist.RangeHi := X ;
             XBuf^[XNum] := X ;
             Inc(XNum) ;
             end ;

          if (xNum >= MaxEvents) then Break ;
          if AbortFlag then begin
             bNewHistogram.Enabled := True ;
             FreeMem( XBuf ) ;
             Exit ;
             end ;
          end ;

     // Clear histogram and set up bin ranges
     if not BinRangePanel.Visible then begin
        Hist.NumBins := Round(edNumBins.Value) ;
        edBinsUpper.Value := Hist.RangeHi ;
        edBinsLower.Value := Hist.RangeLo ;
        edBinWidth.Value := (Hist.RangeHi - Hist.RangeLo) / Hist.NumBins ;
        BinRangePanel.Visible := True ;
        edBinWidth.Units := xUnits ;
        edBinsLower.Units := edBinWidth.Units ;
        edBinsUpper.Units := edBinWidth.Units ;
        end ;

     if edBinsUpper.Value <= edBinsLower.Value then
        edBinsUpper.Value := edBinsLower.Value + 1.0 ;
     Hist.RangeLo :=  edBinsLower.Value ;
     edBinsLower.Value := Hist.RangeLo ;  // Ensure units is updated
     Hist.RangeHi :=  edBinsUpper.Value ;
     edBinsUpper.Value := Hist.RangeHi ;     // Ensure units is updated
     edBinWidth.Value := (edBinsUpper.Value - edBinsLower.Value)/Round(edNumBins.Value) ;
     Hist.BinWidth := edBinWidth.Value ;
     Hist.NumBins := Round(edNumBins.Value) ;

     Hist.MaxBin := Hist.NumBins - 1 ;
     x := Hist.RangeLo ;
     for i := 0 to Hist.NumBins-1 do begin
         Hist.Bins[i].Lo := x ;
         Hist.Bins[i].Mid := x + Hist.BinWidth / 2. ;
         Hist.Bins[i].Hi := x + Hist.BinWidth ;
         Hist.Bins[i].y := 0. ;
         x := x + Hist.BinWidth ;
         end ;

     // Fill Histogram from XBuf

     HScale := (Hist.NumBins) / ( Hist.Bins[Hist.NumBins-1].Mid
                                         - Hist.Bins[0].Mid ) ;
     HLowerLimit := Hist.Bins[0].Lo ;
     XMin := 1E30 ;
     XMax := -XMin ;
     XSum := 0.0 ;
     for iEvent := 0 to XNum-1 do begin
         X := XBuf[iEvent] ;
         i := Round( (x - HLowerLimit) * HScale ) ;
         i := Max( Min( i,Hist.NumBins-1 ), 0 ) ;
         Hist.Bins[i].y := Hist.Bins[i].y + 1. ;
         if Hist.Bins[i].y > Hist.yHi then Hist.yHi := Hist.Bins[i].y ;
         if XMin >= X then XMin := X ;
         if XMax <= X then XMax := X ;
         XSum := XSum + X ;
         end ;

     { Plot new Histogram }
     plHist.xAxisAutoRange := False ;
     plHist.xAxisMin := Hist.Bins[0].Lo ;
     plHist.xAxisMax := Hist.Bins[Hist.NumBins-1].Hi ;
     plHist.XAxisTick := (plHist.xAxisMax - plHist.xAxisMin) / 5.0 ;

     plHist.HistogramCumulative := ckCumulative.Checked ;
     plHist.HistogramPercentage := ckPercentage.Checked ;

     { Create X and Y axes labels }
     plHist.xAxisLabel := cbHistVar.Text + ' (' + xUnits + ')' ;

     plHist.yAxisAutoRange := True ;
     if ckPercentage.Checked then plHist.yAxisLabel := '%'
                             else plHist.yAxisLabel := ' ' ;

     { Create Histogram plot }
     plHist.CreateHistogram( 0 ) ;
     for i := 0 to Hist.NumBins-1 do plHist.AddBin( 0,
                                     Hist.Bins[i].Lo,
                                     Hist.Bins[i].Mid,
                                     Hist.Bins[i].Hi,
                                     Hist.Bins[i].y ) ;

     { Create and set Histogram cursors to initial positions }

     { Initial cursor positions }
     i := 0 ;
     while (Hist.Bins[i].y = 0.0) and (i < Hist.MaxBin) do Inc(i) ;
     plHist.VerticalCursors[HistCurs.C0] := Hist.Bins[i].Mid ;

     i := Hist.MaxBin ;
     while (Hist.Bins[i].y = 0.0) and (i > 0) do Dec(i) ;
     plHist.VerticalCursors[HistCurs.C1] := Hist.Bins[i].Mid ;

     plHist.VerticalCursors[HistCurs.Read] := Hist.Bins[Hist.MaxBin div 2].Mid ;

     // Calculate mean
     if XNum > 0 then XMean := XSum / XNum
                 else XMean := 0.0 ;

     meHistResults.Clear ;
     meHistResults.Lines.Add(format('Mean = %.5g %s',[XMean,XUnits])) ;
     meHistResults.Lines.Add(format('Min. = %.5g %s',[XMin,XUnits])) ;
     meHistResults.Lines.Add(format('Max. = %.5g %s',[XMax,XUnits])) ;
     meHistResults.Lines.Add(format('n= %d',[XNum])) ;

     HistAvailable := True ;
     bSetHistAxes.Enabled := HistAvailable ;
     bNewHistogram.Enabled := True ;
     Main.StatusBar.SimpleText := '' ;
     ComputationInProgress := False ;

     FreeMem( XBuf ) ;

     end;


procedure TEventDetFrm.bAddFilterClick(Sender: TObject);
// ----------------------------------
// Add a new filter criterion to set
// ----------------------------------
var
     i : Integer ;
begin
     // Find next empty filter slot
     i := 0 ;
     while Filters[i].Use and (i<High(Filters)) do Inc(i) ;
     // Add filter criteria
     if not Filters[i].Use then begin
        Filters[i].Use := True ;
        Filters[i].Variable := Integer(cbVariable.Items.Objects[cbVariable.ItemIndex]) ;
        Filters[i].LoLimit := edLoLimit.Value ;
        Filters[i].HiLimit := edHiLimit.Value ;
        end
     else bAddFilter.Enabled := False ;

     // Update filter criteria list
     UpdateFilterSet( Filters ) ;
     end;


procedure TEventDetFrm.bDeleteClick(Sender: TObject);
// ---------------------------------
// Delete all filter criteria in set
// ---------------------------------
var
     i : Integer ;
begin
     for i := 0 to High(Filters) do Filters[i].Use := False ;
     UpdateFilterSet( Filters ) ;
     bAddFilter.Enabled := True ;
     end;


procedure TEventDetFrm.UpdateFilterSet(
          Filters : Array of TFilter ) ;
// ------------------------------
// Update list of filter criteria
// ------------------------------
var
    Action : string ;
    i : Integer ;
begin

    meFilterSet.Clear ;

    // Filter combination mode

    // Update filter list
    for i := 0 to High(Filters) do if Filters[i].Use then begin

        // Add criterion combination mode to end of line
        if rbAND.Checked then Action := ' AND'
                         else Action := ' OR' ;

        // Clear combination mode from last line
        if i <  High(Filters) then begin
           if not Filters[i+1].Use then Action := '' ;
           end
        else Action := '' ;

        meFilterSet.Lines.Add(format('%.3g <= %s <= %.3g %s',
        [Filters[i].LoLimit,
         VarNames[Filters[i].Variable],
         Filters[i].HiLimit,
         Action] )) ;

        end ;
    end ;


procedure TEventDetFrm.bApplyClick(Sender: TObject);
// ----------------------------------
// Select events using filter critera
// ----------------------------------
var
     iEvent : Integer ;                     // Event no.
     Event : TEventAnalysis ;               // Event waveform anaysis results
     i : Integer ;                          // Counter
     Results : Array[0..MaxVar] of Single ; // Event analysis results
     Val : Single ;                         //
     Match : Boolean ;           // Criterion match flag
     RemoveEvent : Boolean ;     // Combined criterion
     NumDeleted : Integer ;                 // No. events deleted
begin

     iEvent := 0 ;
     NumDeleted := 0 ;
     while iEvent < NumEvents do begin

         // Analyse event waveform
         AnalyseEvent( iEvent, Event ) ;
         Results[vPeak] := Event.Peak ;
         Results[vArea] := Event.Area ;
         Results[vTRise] := Event.TRise ;
         Results[vTDecay] := Event.TDecay ;
         Results[vTauDecay] := Event.TauDecay ;
         Results[vDuration] := Event.Duration ;

         if iEvent > 0 then
            Results[vInterval] := (Events[iEvent] - Events[iEvent-1])*CDRFH.dt
         else Results[vInterval] := 0.0 ;

         Results[vEventNum] := iEvent ;
         Results[vTime] := Events[iEvent]*CDRFH.dt ;

        // Determine if event meets the rejection criteria
        if rbAND.Checked then RemoveEvent := True
                         else RemoveEvent := False ;
        for i := 0 to High(Filters) do if Filters[i].Use then begin

            // Criterion match
            Val := Results[Filters[i].Variable] ;
            if (Val >= Filters[i].LoLimit) and
               (Val <= Filters[i].HiLimit) then
               Match := True
            else Match := False ;

            // Combine with results of previous criteria
            if rbAND.Checked then RemoveEvent := RemoveEvent AND Match
                             else RemoveEvent := RemoveEvent OR Match ;

            end ;

        // Remove event if it matches criteria
        if RemoveEvent then begin
           for i := iEvent to NumEvents-1 do Events[i] := Events[i+1] ;
           Dec(NumEvents) ;
           Inc(NumDeleted) ;
           end
        else Inc(iEvent) ;

        Main.StatusBar.SimpleText :=  format(
        ' Filtering Events %d/%d (%d events deleted)',
        [iEvent+1,NumEvents,NumDeleted] ) ;

        EventAnalysisFileUpdateRequired := True ;
        BaselineSplinesAvailable := False ;

        end ;

     Main.StatusBar.SimpleText :=  format(
     ' Event Filter : Completed (%d events deleted)',
     [NumDeleted] ) ;
     WriteToLogFile(Main.StatusBar.SimpleText) ;

     sbEvent.Position := Min( sbEvent.Position, NumEvents ) ;
     sbEvent.Max := Max( NumEvents,1 ) ;
     Application.ProcessMessages ;

     // Update event list file
     SaveEventList ;

     // Update controls on edit events page
     UpdateEditEventPage ;

     end ;


procedure TEventDetFrm.cbHistVarChange(Sender: TObject);
// ------------------------------------
// Histogram variable selection changed
// ------------------------------------
begin
     BinRangePanel.Visible := False ;
     end;


procedure TEventDetFrm.bLoadEventListClick(Sender: TObject);
// ------------------------------------------
// Load list of event detection times from file
// ------------------------------------------
var
   Time : Single ;
   InFile : TextFile ;
begin

     // Get the name of a data file to hold ezported data
     OpenDialog.options := [ofPathMustExist] ;
     OpenDialog.DefaultExt := '.txt' ;
     OpenDialog.Filter := ' Text Files (*.txt)|*.txt' ;
     OpenDialog.Title := 'Load event times';

     if OpenDialog.execute then begin
        // Open text file
        AssignFile( InFile, OpenDialog.FileName ) ;
        Reset( InFile ) ;
        // Write data to file
        NumEvents := 0 ;
        While not EOF(InFile) do begin
            ReadLn(InFile, Time ) ;
            Events[NumEvents] := Round(Time/CdrFH.dt) ;
            if NumEvents < High(Events) then Inc(NumEvents) ;
            end ;
        CloseFile( InFile ) ;
        end ;

    Main.StatusBar.SimpleText := format( 'Detect Events : Event list (%d events) loaded from %s',
                                 [NumEvents,OpenDialog.FileName]) ;
    WriteToLogFile(Main.StatusBar.SimpleText) ;

    // Update controls on edit events page
    UpdateEditEventPage ;

    // Update event list file
    SaveEventList ;

    EventAnalysisFileUpdateRequired := True ;
    BaselineSplinesAvailable := False ;

    end ;


procedure TEventDetFrm.bSaveEventListClick(Sender: TObject);
// ------------------------------------------
// Save list of event detection times to file
// ------------------------------------------
var
   i : Integer ;
   Time : Single ;
   OutFile : TextFile ;

begin

     // Get the name of a data file to hold exported data
     SaveDialog.options := [ofPathMustExist] ;
     SaveDialog.DefaultExt := '.txt' ;
     SaveDialog.Filter := ' Text Files (*.txt)|*.txt' ;
     SaveDialog.Title := 'Save event times';

     SaveDialog.FileName := AnsiReplaceStr(
                            LowerCase(ExtractFileName(CdrFH.FileName)),
                                 '.edr',
                                 ' (event list).txt' ) ;

     if SaveDialog.execute then begin
        // Open text file
        AssignFile( OutFile, SaveDialog.FileName ) ;
        ReWrite( OutFile ) ;
        // Write data to file
        for i := 0 to NumEvents-1 do begin
            Time := Events[i]*CdrFH.dt ;
            WriteLn(OutFile, format( '%.6g', [Time] ))
            end ;
        CloseFile( OutFile ) ;

        Main.StatusBar.SimpleText := format( 'Detect Events : Event list (%d events) saved to %s',
                                 [NumEvents,OpenDialog.FileName]) ;
        WriteToLogFile(Main.StatusBar.SimpleText) ;

        end ;


    end ;


procedure TEventDetFrm.cbVariableChange(Sender: TObject);
// -----------------------------
// Event filter variable changed
// -----------------------------
begin
     edLoLimit.Units := GetVariableUnits(Integer(cbVariable.Items.Objects[cbVariable.ItemIndex])) ;
     edHiLimit.Units := edLoLimit.Units ;
     end;


function TEventDetFrm.GetVariableUnits(
         VarNum : Integer
         ) : String ;
// --------------------------------
// Return units of variable VarNum
// --------------------------------
begin
     case VarNum of
        vEventNum : Result := '' ;
        vTime,vInterval : Result := 's' ;
        vTRise,vTDecay,vTauDecay,vDuration : Result := 'ms' ;
        vFrequencyAvg,vFrequencyInst : Result := 'Hz' ;
        vPeak,vBaseline : Result := Channel[cbChannel.ItemIndex].ADCUnits ;
        vArea : Result := Channel[cbChannel.ItemIndex].ADCUnits + '.ms' ;
        end ;
     end;


procedure TEventDetFrm.edLoLimitKeyPress(Sender: TObject; var Key: Char);
begin
     if Key = #13 then begin
        edLoLimit.Value := edLoLimit.Value ;
        edHiLimit.Value := edHiLimit.Value ;
        end ;
     end;


procedure TEventDetFrm.edHiLimitKeyPress(Sender: TObject; var Key: Char);
begin
     if Key = #13 then begin
        edLoLimit.Value := edLoLimit.Value ;
        edHiLimit.Value := edHiLimit.Value ;
        end ;
     end;


procedure TEventDetFrm.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
// -------------------------------
// Special control keys processing
// -------------------------------
begin
     Case Key of
          VK_F2 : bDeleteEvent.Click ;
          VK_F1 : bInsertEvent.Click ;
          end ;
     end;


procedure TEventDetFrm.edAnalysisWindowKeyPress(Sender: TObject;
  var Key: Char);
// ------------------------------------------------------
// Re-display event when analysis window duration changed
// ------------------------------------------------------
begin
     if Key = #13 then DisplayEvent ;
     EventAnalysisFileUpdateRequired := True ;
     end ;


procedure TEventDetFrm.edDeadTimeKeyPress(Sender: TObject; var Key: Char);
// ----------------------------------------------
// Update display when dead time has been changed
// ----------------------------------------------
begin
     if Key = #13 then begin
        Settings.EventDetector.DeadTime := edDeadTime.Value ;
        DisplayRecord ;
        end ;

     end;

     
procedure TEventDetFrm.edRunningMeanDurationKeyPress(Sender: TObject;
  var Key: Char);
// --------------------------------------------------
// Update display when running mean has been changed
// --------------------------------------------------
begin
     if Key = #13 then begin
        DisplayRecord ;
        end ;
     end;

procedure TEventDetFrm.rbANDClick(Sender: TObject);
begin
     UpdateFilterSet( Filters ) ;
     end;

procedure TEventDetFrm.rbPositiveClick(Sender: TObject);
// ---------------------------
// Analysis polarity changed
// ---------------------------
begin

     Settings.EventDetector.PositivePeaks := rbPositive.Checked ;
     SaveCDRHeader( CDRFH ) ;
     DisplayEvent ;
     EventAnalysisFileUpdateRequired := True ;
     end ;


procedure TEventDetFrm.bHistFitCurveClick(Sender: TObject);
{ ---------------------------------------------------
  Fit a gaussian prob. density functions to histogram
  --------------------------------------------------- }
const
     NumFitPoints = 500 ;
     FittedLine = 1 ;
var
   i,iStart,iEnd,iBins, Comp,LineNum,NumComp : Integer ;
   x,dx : single ;
   ParTemp : Array[0..LastParameter] of single ;
begin

    lbHistResults.Caption := '' ;
    CVFit.Equation := TCFEqnType(cbHistEqn.Items.Objects[cbHistEqn.ItemIndex]) ;
    CVFit.xUnits := GetVariableUnits( Integer(cbHistVar.Items.Objects[cbHistVar.ItemIndex]) ) ;
    CVFit.YUnits := '' ;

    // Add points to curve fitter
    CVFit.ClearPoints ;

    { Clear all existing lines on plot }
    plHist.ClearAllLines ;

    if CVFit.Equation = None then begin
        plHist.Invalidate ;
        Exit ;
        end;

    { Lower and upper x data limit set by display cursors }
    iStart := Min( plHist.FindNearestIndex( 0, HistCurs.C0 ),
                   plHist.FindNearestIndex( 0, HistCurs.C1 )) ;
    iEnd := Max( plHist.FindNearestIndex( 0, HistCurs.C0 ),
                 plHist.FindNearestIndex( 0, HistCurs.C1 )) ;
    for iBins := iStart to iEnd do begin
        CVFit.AddPoint( Hist.Bins[iBins].Mid, Hist.Bins[iBins].y ) ;
        end ;

    if CVFit.NumPoints < CVFit.NumParameters then begin
       ShowMessage('Not enough points to fit curve to histogram' ) ;
       Exit ;
       end ;

    // Let user set fitting parameters
    CVFit.ParametersSet := False ;
    SetFitPars1frm.CVFit := CVFit ;
    SetFitPars1frm.ShowModal ;
    if SetFitPars1frm.ModalResult <> mrOK then Exit ;

    // Find best fit curve
    CVFit.FitCurve ;

    x := plHist.xAxisMin ;
    dx := (plHist.xAxisMax - plHist.xAxisMin) / NumFitPoints ;
    plHist.ShowLines := True ;
    plHist.CreateLine( FittedLine, clRed, msNone, psSolid ) ;
    for i := 0 to NumFitPoints-1 do begin
        plHist.AddPoint( FittedLine, x, CVFit.EquationValue(x) ) ;
         x := x + dx ;
         end ;

    { Save parameters and initialise gaussian component lines }
    if (CVFit.Equation = Gaussian2) or
       (CVFit.Equation = Gaussian3) then begin

       NumComp := CVFit.NumParameters div 3 ;
       LineNum := FittedLine ;
       for Comp := 0 to NumComp-1 do begin
          Inc(LineNum) ;
          plHist.CreateLine( LineNum, clRed, msNone, psSolid ) ;
          ParTemp[Comp*3] := CVFit.Parameters[Comp*3] ;
          ParTemp[Comp*3+1] := CVFit.Parameters[Comp*3+1] ;
          ParTemp[Comp*3+2] := CVFit.Parameters[Comp*3+2] ;
          CVFit.Parameters[Comp*3+2] := 0.0 ;
          end ;

       { Plot each individual gaussian component }
       if NumComp > 1 then begin
          LineNum := FittedLine ;
          for Comp := 0 to NumComp-1 do begin
             CVFit.Parameters[Comp*3+2] := ParTemp[Comp*3+2] ;
             Inc(LineNum) ;
             x := plHist.xAxisMin ;
             dx := (plHist.xAxisMax - plHist.xAxisMin) / NumFitPoints ;
             for i := 0 to Hist.NumBins-1 do begin
                plHist.AddPoint( LineNum, x, CVFit.EquationValue(x) ) ;
                x := x + dx ;
                end ;
             CVFit.Parameters[Comp*3+2] := 0.0 ;
             end ;
          end ;

       { Restore parameters }
       for Comp := 0 to NumComp-1 do begin
          CVFit.Parameters[Comp*3] := ParTemp[Comp*3] ;
          CVFit.Parameters[Comp*3+1] := ParTemp[Comp*3+1] ;
          CVFit.Parameters[Comp*3+2] := ParTemp[Comp*3+2] ;
          end ;

       end ;

     { Make sure plot is updated with changes }
     plHist.Invalidate ;

    lbHistResults.Caption := CVFit.FitResults ;

     end ;



procedure TEventDetFrm.bSetHistAxesClick(Sender: TObject);
{ -----------------------------------
  Set histogram axes range/law/labels
  -----------------------------------}
begin
     SetAxesFrm.Plot := plHist ;
     SetAxesFrm.Histogram := True ;
     SetAxesFrm.ShowModal ;
     end;


procedure TEventDetFrm.bDoAverageClick(Sender: TObject);
// ----------------------------------
// Compute average of detected events
// ----------------------------------
const
     NumAvgRequired = 10 ;
var
     StartAtEvent : Integer ;  // First event in averaging range
     EndAtEvent : Integer ;    // Last event
     iEvent : Integer ;        // Event index
     i,j : Integer ;
     PreScans : Integer ;   // No. of scans before event detection point
     StartScan : Integer ;  // Start of analysis block
     NumScans : Integer ;   // No. of scans in analysis block
     EventScan : Integer ;  // Scan (within Buf) where event occurs

     NumRead : Integer ;      // No. of scans read into buffer

     SortedEvents : PIntArray ;
     iEv : Integer ;
     NumSortedEvents : Integer ;
     AvgSum : PSingleArrayDyn ;

begin


     bDoAverage.Enabled := False ;
     bAbortAverage.Enabled := True ;
     ComputationInProgress := True ;
     AbortFlag := False ;

     Main.StatusBar.SimpleText := 'Event Detector : WAIT ... Plotting Histogram ' ;

     if rbAverageAllEvents.Checked then begin
        { Use all Events }
        StartAtEvent := 0 ;
        EndAtEvent := NumEvents - 1 ;
        end
     else begin
        { Use selected range of records }
        StartAtEvent := Round(edAverageRange.LoValue)-1 ;
        EndAtEvent := Round(edAverageRange.HiValue)-1 ;
        end ;

     // Get first and last scans of analysis window
     NumScans := Round(edEditDisplayWidth.Value) ;
     PreScans := Round(NumScans*edPreTrigger.Value) ;
     Settings.EventDetector.PreTriggerFraction := edPreTrigger.Value ;

     // Allocate averaging buffer
     GetMem( AvgSum, NumScans*4 ) ;
     GetMem( SortedEvents, Max(NumEvents,1)*4 ) ;

     for i := 0 to NumScans-1 do AvgSum^[i] := 0.0 ;

     // Sort events by amplitude if count match averaging in use
     if ckCountMatchedAvg.Checked then begin
        CreateAmplitudeSortedEventList( StartAtEvent,
                                        EndAtEvent,
                                        SortedEvents^,
                                        NumSortedEvents ) ;
        StartAtEvent := 0 ;
        EndAtEvent := Max( Round(edNumCountMatchedAvg.Value)-1,0) ;
        end ;

     NumEventsAveraged := 0 ;
     EventScan := 0 ;
     for iEv := StartAtEvent to EndAtEvent do begin

        // Select event number from sorted list if count matched averaging in use
        if ckCountMatchedAvg.Checked then iEvent := SortedEvents^[iEv]
                                     else iEvent := iEv ;

        // First multi-channel scan in analysis block
        StartScan := Events[iEvent] - PreScans ;
        // Skip if insufficient pre-event scans
        if StartScan < 0 then Continue ;

        // Set scan at which event detection point is located
        EventScan := Events[iEvent] - StartScan ;

        // Read A/D sample data for event from file
        NumRead := ReadCDRBuffer(CdrFH,StartScan,AvgBuf^,NumScans) ;

        // Terminate averaging loop if insufficient post-detection scans
        if NumRead < NumScans then Break ;

        // Add to averaging buffer
        j := Channel[cbChannel.ItemIndex].ChannelOffset ;
        for i := 0 to NumScans-1 do begin
            AvgSum[i] := AvgSum[i] + AvgBuf^[j] ;
            j :=  j + CDRFH.NumChannels ;
            end ;

        Inc( NumEventsAveraged) ;

        Main.StatusBar.SimpleText := format(
                                     'Event Detector : Averaging %d/%d ',
                                     [iEvent+1,EndAtEvent+1]) ;

        Application.ProcessMessages ;

        if AbortFlag then Break ;

        end ;

     // Calculate average
     j := Channel[cbChannel.ItemIndex].ChannelOffset ;
     for i := 0 to NumScans-1 do begin
         AvgBuf^[j] := Round( AvgSum[i]/Max(NumEventsAveraged,1) ) ;
         j :=  j + CDRFH.NumChannels ;
         end ;

     // Calculate waveform measurements for average
     AvgStartEvent := StartAtEvent ;
     AvgEndEvent := EndAtEvent ;
     AvgEventScan := EventScan ;
     AvgNumScans := NumScans ;
     AvgPreScans := Prescans ;
     AnalyseAverage ;

     bDoAverage.Enabled := True ;
     bAbortAverage.Enabled := False ;
     ComputationInProgress := False ;

     scAverageDisplay.Invalidate ;

     // Release buffer
     FreeMem( AvgSum ) ;
     FreeMem( SortedEvents ) ;

     end ;


procedure TEventDetFrm.AnalyseAverage ;
// -------------------------------------------
// Calculate waveform measurements for average
// -------------------------------------------
const
     NumAvgRequired = 10 ;
var
     i,j : Integer ;
     PostScans : Integer ;  // No. of scans after event detection point
     StartScan : Integer ;  // Start of analysis block
     EndScan : Integer ;    // End of analysis block

     Event : TEventAnalysis ;
     Polarity : Integer ;   // Event polarity (1=positive-going,-1=negative-)
     Sum : Single ;         // Summation variable
     NumAvg : Integer ;     // No. samples averaged for baseline
     NumNegDiffs : Integer ; // No. of negative differences (baseline detector)
     Y : Single ;           // A/D sample value
     YMax : Single ;        // Maximum value within block
     PeakAt : Integer ;     // Scan at which peak occurs
     MidPointOfRiseAt : Integer ; // Scan at mid-point of event rise
     YDecay : Single ;         // X% of peak
     Y90 : Single ;         // 90% of peak
     Y50 : Single ;         // 50% of peak
     Y10 : Single ;         // 10% of peak
     NumRise : Integer ;    // No. of scans within event rising phase
     Num10toPeak : Integer ;
     T : Single ;          //
     SumT : Single ;        // Summation variables for time constant fit
     SumT2 : Single ;         //
     SumY : Single ;         //
     SumYT : Single ;         //
     nPoints : Integer ;
     Slope : Single ;
     NumRead : Integer ;      // No. of scans read into buffer
     Done : Boolean ;
     AnalysisStart : Integer ;
     AnalysisEnd : Integer ;

begin

     if NumEventsAveraged <= 0 then Exit ;

     // Calculate waveform measurements

     // Set positive/negative polarity of event to be measured
     if rbPositive.Checked then Polarity := 1
                           else Polarity := -1 ;

     // Find baseline level
     Event.YBaseline := PreEventBaselineLevel( AvgBuf^,
                                               AvgPreScans,
                                               scAverageDisplay.NumPoints ) ;

     scAverageDisplay.HorizontalCursors[0] := Event.YBaseline ;

     // Set limits of range to be analysed
     AnalysisStart := Min( scAverageDisplay.VerticalCursors[AverageC0Cursor],
                           scAverageDisplay.VerticalCursors[AverageC1Cursor] ) ;
     AnalysisStart := Max( AnalysisStart,0) ;
     AnalysisEnd := Max( scAverageDisplay.VerticalCursors[AverageC0Cursor],
                         scAverageDisplay.VerticalCursors[AverageC1Cursor] ) ;
     AnalysisEnd := Min( Max(AnalysisEnd,AnalysisStart+1),AvgNumScans-1 ) ;

     // Find peak amplitude and area under curve
     YMax := -1E30 ;
     SumY := 0.0 ;
     PeakAt := AnalysisStart ;
     for i := AnalysisStart to AnalysisEnd do begin
          j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
          Y := Polarity*(AvgBuf^[j] - Event.YBaseline) ;
          if Y >= YMax then begin
             YMax := Y ;
             PeakAt := i ;
             end ;
         SumY := SumY + Y ;
         end ;

     Event.Peak := Polarity*YMax*Channel[cbChannel.ItemIndex].ADCScale ;
     Event.Area := Polarity*SumY*Channel[cbChannel.ItemIndex].ADCScale*CDRFH.dt*SecsToMs ;

     // Find rise time
     i := PeakAt ;
     MidPointOfRiseAt := PeakAt ;
     Y10 := YMax / 10.0 ;
     Y50 := YMax / 2.0 ;
     Y90 := YMax - Y10 ;
     NumRise := 0 ;
     Num10toPeak := 0 ;
     Done := False ;
     while not Done do begin
          j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
          Y := Polarity*(AvgBuf^[j] - Event.YBaseline) ;
          if Y <= Y90 then Inc(NumRise) ;
          if Y >= Y50 then MidPointOfRiseAt := i ;
          Inc(Num10toPeak) ;
          Dec(i) ;
          if (Y < Y10) or (i<=0) then Done := True ;
          end ;
     Event.TRise := NumRise*CDRFH.dt*SecsToMs ;

     // Find time to X% decay
     i := PeakAt ;
     YDecay := YMax*(1.0 - (0.01*edTDecayPercentage.Value)) ;
     Done := False ;
     while not Done do begin
          j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
          Y := Polarity*(AvgBuf^[j] - Event.YBaseline) ;
          Inc(i) ;
          if (Y < YDecay) or (i>=AvgNumScans) then Done := True ;
          end ;

     if cbDecayFrom.ItemIndex = cTDecayFromPeak then begin
        // Decay time from peak
        Event.TDecay := (i-PeakAt)*CDRFH.dt*SecsToMs ;
        end
     else if cbDecayFrom.ItemIndex = cTDecayFromMidRise then begin
        // Decay time from mid-point of rising phase
        Event.TDecay := (i-MidPointOfRiseAt)*CDRFH.dt*SecsToMs ;
        end
     else begin
        // Decay time from first analysis cursor
        Event.TDecay := (i-AnalysisStart)*CDRFH.dt*SecsToMs ;
        end ;

     // Event duration (10% of rising phase to 90% decay after peak)
     i := PeakAt ;
     YDecay := YMax*0.1 ;
     Done := False ;
     while not Done do begin
         j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
         Y := Polarity*(AvgBuf[j] - Event.YBaseline) ;
         Inc(i) ;
         if (Y < YDecay) or (i>=AvgNumScans) then Done := True ;
         end ;
     Event.Duration :=  ((i-PeakAt) + Num10toPeak)*CDRFH.dt*SecsToMs ;

   // Calculate decay time constant
   i := PeakAt ;
   T := 0.0 ;
   SumT := 0.0 ;
   SumT2 := 0.0 ;
   SumY := 0.0 ;
   SumYT := 0.0 ;
   nPoints := 0 ;
   Done := False ;
   while not Done do begin
      j := (i*CDRFH.NumChannels) + Channel[cbChannel.ItemIndex].ChannelOffset ;
      Y := Polarity*(AvgBuf^[j] - Event.YBaseline) ;
      if Y > Y90 then begin
         Y := Ln(Y) ;
         SumT := SumT + T ;
         SumT2 := SumT2 + T*T ;
         SumY := SumY + Y ;
         SumYT := SumYT + Y*T ;
         Inc(nPoints) ;
         end
      else Done := True ;
      Inc(i) ;
      T := T + CDRFH.dt ;
      if i >= AnalysisEnd then Done := True ;
      end ;
   if nPoints > 1 then begin
      Slope := ((nPoints*SumYT) - (SumT*SumY)) /
               ((nPoints*SumT2) - (SumT*SumT)) ;
      if Slope < 0.0 then Event.TauDecay := (-1.0/Slope)*SecsToMs
                     else Event.TauDecay := 0.0 ;
      end
   else Event.TauDecay := 0.0 ;


     meAverageResults.Clear ;
     meAverageResults.Lines.Add( format( 'Avg. of %d-%d (%d)',
                                [AvgStartEvent+1,
                                 AvgEndEvent+1,
                                 AvgEndEvent-AvgStartEvent+1] )) ;

     meAverageResults.Lines.Add( format( 'Peak (a-a)= %.5g %s',
                                  [Event.Peak,
                                   Channel[cbChannel.ItemIndex].ADCUnits]));

     meAverageResults.Lines.Add( format( 'Area (a-a)= %.5g %s.ms',
                                  [Event.Area,
                                   Channel[cbChannel.ItemIndex].ADCUnits]));

     meAverageResults.Lines.Add( format( 'T(rise)= %.5g ms',[Event.TRise]));

     meAverageResults.Lines.Add( format( 'T(%.0f%%)= %.5g ms',
                                 [edTDecayPercentage.Value,Event.TDecay]));

     meAverageResults.Lines.Add( format( 'Tau(decay)= %.5g ms',[Event.TauDecay]));

     meAverageResults.Lines.Add( format( 'Duration= %.5g ms',[Event.Duration]));

     end;


procedure TEventDetFrm.CreateAmplitudeSortedEventList(
          StartEvent : Integer ;
          EndEvent : Integer ;
          var SortedEvents : Array of Integer ;
          var NumSortedEvents : Integer
          ) ;
// --------------------------------------------
// Calculate list of events sorted by amplitude
// --------------------------------------------
var
    Amplitude : PSingleArray ; // Event amplitude
    X,Y : Single ;
    iEvent,iEv : Integer ;
    Last,Current : Integer ;
    Temp : Single ;
    iTemp : Integer ;
begin

   GetMem( Amplitude, Max(NumEvents,1)*4 ) ;

   // Calculate peak amplitude
   AbortFlag := False ;
   NumSortedEvents := 0 ;

   for iEv := StartEvent to EndEvent do begin

       iEvent := iEv ;
       CalculateVariables( iEvent,
                           vEventNum,
                           vPeak,
                           X,
                           Y ) ;

       SortedEvents[NumSortedEvents] := iEv ;
       Amplitude^[NumSortedEvents] := Y ;

       Inc(NumSortedEvents) ;

       if AbortFlag then begin
          FreeMem( Amplitude ) ;
          Exit ;
          end ;

       end ;

   // Sort by amplitude
   for Last := NumSortedEvents-1 DownTo 1 do begin
       for Current := 0 to Last-1 do begin
           if Amplitude[Current] < Amplitude[Current+1] then begin
              Temp := Amplitude[Current] ;
              Amplitude^[Current] := Amplitude^[Current+1] ;
              Amplitude^[Current+1] := Temp ;
              iTemp := SortedEvents[Current] ;
              SortedEvents[Current] := SortedEvents[Current+1] ;
              SortedEvents[Current+1] := iTemp ;
              end ;
           end ;
       end ;

   FreeMem( Amplitude ) ;

   end ;


procedure TEventDetFrm.scAverageDisplayCursorChange(Sender: TObject);
// ----------------------------------
// Average display window cursor changed
// ----------------------------------
var
     ch : Integer ;
begin

     for ch := 0 to scAverageDisplay.NumChannels-1 do
         if scAverageDisplay.ChanVisible[ch] then begin
         { Get signal baseline cursor }
         Channel[ch].yMin := scAverageDisplay.yMin[ch] ;
         Channel[ch].yMax := scAverageDisplay.yMax[ch] ;
         end ;
     end;


procedure TEventDetFrm.rbAveragePositiveClick(Sender: TObject);
// -----------------------------------
// Positive waveform analysis selected
// -----------------------------------
begin
     // Force new average (if one already exists)
     if NumEventsAveraged > 0 then bDoAverage.Click ;
     end;


procedure TEventDetFrm.rbAverageNegativeClick(Sender: TObject);
// -----------------------------------
// Negative waveform analysis selected
// -----------------------------------
begin
     // Force new average (if one already exists)
     if NumEventsAveraged > 0 then bDoAverage.Click ;
     end;


procedure  TEventDetFrm.ZoomIn( Chan : Integer ) ;
{ -----------------------------------------------------
  Let user set display magnification for channel 'Chan'
  ----------------------------------------------------- }
begin
     scDisplay.YZoom( Chan, -50.0 );
     scEditDisplay.YZoom( Chan, -50.0 );
     scAverageDisplay.YZoom( Chan, -50.0 );
     end ;


procedure  TEventDetFrm.ZoomOut( Chan : Integer ) ;
{ -----------------------------------------------------
  Let user set display magnification for channel 'Chan'
  ----------------------------------------------------- }
begin
     scDisplay.YZoom( Chan, 50.0 );
     scEditDisplay.YZoom( Chan, 50.0 );
     scAverageDisplay.YZoom( Chan, 50.0 );
     end ;



procedure  TEventDetFrm.ZoomOutAll ;
{ ---------------------------------
  Set minimum display magnification
  --------------------------------- }
begin
     scDisplay.MaxADCValue := Channel[0].ADCMaxValue ;
     scDisplay.MinADCValue := -Channel[0].ADCMaxValue -1 ;
     scDisplay.ZoomOut ;
     scEditDisplay.MaxADCValue := scDisplay.MaxADCValue ;
     scEditDisplay.MinADCValue := scDisplay.MinADCValue ;
     scEditDisplay.ZoomOut ;
     scAverageDisplay.MaxADCValue := scDisplay.MaxADCValue ;
     scAverageDisplay.MinADCValue := scDisplay.MinADCValue ;
     scAverageDisplay.ZoomOut ;
     end ;



procedure TEventDetFrm.ckSubtractBaselineClick(Sender: TObject);
// --------------------------------------
// Baseline subtraction check box changed
// --------------------------------------
begin

     Settings.EventDetector.SubtractBaseline := ckSubtractBaseline.Checked ;
     SaveCDRHeader( CDRFH ) ;

     EventAnalysisFileUpdateRequired := True ;
     DisplayEvent ;
     end ;

procedure TEventDetFrm.bPlotAbortClick(Sender: TObject);
begin
     AbortFlag := True ;
     end;

procedure TEventDetFrm.bAbortExportClick(Sender: TObject);
// --------------------------
// Terminate export of events
// --------------------------
begin
     AbortFlag := True ;
     end;


procedure TEventDetFrm.edDetDisplayWidthKeyPress(Sender: TObject;
  var Key: Char);
// ---------------------------------------
// Update no. sample scans in display window
// ---------------------------------------
begin
     if key = #13 then begin
        NewFile ;
        DisplayRecord ;
        end ;
     end;


procedure TEventDetFrm.bAverageFitCurveClick(Sender: TObject);
{ ------------------------------------------
  Fit exponential functions to event average
  ------------------------------------------}
var
     iStart,iEnd,TZero,YZero : Integer ;
     i, j : Integer ;
     X, Y : Single ;
begin


    CVFit.Equation := TCFEqnType(cbAverageEqn.Items.Objects[cbAverageEqn.ItemIndex]) ;
    CVFit.XUnits := Settings.TUnits ;
    CVFit.YUnits := Channel[cbChannel.ItemIndex].ADCUnits ;
    lbAvgFitResults.Caption := '' ;
    scAverageDisplay.CreateLine(cbChannel.ItemIndex,clred,psSolid, 1);

    if CVFit.Equation = None then begin
        scAverageDisplay.Invalidate ;
        Exit ;
        end;

    // Add points to curve fitter
    TZero := scAverageDisplay.VerticalCursors[AverageT0Cursor] ;
    iStart := Min( scAverageDisplay.VerticalCursors[AverageC0Cursor],
                   scAverageDisplay.VerticalCursors[AverageC1Cursor] ) ;
    iEnd := Max( scAverageDisplay.VerticalCursors[AverageC0Cursor],
                 scAverageDisplay.VerticalCursors[AverageC1Cursor] ) ;
    YZero :=scAverageDisplay.HorizontalCursors[cbChannel.ItemIndex] ;
    CVFit.ClearPoints ;
    for i := iStart to iEnd do begin
        j := (i*scAverageDisplay.NumChannels)
             + scAverageDisplay.ChanOffsets[cbChannel.ItemIndex] ;
        X := (i-TZero)*scAverageDisplay.TScale ;
        Y := (AvgBuf^[j] - YZero)*Channel[cbChannel.ItemIndex].ADCScale ;
        CVFit.AddPoint( X, Y ) ;
        end ;

    // Let user set fitting parameters
    CVFit.ParametersSet := False ;
    SetFitPars1frm.CVFit := CVFit ;
    SetFitPars1frm.ShowModal ;
    if SetFitPars1frm.ModalResult <> mrOK then Exit ;

    // Fit curve
    CVFit.FitCurve ;

    // Copy results to display
    lbAvgFitResults.Caption := CVFit.FitResults ;

    // Draw best fit line
    scAverageDisplay.CreateLine(cbChannel.ItemIndex,clred,psSolid, 1);
    for i := TZero to iEnd do begin
        X := (i-TZero)*scAverageDisplay.TScale ;
        scAverageDisplay.AddPointToLine(i, CVFit.EquationValue(X)/
                                           scAverageDisplay.ChanScale[cbChannel.ItemIndex]
                                           + YZero ) ;
        end ;

     // Ensure display is updated
     scAverageDisplay.Invalidate ;

     end;



procedure TEventDetFrm.edPreTriggerKeyPress(Sender: TObject;
  var Key: Char);
// ---------------------
// Pre-trigger % changed
// ---------------------
begin
     if Key = #13 then begin
        Settings.EventDetector.PreTriggerFraction := edPreTrigger.Value ;
        DisplayEvent ;
        end ;
     end ;

     
procedure TEventDetFrm.edTDecayPercentageKeyPress(Sender: TObject;
  var Key: Char);
// -----------------------------
// Decay time percentage changed
// -----------------------------
begin
     if Key = #13 then begin
        Settings.EventDetector.TDecayPercent := edTDecayPercentage.Value ;
        SaveCDRHeader( CDRFH ) ;
        DisplayEvent ;
        end ;
     end;

procedure TEventDetFrm.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
begin
     if ComputationInProgress then begin
        AbortFlag := True ;
        ComputationInProgress := False ;
        CanClose := false ;
        end
     else CanClose := True ;
     end;

procedure TEventDetFrm.bAbortAverageClick(Sender: TObject);
// ---------------
// Abort averaging
// ---------------
begin
     AbortFlag := True ;
     end;

procedure TEventDetFrm.scEditDisplayMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
     if (scEditDisplay.VerticalCursors[EditC0Cursor] <> OldEditC0CursorPos) or
        (scEditDisplay.VerticalCursors[EditC1Cursor] <> OldEditC1CursorPos) then begin
        DisplayEvent ;
        EventAnalysisFileUpdateRequired := True ;
        end ;
     end;


procedure TEventDetFrm.ChangeDisplayGrid ;
{ --------------------------------------------
  Update grid pattern on oscilloscope display
  -------------------------------------------- }
begin

     scDisplay.DisplayGrid := Settings.DisplayGrid ;
     scDetDisplay.DisplayGrid := Settings.DisplayGrid ;
     scEditDisplay.DisplayGrid := Settings.DisplayGrid ;
     scAverageDisplay.DisplayGrid := Settings.DisplayGrid ;

     scDisplay.NumHorizontalGridLines := Settings.NumHorizontalGridLines ;
     scDetDisplay.NumHorizontalGridLines := Settings.NumHorizontalGridLines ;
     scEditDisplay.NumHorizontalGridLines := Settings.NumHorizontalGridLines ;
     scAverageDisplay.NumHorizontalGridLines := Settings.NumHorizontalGridLines ;

     scDisplay.NumVerticalGridLines := Settings.NumVerticalGridLines ;
     scDetDisplay.NumVerticalGridLines := Settings.NumVerticalGridLines ;
     scEditDisplay.NumVerticalGridLines := Settings.NumVerticalGridLines ;
     scAverageDisplay.NumVerticalGridLines := Settings.NumVerticalGridLines ;

     end ;


procedure TEventDetFrm.scAverageDisplayMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
// ---------------------
// Mouse button released
// ---------------------
begin
     if (scAverageDisplay.VerticalCursors[AverageC0Cursor] <> OldAvgC0CursorPos) or
        (scAverageDisplay.VerticalCursors[AverageC1Cursor] <> OldAvgC1CursorPos) then begin
        AnalyseAverage ;
        end ;
end;


procedure TEventDetFrm.rbBaselineAtStartClick(Sender: TObject);
// -----------------------------------------
// Zero baseline at start of record selected
// -----------------------------------------
begin

     Settings.EventDetector.BaselineAtStart := True ;
     SaveCDRHeader( CDRFH ) ;

     DisplayEvent ;
     EventAnalysisFileUpdateRequired := True ;
     end ;


procedure TEventDetFrm.rbBaselineAtEventClick(Sender: TObject);
// -----------------------------------------
// Zero baseline at start of event selected
// -----------------------------------------
begin

     Settings.EventDetector.BaselineAtStart := False ;
     SaveCDRHeader( CDRFH ) ;

     DisplayEvent ;
     EventAnalysisFileUpdateRequired := True ;
     end ;

procedure TEventDetFrm.rbTDecayFromPeakClick(Sender: TObject);
// -----------------------------------------
// Decay time relative to peak selected
// -----------------------------------------
begin
     DisplayEvent ;
     EventAnalysisFileUpdateRequired := True ;
     end ;

procedure TEventDetFrm.rbTDecayFromMidRiseClick(Sender: TObject);
// -----------------------------------------
// Decay time relative to mid-point of rise selected
// -----------------------------------------
begin
     DisplayEvent ;
     EventAnalysisFileUpdateRequired := True ;
     end ;

procedure TEventDetFrm.rbTDecayFromC0Click(Sender: TObject);
// -----------------------------------------
// Decay time relative to a0 cursor selected
// -----------------------------------------
begin
     DisplayEvent ;
     EventAnalysisFileUpdateRequired := True ;
     end ;

procedure TEventDetFrm.edZeroNumAvgKeyPress(Sender: TObject;
  var Key: Char);
begin
     if Key = #13 then begin
        Settings.EventDetector.NumBaselinePoints := Round(edZeroNumAvg.Value) ;
        SaveCDRHeader( CDRFH ) ;
        DisplayEvent ;
        end ;
     end ;

procedure TEventDetFrm.edZeroGapKeyPress(Sender: TObject; var Key: Char);
begin
     if Key = #13 then begin
        Settings.EventDetector.NumBaselineGap := Round(edZeroGap.Value) ;
        SaveCDRHeader( CDRFH ) ;
        DisplayEvent ;
        end ;
     end ;


procedure TEventDetFrm.cbDecayFromChange(Sender: TObject);
// -----------------------
// Decay time mode changed
// -----------------------
begin
     Settings.EventDetector.TDecayFrom := cbDecayFrom.ItemIndex ;
     SaveCDRHeader( CDRFH ) ;
     DisplayEvent ;
     end;

procedure TEventDetFrm.FormCreate(Sender: TObject);
// ------------------------------------
// Initialisations when form is created
// ------------------------------------
begin
     AvgBuf := Nil ;
     DetBuf := Nil ;
     EditBuf := Nil ;
     ADC := Nil ;
     end;

procedure TEventDetFrm.bTDisplayDoubleClick(Sender: TObject);
// -------------------------------------------
// Double size of detect events display window
// -------------------------------------------
begin
        edDetDisplayWidth.Value := edDetDisplayWidth.Value*2.0 ;
        NewFile ;
        DisplayRecord ;
        end;

procedure TEventDetFrm.bTDisplayHalfClick(Sender: TObject);
// -------------------------------------------
// Halve size of detect events display window
// -------------------------------------------
begin
        edDetDisplayWidth.Value := edDetDisplayWidth.Value*0.5 ;
        NewFile ;
        DisplayRecord ;
        end;

procedure TEventDetFrm.bEditDisplayWidthHalveClick(Sender: TObject);
// ---------------------------------
// Halve width of edit event display
// ---------------------------------
begin
    edEditDisplayWidth.Value := edEditDisplayWidth.Value*0.5 ;
    Settings.EventDetector.AnalysisWindow := edEditDisplayWidth.Value ;
    NewFile ;
    DisplayEvent ;
    end ;

procedure TEventDetFrm.bDoubleEditDisplayWidthClick(Sender: TObject);
// ---------------------------------
// Double width of edit event display
// ---------------------------------
begin
    edEditDisplayWidth.Value := edEditDisplayWidth.Value*2.0 ;
    Settings.EventDetector.AnalysisWindow := edEditDisplayWidth.Value ;
    NewFile ;
    DisplayEvent ;
    end ;

procedure TEventDetFrm.edNumBinsKeyPress(Sender: TObject; var Key: Char);
// -------------------
// No. of bins changed
// -------------------
begin
    if Key = #13 then begin
       edNumBins.Value := Round(edNumBins.Value) ;
       edBinWidth.Value := (edBinsUpper.Value - edBinsLower.Value)/edNumBins.Value ;
       end;
    end;

procedure TEventDetFrm.edBinWidthKeyPress(Sender: TObject; var Key: Char);
// -----------------
// Bin width changed
// -----------------
begin
     if Key = #13 then begin
        if edBinWidth.Value <= 0.0 then begin
           edBinWidth.Value := (edBinsUpper.Value - edBinsLower.Value)/edNumBins.Value ;
           end;
        edNumBins.Value := Round((edBinsUpper.Value - edBinsLower.Value)/edBinWidth.Value) ;
        end;
    end;

procedure TEventDetFrm.edBinsLowerKeyPress(Sender: TObject; var Key: Char);
// -----------------------------
// Histogram lower limit changed
// -----------------------------
begin
     if Key = #13 then begin
        edBinWidth.Value := (edBinsUpper.Value - edBinsLower.Value)/edNumBins.Value ;
        end;
      end;

procedure TEventDetFrm.edBinsUpperKeyPress(Sender: TObject; var Key: Char);
// -----------------------------
// Histogram upper limit changed
// -----------------------------
begin
     if Key = #13 then begin
        edBinWidth.Value := (edBinsUpper.Value - edBinsLower.Value)/edNumBins.Value ;
        end;
      end;

procedure TEventDetFrm.edRisingEdgeWindowKeyPress(Sender: TObject;
  var Key: Char);
// ---------------------------------------------
// Rising edge detection window duration changed
// ---------------------------------------------
begin
     if Key = #13 then begin
        Settings.EventDetector.RisingEdgeWindow := Round(edRisingEdgeWindow.Value) ;
        DisplayRecord ;
        end ;

     end;

procedure TEventDetFrm.cbPlotXVarChange(Sender: TObject);
// -----------------------
// X axis variable changed
// -----------------------
begin
     if (cbPlotXVar.Items.Objects[cbPlotXVar.ItemIndex] = TObject(VFrequencyAvg)) or
        (cbPlotYVar.Items.Objects[cbPlotYVar.ItemIndex] = TObject(VFrequencyAvg)) then begin
        FrequencyAvgPan.Visible := True ;
        EventRangePan.Visible := False ;
        end
     else begin
        FrequencyAvgPan.Visible := False ;
        EventRangePan.Visible := True ;
        end ;

     end;

procedure TEventDetFrm.cbPlotYVarChange(Sender: TObject);
// -----------------------
// Y axis variable changed
// -----------------------
begin
     if (cbPlotXVar.Items.Objects[cbPlotXVar.ItemIndex] = TObject(VFrequencyAvg)) or
        (cbPlotYVar.Items.Objects[cbPlotYVar.ItemIndex] = TObject(VFrequencyAvg)) then begin
        FrequencyAvgPan.Visible := True ;
        EventRangePan.Visible := False ;
        end
     else begin
        FrequencyAvgPan.Visible := False ;
        EventRangePan.Visible := True ;
        end ;

     end;


procedure TEventDetFrm.edAverageIntervalKeyPress(Sender: TObject;
  var Key: Char);
begin
     if key = #13 then
        Settings.EventDetector.AvgFrequencyInterval := edAverageInterval.Value ;
     end;

end.


