unit Spectrum;{ Nonvisible objects for control/file storage of acquired data }//ulfobju
{
  (C) 2000 - 2003 Jindrich Jindrich, Pavel Pisa, PiKRON Ltd.

  Originators of the CHROMuLAN project:

  Jindrich Jindrich - http://www.jindrich.com
                      http://chromulan.org
                      software developer, project coordinator
  Pavel Pisa        - http://cmp.felk.cvut.cz/~pisa
                      embeded software developer
  PiKRON Ltd.       - http://www.pikron.com
                      project initiator, sponsor, instrument developer

  The CHROMuLAN project is distributed under the GNU General Public Licence.
  See file COPYING for details.

  Originators reserve the right to use and publish sources
  under different conditions too. If third party contributors
  do not accept this condition, they can delete this statement
  and only GNU license will apply.
}

{$I DEFINE.PAS}
interface
uses
  SysUtils, Classes,
  {$IFNDEF CONSOLE}
  Dialogs, Controls, Forms, Graphics,
  {$ENDIF}
  {v0.45}
  UtlType, WinUtl,
  {/v0.45 TVType, MyType, MyLib,} Fileu,
  Msgu,
  {$IFNDEF CONSOLE}
  {$ENDIF}
  UlanType, UlanGlob,
  ULRecTyp, ULObju, ULFObju,
  ULAType,  ULAObju,
  ULADType, ULADObju,
  ULAFType, ULAFObju,
  ULBType,  ULBObju,
  ULBRType, ULBRObju,
  ULEType,  ULEObju,
  ULPType,  ULPObju,
  ULPRType, ULPRObju,
  ULMType,  ULMObju,
  ULMFType, ULMFObju,
  ULVType,  ULVObju,
  ULVLType, ULVLObju,
  ULVOType, ULVOObju,
  {v0.50}
  UDLSType, UDLSObju,
  UDLRType, UDLRObju,
  {DataLineu,}
  {/v0.50}
  ExpImpu
  {$IFNDEF CONSOLE}
  ,DebugFrm
  {$ENDIF}
  {v0.11}, MathCopr {/v0.11}
  {v0.13}
  ,ULIType, ULIObju
  {/v0.13}
  {v0.14}
  ,Channelsu
  {/v0.14}
  {v0.19}
  {$IFNDEF CONSOLE}
  ,infofrm
  {$ENDIF}
  {/v0.19}
  {v0.28}, Language {/v0.28}{v0.30}
  {$IFNDEF CONSOLE}
  ,FileMenuHdl
  {$ENDIF}
  {/v0.30}
  {v0.31}
  ,ULDRObju, Messages
  {/v0.31}
  {v0.43}, ExeLogu{/v0.43}
  {v0.50}
  , USPType, USPObju, SeqPrgu
  {/v0.50}
  {v0.51pi}
  ,ULDPType, ULDPObju
  {/v0.51pi}
  {v0.57}
  ,UDVRType, UDVRObju,
  UDVSType, UDVSObju, Math, DataFileu,
  ULCType, ULCObju, UCFType, UCFObju, UCPType, UCPObju,
  UCFRType, UCFRObju, UCPRType, UCPRObju,
  AAPGType, AAPGObju
;

type
  {v0.11}
  ESpectrum = class(Exception);
  {/v0.11}
  {v0.13}
  EUserAborted = class(Exception);
  {/v0.13}
  {v0.14}
  EAcqData = class(Exception);
  {/v0.14}

type
  TProcPeak = record
    { Structure with data used for processing of one peak }
    PeakStart:TIndexPoint;
    Point_1,
    Point_2:TIndexPoint;
      { CurPoint-1, CurPoint-2 }
    LocMin_1, LocMax_1,
    LocMin_2, LocMax_2: TIndexPoint;
      { Last local (noise) extreme values. }
    CurMin, CurMax,
    Min_1, Max_1,
    Min_2, Max_2:TIndexPoint;
      { Last real extreme values. }
    IsInPeak:boolean;
  end;


  TSpectrum = class;

  {v0.24}
  { Forward declaration of TAcqData class. }
  TAcqData = class;
  {v0.57}
  TULADs = class;
  {/v0.57}


  TULADRc = class(TObject)
  {v0.57}
  private
    FULADs: TULADs;
    FULAD: TULADObj;
    FUDVR: TUDVRObj;
    procedure SetULAD(AULAD: TULADObj);
  public
  {/v0.57
    ULAD: TULADObj;}
    CurDataSize: integer;
    CurDataPos: integer;
    {v0.31}
    Addr:integer;
      {uLan net address of the device (detector) that gives the data}
    PointTimeInterval: single;
      { in what interval (in ms) come data points
        (default value = UlanGlob .UlanPointTimeInterval) }
    CurTime: single;
    YCoef: single;
      { used to multiply the Y value got from the device with (the result
        will be stored to the file) }
    {ulanglob}
    {/v0.31}
  protected
    function GetData: TStream;
  public
    constructor Create{v0.57}(AULADs: TULADs){/v0.57}; reintroduce;
    property Data: TStream read GetData;
    {v0.57}
    property ULAD: TULADObj read FULAD write SetULAD;
    property UDVR: TUDVRObj read FUDVR;
    {/v0.57}
  end;

  TULADs = class(TList)
  private
    FAcqData: TAcqData;
  protected
    function GetULADRc(Index: integer): TULADRc;
    procedure AddULAD(AULAD: TULADObj);
    procedure DelULAD(AULAD: TULADObj);
    function FindULAD(AULAD: TULADObj; var Index: integer): boolean;
  public
    procedure Clear;override;
    constructor Create(AAcqData: TAcqDAta);
    destructor Destroy;override;
    procedure Update;
    procedure Reset;
    procedure SetZeroSizes;
    procedure CurDataSizeUpdate;
    {v0.31}
    procedure Start;
      { set curTime to zero for all ULADs }
    {/v0.31}
    property ULAD[Index: integer]: TULADRc read GetULADRc;default;
  end;

  {TAcqData}
  { Object encapsulating the acquired chromatogram data. }
  TAcqData = class({v0.64}TDataFile{/v0.64 TObject})
    { Analysis data - points }

    { Pointer to Method objects, owned by dataroot, owner of ULMF }
    ULM: TULMObj;

    { Pointer to Analysis object, owner of all other objects: ULAD, ULAF, ULM,... }
    ULA: TULAObj;
    UsrDataIndex: integer;
    ULADIndex: integer;
    { Array of objects holding acquired data }
    ULAD: TULADs;
    { Pointer to Analysis Filtered data object (using ULAF.Data: TStream);
      owned by ULA. }
    ULAF: TULAFObj;
    { Pointer to fiLter parameters (was FFilterPars: TFilterPars;) for
      filtering ULAD.Data to ULAF.Data; owned by ULA }
    ULMF: TULMFObj;
    { Method peaks (templates for autodetection of data peaks) }
    ULM_ULP: TULPObj;
    {  Method baseline records (in those baseline sections
       no peaks will be autodetected) }
    ULM_ULB: TULBObj;
    { Acquired Baseline sections }
    ULB: TULBObj;
    { Acquired Peaks }
    ULP: TULPObj;
    { Owner of view option records }
    ULV: TULVObj;
    { View limits option record }
    ULVL: TULVLObj;
    { View objects options }
    ULVO: TULVOObj;
    ULI_UDLS: TUDLSObj;
    { Owner of data lines views options }
    UDVS: TUDVSObj;
    { Peaks from calibration file (specified in ULM.CalibrationFileName)
      used for calculation of responses, temporary object (doesn't get
      saved), can be nil, inicialized only when needed by reading from
      the file. }
    Cal_ULP: TULPObj;
    { Peaks from composed peaks calibration file. Assigned if
      the file ULM.CalibrationFileName has ULC_UCP.ChildCount > 0.
      Then used instead of Cal_ULP. }
    Cal_UCP: TUCPObj;
    SpectrumOpt: TSpectrumOpt;
    Spectrum: TSpectrum;
      { To which spectrum object are the data assigned right now
        (if any) }
  private
    { Current index used in sequential access using methods:
      Reset (or SeekIndex) and ReadIndexPoint }
    CurIndex:Longint;
    UseFiltered:boolean;
      { If set, then public stream READING methods access filtered data
        in ULAF.Data. }
    FApplyMath: boolean;
      { should the mathematic operation be made on retrieved points? }
    FMultiplyX: TXValue;
    FMultiplyY: TYValue;
    FAddX: TXValue;
    FAddY: TYValue;
    FLogY: boolean;
    { Counter used in ReadPoint }
    FCurDiluteCount: longint;
    { Default = 1, if > 1, then only every FDiluteCount'th data point will
      be considered }
    FDiluteCount: longint;
    {/v0.24}
    {v0.31}
    FChannel: TChannel;
      { assigned when aquiring data, to decide how many data lines to be acquired }
      {v0.41 also looked up if Channel property requested }{/v0.41}
    FULI: TULIObj;
    {/v0.31}
    {v0.34}
    FTemplateULADCount: integer;
    {/v0.34}
    {v0.50}
    FSettingULI: boolean;
    { sequence program }
    FULI_USP: TUSPObj;
    { AAA program, for read only purposes. }

    FPrg: TSeqPrg;
    FDataStreamsCreated: boolean;
    {/v0.50}
    procedure Seek(APos:longint);
    function GetSize:longint;

    { Called by SetFileName, if some data present, finds extreme values
        of spectrum, calls Reset and then CheckPoint for each data point }
    procedure ScanData;
    {v0.61}
    { Called from ScanData, makes sure that also all peaks fit in the view }
    procedure ScanPeaks;
    {/v0.61}
    procedure FixPoint(var APoint:TExpPoint);
      { Eventually fix APoint coordinates to fit in allowed Limits
        (if any is out) }
    {v0.31}
    { called from CheckChannel: }
      procedure RecreateDataLines;
        procedure RecreateDataLinesByDefinition;
        procedure RecreateDataLinesByDevices;
    {/v0.50}
    procedure SetULI(AULI: TULIObj);
    function GetDetAddrList: string;
    {/v0.31}
    {v0.41}
    {$IFNDEF CONSOLE}
    function GetChannel: TChannel;
    {$ENDIF}
    {/v0.41}
    {v0.50}
    function GetULI_USP: TUSPObj;{ sequence program }
    {/v0.50}
    function GetActiveMethodFileName: string;
  protected
    { Free ULF, set to nil, also all pointers to its branches. }
    procedure ULFFree; override;
    { Called after ULFCreate or after ULFCreate and loading data from a file,
      create, eventually assing pointers to branches of ULF. }
    procedure ULFCreated; override;
    function GetNonameFileName: string; override;
    function GetFileExt: string; override;
    function GetTemplateExt: string; override;
    procedure ObjUpdated(AObj: TULObj); override;
    procedure ObjDestroyed(AObj: TULObj); override;
    function IsAllowedExt(const AExt: string): boolean;override;
    function HasData: boolean; override;
    procedure DiscardData; override;
    procedure SetDefaults; override;
    procedure DataLoaded; override;

    {v0.16}
    function GetIsTemplate: boolean;
    {/v0.16}
    {v0.24}
    function GetRawFileName(Index: integer): string;
    function GetActiveData: TStream;
    function GetDataCount: integer;
    function GetDataIndex: integer;
    procedure SetDataIndex(Index: integer);
    function GetActiveULADRc: TULADRc;
    {/v0.24}
    {v0.57}
    function GetUsrActiveULADRc: TULADRc;
    procedure SetUsrActiveULADRc(AULADRc: TULADRc);
    {/0.57}
    {v0.25}
    function GetActiveDataName: string;
    procedure SetActiveDataName(const ADataName: string);
      { set ULADIndex accordingly to ADataName }

    function GetUsrActiveDataName: string;
    procedure SetUsrActiveDataName(const ADataName: string);
      { as SetActiveDataName, but also sets UsrDataIndex to the selected
        DataIndex }
    {/v0.25}
    {v0.50}
    function GetPrg: TSeqPrg;
    {/v0.50}
    {v0.61}
    //function GetIsCalFile: boolean;
    {/v0.61}
  public
    {public methods}
    { Create the object from the data contained in the file AFileName.
      AMode can be:
        omCreate - creating (acquiring) data to the file AFileName
        omRead - reading (eventually modifying) already acquired data;
        omReadOnly - only reading the data
    }
    constructor Create(const AFileName: string; AMode: TOpenMode); override;

    { Just change ULF.FileName without any immediate saving (used after
      starting from template, when the name of the file to be acquired
      is already known (AAA). Extension will be automatically changed
      to .ULF }

    procedure SetFileName(const AFileName: string; AMode: TOpenMode); override;

    //procedure ChangeFileName(const AFileName: string); override;


    { Saves current data to AFileName and set it as the current file. }
    procedure SaveTo(const AFileName: string); override;
    destructor Destroy; override;
    //function CanClose: boolean; override;
    { as SetFileName but does not initialize ULAD,
      Free should be called as the last action. }
    //procedure CreateTemplate(const AFileName: string); override;


    constructor CreateFromPoints(APoints: PExpPoints; APointCount: integer);
    { Called from ULFCreate }
    procedure CheckULRecords;
      procedure CheckULMRecords;
    { Moves to the beggining of data (both ULAD, ULAF). }
    procedure Reset;
    { Direct stream access, using current file position: }
    function WritePoint(var APoint:TExpPoint):boolean;
    function ReadPoint(var APoint:TExpPoint):boolean;
    function GetPointCount:longint;
    { = Seek(Index*sizeof(TExpPoint);
      if Index < 0, then makes Seek(ActivData.Position + sizeof(TExpPoint)*Index) }
    procedure SeekPoint(Index:Longint);
    function ReadIndexPoint(var APoint:TIndexPoint):boolean;
    {/direct stream..}
    { Deletes all points. }{v0.20}{Clears also baseline/peaks}{/v0.20}
    procedure Clear;
    { Clears found extreme values, gets ready for ScanData or for
      AddPoint calls. }
    procedure ClearScanInfo;
    procedure CheckPoint(const APoint:TExpPoint);
      {.. called from ScanData or from AddPoint; updates Extremes }
    {procedure SetLimits(MinX: TXValue; MinY: TYValue; MaxX: TXValue; MaxY: TYValue);
      { What is the maximal allowed ViewLimit, exp. values outside
        these bounds are set to these values }
    {procedure SetUserLimit(AUserLimit: TUserViewLimit);}
      { xorigin, xsize, yorigin, ysize : real)}
    procedure AddPoint(var APoint: TExpPoint);
      { Called e.g. during acquisition to append new experimental point }
    procedure PutPoint(Index: TPointCount; APoint: TExpPoint);
      { Replace point at given index or if Index = GetPointCount append APoint }
    function GetPoint(Index: TPointCount; var APoint: TExpPoint): boolean;
    function GetIndexPoint(Index: TPointCount; var APoint: TIndexPoint): boolean;
      { Returns point with its coordinates plus its ordinal number in Data^,
       (false if Index out of range) }
    function Current: Longint;
      { Returns position of last inserted item in mempage buffer }
    {v0.45 obsolete}
    {/v0.45
    procedure Error(Code,Info:integer);}
    procedure GrowShowLimits(dX: TScreenX; dY: TScreenY);
    procedure MoveShowLimits(dX: TScreenX; dY: TScreenY);
    {procedure SetScreenDisp(const AScreenDisp: TScreenDisp);
      { Size of graphic window used for conversion to graphic coordinates. }
    function ExpToScreen(const AExpPoint: TExpPoint; const AScreenDisp: TScreenDisp;
      var AScreenPoint: TScreenPoint): integer;
      { Converts datapoint to screenpoint (coordinates) using SpectrumOpt
        and ScreenDisp parameters. Fills AScreenPoint, returns 0 if expoint is
        on the screen, sets opLeft on Result if on the left from screen,
        opRight if on the right from screen.
        If X is in screen range, but Y out of it, the Y is adjusted to the
        Y limit value (and 0 is returned). }

   { Converts the distance in exp. units to the distance in screen points. }
   function ExpSizeToScreenSize(const AExpPoint:TExpPoint;
     const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint): integer;

   { Converts the ScreenPoint (in pixels) to the corresponding experimental
      point (in experimental coordinates - sec./AU) using SpectrumOpt and
      ScreenDisp parameters. }
    function ScreenToExp(const ScreenPoint: TScreenPoint; const AScreenDisp: TScreenDisp;
      var AExpPoint:TExpPoint): boolean;
    { Looks in the collected data point and finds the closest point to the
        AExpPoint (using x coordinate). }

    function FindDataPoint(const AExpPoint:TExpPoint;
      var ADataPoint:TExpPoint; var Index:TPointCount): boolean;
    procedure SetViewLimit(const AViewLimit: TExpViewLimit);
    {v0.61}
    procedure GetViewLimit(var AViewLimit: TExpViewLimit);
    {/v0.61}
    procedure SetLimit(const ALimit: TExpViewLimit);
    class function UserToExp(const AUserPoint: TUserPoint; var AExpPoint: TExpPoint): boolean;
    class function ExpToUser(const AExpPoint: TExpPoint; var AUserPoint: TUserPoint): boolean;
    function UserToScreen(const AUserPoint:TUserPoint;
      const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint): boolean;
    function UserSizeToScreenSize(const AUserPoint:TUserPoint;
      const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint): boolean;
    procedure FilterUpdate;
    procedure MethodLoadFromFile(const AFileName: string);
    procedure MethodSaveToFile(const AFileName: string);
    procedure CalibrationFileSelect(const AFileName: string);
    procedure SpectrumGet(var sp: TSpectrum);
      { checks if some Spectrum already assigned to Self, if yes returns
        pointer to it,if not creates one, returns pointer to it;
        SpectrumRelease MUST be called when sp not needed anymore }
    procedure SpectrumRelease(sp: TSpectrum);
      { MUST be called after SpectrumGet if sp not needed anymore }
    procedure AcquiredDataDiscard;
    procedure DoMath(mo:TDataMathOperation; r: single);
      { set value of mo corresponding variable and activate FApplyMath field
        (or desactivate if mo = moNone) }
    procedure MakeMathPermanent;
      { save the values obtained by getpoint or readpoint method
        (eventyally modified if FApplyMath active) back to the file,
        then disable FApplyMath }

    { Modify APoint accorging to current math settings }
    procedure DoApplyMath(var APoint: TExpPoint);

    procedure InstrumentLoadFromFile(const AFileName: string);
    procedure InstrumentSaveToFile(const AFileName: string);
    {v0.15}
    procedure GetReadyForTemplate;
      { Strip the file off (data, peaks, baseline sections) }
    {/v0.15}
    {v0.24}
    function DataSize: integer;
    {/v0.24}
    {v0.25}
    procedure UsrDataIndexReselect;
    {/v0.25}
    {v0.31}
    function GetAddrULADIndex(AAddr: integer): integer;
      { returns ULADIndex of ULADRc, that has given AAddr; if
        not such found - returns 0 (default index) }
    {/v0.31}
    {v0.77}
    { Calculates theoretical plates number for the specified APeak, using
      formula N = 5.54 * (t/w)^2, i.e. retention time divided by halfwidth
      squared multiplied by constant 5.54. Returns also the points found and
      used for calculation of the halfwidth. }
    function CalcTheorPlates(APeak: TULPRObj; var ph1: TExpPoint; var ph2: TExpPoint): extended;

    {/v0.77}


    property MultiplyX: TXValue read FMultiplyX;
    property MultiplyY: TYValue read FMultiplyY;
    property AddX: TXValue read FAddX;
    property AddY: TXValue read FAddY;
    property LogY: boolean read FLogY;
    {/v0.11}
    {v0.15}
    procedure Truncate(AfterX: TXValue);
      { remove all acqisition data (together with peaks, baselines)
        after specified X value }
    {/v0.15}

    {v0.50}
    function DataNamesEqual(ADataName1: string; ADataName2: string): boolean;
    procedure FormRegister(AForm: TObject);
    procedure FormUnregister(AForm: TObject);
    {/v0.50}
    {v0.55}
    function CheckCalibrationFilePresent: boolean;
      { returns true if ulm.calibrationfilename exists; should be called
        only if the name <> '' }
    {/v0.55}

    {v0.61}
    { Makes sure that all peaks are visible }
    procedure FitPeaksInView;
    {/v0.61}
    procedure CalPeaksFree;

    { Used if opened in omCreate mode, sets pointer to channel which
      devices used to acquire the data, eventually increase number of ULADs
      accorging to number detector devices in the channel. Returns false
      if a channel of ULI.ChannelName could not be located and set FChannel
      to nil and ULI.ChannelName to ''. }
    {$IFNDEF CONSOLE}
    function CheckChannel: boolean;
    {$ENDIF}
    { Called just before starting acquisition (from TAcqInfo.Create
      eventually from AnalSetupForm), tries to locate the channel
      and create datastreams for acquired data. Raises exception if channel
      not found or the channel already in use. }
    procedure CreateDataStreams;
    procedure ProgramBrowse;
    { Assign amino acid analyzer program object. Used only for data files
      created by AAA. }
    procedure AAPGAssign(AAAPG: TAAPGObj);
    {/public methods}

    {properties}
    {v0.16}
    property IsTemplate: boolean read GetIsTemplate;
    {/v0.16}
    {v0.24}
    property RawFileName[Index: integer]: string read GetRawFileName;
    property ActiveData: TStream read GetActiveData;
    property DataIndex: integer read GetDataIndex write SetDataIndex;
    property DataCount: integer read GetDataCount;
    property ActiveULADRc: TULADRc read GetActiveULADRc;
    {/v0.24}
    {v0.25}
    property ActiveDataName: string read GetActiveDataName write SetActiveDataName;
    property UsrActiveDataName: string read GetUsrActiveDataName write SetUsrActiveDataName;
    {/v0.25}
    {v0.57}
    property UsrActiveULADRc: TULADRc read GetUsrActiveULADRc write SetUsrActiveULADRc;
    {/v0.57}
    {v0.31}
    property ULI: TULIObj read FULI write SetULI;
    property ULDetAddrList: string read GetDetAddrList;
    {/v0.31}
    {v0.41}
    {$IFNDEF CONSOLE}
    property Channel: TChannel read GetChannel;
    {$ENDIF}
    {/v0.41}
    {v0.50}
    property ULI_USP: TUSPObj read GetULI_USP;
    property Prg: TSeqPrg read GetPrg;
    {/v0.50}
    {v0.61}
    { ReadOnly property, is true if the file has .ULC extension, i.e. holds only
      peaks for calibration (no acquisition data). }
    //property IsCalFile: boolean read GetIsCalFile;
    {/v0.61}
    { Returns an object holding AAA program. It is nil for non AAA data files.
      Non nil only when created by AAA processor; holds program used for
      running the sequence sample, assigned just at the program/sample start
      using AAPGAssign method.}
    //property AAPG: TAAPGObj read FAAPG;
    { Returns ULM.MethodFileName if non empty, otherwise ULM.MethodTemplate }
    property ActiveMethodFileName: string read GetActiveMethodFileName;

    {/properties}
  end;
  {/TAcqData}


  {TSpectrum}
  TSpectrum = class(TObject)
    {SpectrumOpt:TSpectrumOpt;{set by init, parameters for calculation
      of peaks (used in FinishPeak method, eventually in AutoDetect)}
    AcqData: TAcqData;
      { Set by Create; data points to investigate. }
    Peaks: TULPObj;
      { Peaks, user defined or autodetected. TULPObj owned by AcqData.ULA. }
    BaseLine: TULBObj;
    {ReportData:TReportData;{report created during CalculatePeaksRatio}
    AutoDetectPeaks: boolean;
      { False by default; used in DoPeak method to decide if some kind
        of autodetection of peak end and peak start should be tried }
    ProcPeak: TProcPeak;
      { Parameters for processing current peak, i.e. the one started
        by StartPeak method and finished by FinshPeak method; inbetween
        some calls for DoPeekPoint should be called for all peaks between
        those two. }
    BLPeak: TProcPeak;
    {v0.11}
    LastResult: TSpectrumResult;
    {/v0.11}
    constructor Create(AData: TAcqData);
      { if non nil, overrides AData.SpectrumOpt}
    procedure SetData(AData: TAcqData);
    procedure OrderPoints(var P1, P2: TIndexPoint);
      { Make sure P1.X < P2.X }
    procedure DoPoint(const APoint: TIndexPoint);
      { Used in autodetect to find start and end of peak, calls
        StartPeak, DoPeakPoint and FinishPeak, when it think it's
        appropriate }
    procedure StartPeak(const APoint: TIndexPoint);
      procedure ClearProcPeak;
      procedure ClearBLPeak;
    procedure FinishPeak(const APoint: TIndexPoint);
      function CountHeight(var P1, P2: TIndexPoint; var X:TXValue): TYValue;
      function CountArea(var P1, P2: TIndexPoint): TAreaSize;
      function CountWidth(var P1, P2: TIndexPoint; H: TYValue): TXValue;

    procedure PeakCreate(const P1, P2: TIndexPoint);
      { Calls StartPeak(P1), then FinishPeak(P2); used usually when
        defining peaks  manually}

    procedure BaseLineCreate(const P1, P2: TIndexPoint);
      procedure StartBaseLine(const APoint: TIndexPoint);
      procedure FinishBaseLine(const APoint: TIndexPoint);
    procedure BaseLineMerge;
      { remove all inner points from neighbouring selected baseline sections -
        make just one section }
    procedure BaseLineGroup;
      { makes selected neighbouring baseline sections grouped - move edges
        together }
    procedure BaseLineUngroup;
      { makes grouped baseline sections independent }
    procedure DoBaseLineLimitsChanged(b: TULBRObj);
      {should be called if some X value of b changed manually,
        calls UpdateBaseLineRecY and recalculate peaks that are above
        this baseline rec }
      procedure UpdateBaseLineRecY(b: TULBRObj);
      { finds corresponding Y values to b.X1,X2 values in data points }

    function AutoDetectOld: boolean;
    function AutoDetect: boolean;
      { Can be called if automatic detection of peaks is desired (ignores
        AutoDetectPeaks value). }
      function AutoDetectForBase: boolean;
      procedure BaseLineUpdateFromMethod;
        { Called after AutodetectFromBase to remove or split those
          autodetected baseline records, that would overlap with those
          defined in method ( - where peaks should not be autodetected) }
      {v0.25}
      procedure BaseLineRemoveDuplicates;
        { check for overlapped baseline records (added by autodetecting
          for additional wavelengths), and delete them if found }
      {/v0.25}
      function AutoDetectForPeaks: boolean;
    {procedure PeaksClear;}
      { Disposes all peaks. }
    procedure RecalculatePeaks;
      { Recalculates percentual content of peak areas; call after
        all peaks created or peak limits change }
      procedure RecalculatePeak(Peak: TULPRObj);
      procedure PeaksCalculateRatios;
    function IsScreenXInPeak(X: TScreenX; const AScreenDisp:TScreenDisp;
      var PeakIndex: integer): boolean;
    function IsScreenPointInPeakEdge(X: TScreenX; Y: TScreenY;
      const AScreenDisp: TScreenDisp; var PeakIndex: integer): boolean;

    function IsScreenPeakStart(X: TScreenX; const AScreenDisp: TScreenDisp;
       var PeakIndex: integer): boolean;
    function IsScreenPeakStop(X: TScreenX; const AScreenDisp: TScreenDisp;
      var PeakIndex: integer): boolean;
    function MoveScreenPeak(OldStartX, NewStartX: TScreenX; const AScreenDisp: TScreenDisp;
      var PeakIndex: integer): boolean;
    function SizeScreenPeak(OldStopX, NewStopX: TScreenX; const AScreenDisp: TScreenDisp;
      var PeakIndex: integer; Right:boolean): boolean;
    procedure ToggleSelectScreenPeak(X: TScreenX; const AScreenDisp: TScreenDisp; Shift: TShiftState);
    procedure GetScreenPeaks(X: TScreenX; const AScreenDisp: TScreenDisp; var PrevPeak: TScreenPeak;
      var CurPeak: TScreenPeak; var NextPeak: TScreenPeak);
      { Get info about peaks around given screen X coordinate; if xPeak.X1 < 0 then
        no such peak found }

    procedure GetScreenBaseLineRec(X: TScreenX; const AScreenDisp: TScreenDisp;
      var CurIndex: integer; var PrevBL: TScreenPeak;
      var CurBL: TScreenPeak; var NextBL: TScreenPeak);
      { Get info about baseline records around given screen X coordinate; if xPeak.X1 < 0 then
        no such peak found }
    {v0.09}
    procedure GetScreenBaseLineNeighbours(Index:integer; const AScreenDisp: TScreenDisp;
      var PrevBL: TScreenPeak; var CurBL: TScreenPeak; var NextBL: TScreenPeak);
      { Returns inc CurBL screen coordinates of Index baseline section, plus
        in PrevBL and NextBL coordinates of sections on the left and on the right }
    {/v0.09}
    function IsScreenXInBaseLine(X: TScreenX; const AScreenDisp:TScreenDisp;
      var PeakIndex: integer): boolean;
    function IsScreenPointInBaseLineEdge(var X: TScreenX; Y: TScreenY;
      const AScreenDisp: TScreenDisp; var PeakIndex: integer; var LeftEdge:boolean): boolean;
    function MoveScreenBaseLine(OldStartX, NewStartX: TScreenX; const AScreenDisp: TScreenDisp;
      var Index: integer): boolean;
    function SizeScreenBaseLine(OldStopX, NewStopX: TScreenX; const AScreenDisp: TScreenDisp;
      var PIndex: integer; Right:boolean; SizingTwo:boolean): boolean;
    procedure ToggleSelectScreenBaseLine(X: TScreenX; Y: TScreenY;
      const AScreenDisp: TScreenDisp; Shift: TShiftState);
    procedure BaseLineInsertPointFromScreen(const AScreenDisp: TScreenDisp; X: TScreenX);


    procedure PeaksFindYAndIndexValues;
    procedure PeakFindYAndIndexValues(p: TULPRObj{v0.26}; ReselectUsrData: boolean{/v0.26});
      function FindBaseY(const ADataName: string; X: TXValue; var Y: TYValue):boolean;

    procedure DoPeakLimitsChanged(p: TULPRObj);

    procedure CopySelectedPeaksToMethod;
    procedure CopySelectedBaseLineToMethod;
    procedure PeaksAssignParamsFromMethod;
    procedure PeakAssignParamsFromMethod(p: TULPRObj);
    function PeakFindPeakInMethodByX(x: TXValue; var mp: TULPRObj):boolean;
    function PeakFindInPeaksByX(pl: TULPObj; x: TXValue; var fp: TULPRObj): boolean;
    function PeakFindInPeaksInXRange(pl: TULPObj; const ADataName: string; x, xmin, xmax: TXValue; var fp: TULPRObj): boolean;

    function PeakFindPeakInMethod(p: TULPRObj; var mp: TULPRObj):boolean;
    function PeakFindInPeaks(pl: TULPObj; p: TULPRObj; var fp: TULPRObj): boolean;
      { find in pl peaks the peak fp, that has p.X in fp's window interval }
    function FindPeakByName(pl: TULPObj; const AName: TPeakName; var fp: TULPRObj): boolean;
     { find in pl peaks the peak fp, that has p.Name = AName }

    destructor Destroy; override;

    function ULBRToScreenPeak(BL:TULBRObj; const AScreenDisp: TScreenDisp;
      var SP:TScreenPeak): integer;
    function PeaksAutoCalculate: integer;
      { calls either PeaksResponsesCalculate or PeaksAmountsCalculate,
        depending on CalibrationStandard switch value }
    function PeaksResponsesCalculate: integer;
      { Calculates Response value for all peaks, returns <> 0 if invalid
        values encountered (zero Amount value) }
    function PeaksAmountsCalculate: integer;
      { called from menu, calls the following functions according to
        AcqData method settings }
      {v0.12}
      function PeaksAmountsCalculateCalibrationFile: integer;
        { [recalculate responses in calibration file,] assign responses }
      function PeaksAmountsCalculateInternalStandard: integer;
      function PeaksAmountsCalculateValues: integer;
      {/v0.12
      function PeaksAmountsCalculateFromCalibrationFile: integer;
      function PeaksAmountsCalculateFromInternalStandard: integer;}
    procedure PeakAssignPeakCharacterization(Dest, Src: TULPRObj);
      { assign to Dest peak all Src peak characterization values like name,
        response, groupname }
    procedure ClearResult;
    procedure SetResult(sr: TSpectrumResult; const msg:string);
    function CheckCalFileAge: boolean;
    function CheckCalPeaksLoaded: boolean;

    procedure PeakAmountCalculate(p: TULPRObj; f: TFactor);

  end;
  {/TSpectrum.}


const
  DefSpectrumOpt:TSpectrumOpt =
    { Values usable for y = 0 - 255, and x = time }
  ( MaxPointCount: 0; {no limits for time}
    MaxPeakCount: 100; {we don't know, do we?}
    Trashold: 0;
    BaseLine: 0;
    PeakHeight:(Min:0; Max:0);
    PeakArea:(Min:0; Max:0);
    PeakWidth:(Min:0; Max:0);

    PeakStartLevel : 0;
    PeakEndLevel: 0;
    ViewLimit: (Min: (x: MinAcqXValue; y: MinAcqYValue);
                Max: (x: MaxAcqXValue; y: MaxAcqYValue));
    Limit: (Min: (x: MinAcqXValue; y: MinAcqYValue);
            Max: (x: MaxAcqXValue; y: MaxAcqYValue));
    {UserLimit: (Min: (x: 0; y: MinAcqYValue);
                Max: (x: 20; y: MaxAcqYValue));}
    PointCount:0;
    Extreme:(Min:(x:0;y:0); Max:(x:0;y:0))
  );

procedure CalculatePeaksRatios(Peaks:TULPObj);
function GetChannelFileName({v0.29}const ADir: string;{/v0.29}AChannelName: string): string;
{
procedure InitPacketRec(const ApexPacket:TApexPacket; var ApexRec:TApexPacketRec);
 }
const
  SpectrumBufferSize:longint = 1024;

{v0.36}
{$IFNDEF CONSOLE}
function FindOpenedData(const AFileName: string; var AAcqData: TAcqData{v0.36}; var AForm: TForm{/v0.36}): boolean;
{$ENDIF}
{/v0.36}


{v0.61}
{ Initialize data object for non-acquisition purposes }
function CreateOrOpenDataFile(AFileName: string; var AAcqData: TAcqData): boolean;
{/v0.61}

implementation
uses
{$IFNDEF CONSOLE}
  Main, SpecForm {v0.13}, AnalSetupFrm{/v0.13}
  {v0.14},
  ModuType,
  {$IFDEF USEDLL}
  ModuProc
  {$ELSE}
  Modulu
  {$ENDIF}
  {/v0.14}{v0.45}, ChkULFu{/v0.45},
{$ENDIF}
  CalDatau;


{v0.33}
{$IFNDEF CONSOLE}
function FindOpenedData(const AFileName: string; var AAcqData: TAcqData{v0.36}; var AForm: TForm{/v0.36}): boolean;
var
  f: TForm;
  i: integer;
begin
  Result := false;
  for i := 0 to Screen.FormCount - 1 do begin
    f := Screen.Forms[i];
    if f is TSpectrumForm then with f as TSpectrumForm do begin
      if FindAcqData(AFileName, AAcqData) then begin
        {v0.36}
        AForm := f;
        {/v0.36}
        Result := true;
        exit;
      end;
    end;
  end;
end;
{$ENDIF}
{/v0.33}


{v0.28}
function CombineFactors(Factor, MFactor, DFactor: TFactor): TFactor;
{var
  f: TFactor;}
begin
  if Factor = 0 then
    Factor := 1;
  if MFactor = 0 then
    MFactor := 1;
  if DFactor = 0 then
    DFactor := 1;
  Result := Factor * MFactor / DFactor;
end;
{/v0.28}

function CountYOnLine(X: TXValue; X1: TXValue; Y1: TYValue;
  X2: TXValue; Y2: TYValue): TYValue;
begin
  if (X2 = X1) then
    Result := 0
  else
    Result := Y1 + (Y2 - Y1)/(X2 - X1) * (X - X1);
end;

function GetChannelFileName({v0.29}const ADir: string;{/v0.29}AChannelName: string): string;
begin
  GetChannelFileName := {v0.29}ADir + {/v0.29} '$$$' + AChannelName + '.$$$';
end;

{TPeaks}
procedure CalculatePeaksRatios(Peaks:TULPObj);
var
  a: TAreaSize;
  cnt, i: integer;
  p: TULPRObj;
  c: TUsrPeakCoef;
  {ulmtype}
begin
  a := 0;
  cnt := Peaks.ChildCount;
  for i := 0 to cnt - 1 do begin
    {v0.41}
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID = ULPRID then begin
      c := p.UsrPeakCoef;
      if c = 0 then
        c := 1;
      a := a + p.AreaSize * c;
    end;
    {/v0.41
    a := a + TULPRObj(Peaks.Childs[i]).AreaSize; }
  end;
  if a = 0 then
    exit;
  Peaks.DoChangeLock;
  try
    for i := 0 to cnt - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID = ULPRID then begin
        {v0.41}
        c := p.UsrPeakCoef;
        if c = 0 then
          c := 1;
        p.Ratio := p.AreaSize * c/ a;
        {/v0.41
        p.Ratio := p.AreaSize / a;}
      end;
    end;
  finally
    Peaks.DoChangeUnlock;
  end;
end;

{************************* TSpectrum ****************************}
constructor TSpectrum.Create(AData:TAcqData);
begin
  inherited Create;
  SetData(AData);
end;

procedure TSpectrum.SetData(AData: TAcqData);
begin
  if AData = nil then
    raise Exception.Create('TSpectrum.SetData AData = nil');
  {v0.14}
  if AcqData <> nil then
    AcqData.Spectrum := nil;
  AData.Spectrum := Self;
  {/v0.14}
  AcqData := AData;
  Peaks := TULPObj(AcqData.ULA.FindOrAdd(ULPID, ''));
    Peaks.SetFlag(rfCantDelete, true);
  BaseLine := TULBObj(AcqData.ULA.FindOrAdd(ULBID, ''));
    BaseLine.SetFlag(rfCantDelete, true);
end;

procedure TSpectrum.OrderPoints(var P1, P2:TIndexPoint);
  { Make sure P1.X < P2.X }
var P:TIndexPoint;
begin
  if P1.Point.X > P2.Point.X then begin
    P := P1;
    P1 := P2;
    P2 := P;
  end;
end;

procedure TSpectrum.StartPeak(const APoint: TIndexPoint);
begin
  ClearProcPeak;
  with ProcPeak do begin
    PeakStart := APoint;
    IsInPeak := true;
    CurMin := APoint;
    CurMax := APoint;
  end;
end;

function TSpectrum.CountHeight(var P1, P2: TIndexPoint; var X:TXValue): TYValue;
var
  h: TYValue;
  P: TIndexPoint;
  cnt: TPointCount;
begin
  {OrderPoints(P1, P2);}
  h := 0;
  cnt := AcqData.GetPointCount;
  AcqData.SeekPoint(P1.Index);
  repeat
    if not AcqData.ReadIndexPoint(P) then
      break;
    if P.Point.Y > h then begin
      h := P.Point.Y;
      X := P.Point.X;
    end;
    if P.Index >= P2.Index then
      break;
    dec(cnt);
  until cnt = 0;
  CountHeight := h - AcqData.SpectrumOpt.BaseLine;
end;

function TSpectrum.CountArea(var P1, P2: TIndexPoint): TAreaSize;
var
  sum: TAreaSize;
  LP, P: TIndexPoint;
  cnt: TPointCount;
begin
  cnt := AcqData.GetPointCount;
  {OrderPoints(P1, P2);}
  LP := P1;
  sum := 0;
  if P1.Index <> P2.Index then begin
    AcqData.SeekPoint(P1.Index);
    repeat
      if not AcqData.ReadIndexPoint(P) then
        break;
      if P.Point.X < LP.Point.X then
        break;
      sum := sum + (P.Point.X - LP.Point.X) * (LP.Point.Y -
        AcqData.SpectrumOpt.BaseLine);
      LP := P;
      if P.Index = P2.Index then
        break;
      dec(cnt);
    until cnt = 0;
  end;
  CountArea := sum;
end;

function TSpectrum.CountWidth(var P1, P2: TIndexPoint; H: TYValue): TXValue;
  { Give H (peak height) if you know, just to save same time,
   otherwise set H=0 }
var
  LP, P: TIndexPoint;
  cnt: TPointCount;
  w, x: TXValue;
  w1, w2: TIndexPoint;
  hx:TXValue;{x at h}
begin
  CountWidth := 0;
  {OrderPoints(P1, P2);}
  if h <= 0 then
    h := CountHeight(P1, P2, hx);
  h := h / 2;
  if h < 0 then
    exit;
  x := (P2.Point.X - P1.Point.X) / 2;
  if x <= 0 then
    exit;
  cnt := AcqData.GetPointCount;{NumPoints;}
  w1.Index := NoIndexValue;
  w2.Index := NoIndexValue;
  P1.Point.Y := P1.Point.Y - AcqData.SpectrumOpt.BaseLine;
  LP := P1;
  AcqData.SeekPoint(P1.Index);
  repeat
    if not AcqData.ReadIndexPoint(P) then
      break;
    P.Point.Y := P.Point.Y - AcqData.SpectrumOpt.BaseLine;
    if P.Point.Y > LP.Point.Y then begin
      if (LP.Point.Y <= h) and (P.Point.Y >= h) then
        if (P.Point.X <= x) and (W1.Index = NoIndexValue) then
          W1 := P
    end else if P.Point.Y < LP.Point.Y then begin
      if (LP.Point.Y >= h) and (P.Point.Y <= h) then
        if (P.Point.X >= x) then
          W2 := P;
    end;
    LP := P;
    if P.Index >= P2.Index then
      break;
    dec(cnt);
  until cnt = 0;
  w := 0;
  if W1.Index <> NoIndexValue then begin
    if W2.Index <> NoIndexValue then begin
      w:= W2.Point.x - W1.Point.x;
    end else
      w := 2 * (x - W1.Point.x);
  end else begin
    if W2.Index <> NoIndexValue then
      w := 2 * (W2.Point.x - x);
  end;
  CountWidth := w;
end;

procedure TSpectrum.FinishPeak(const APoint:TIndexPoint);
var
  Peak: TULPRObj;
  hw, hx: TXValue;
  h, a: TYValue;
  pe: TIndexPoint;

label
  ex;
begin
  pe := APoint;
  with ProcPeak do begin
    h := CountHeight(PeakStart, pe, hx);
    if (h < AcqData.SpectrumOpt.PeakHeight.Min) and
      (AcqData.SpectrumOpt.PeakHeight.Min <> 0) then
      goto ex;
    if (h > AcqData.SpectrumOpt.PeakHeight.Max) and
      (AcqData.SpectrumOpt.PeakHeight.Max <> 0) then
      goto ex;

    a := CountArea(PeakStart, pe);
    if (a < AcqData.SpectrumOpt.PeakArea.Min) and
       (AcqData.SpectrumOpt.PeakArea.Min <> 0) then
      goto ex;
    if (a > AcqData.SpectrumOpt.PeakArea.Max) and
       (AcqData.SpectrumOpt.PeakArea.Max <> 0) then
      goto ex;

    hw := CountWidth(PeakStart, pe, h);
    if (hw < AcqData.SpectrumOpt.PeakWidth.Min) and
       (AcqData.SpectrumOpt.PeakWidth.Min <> 0) then
      goto ex;
    if (hw > AcqData.SpectrumOpt.PeakWidth.Max) and
      (AcqData.SpectrumOpt.PeakWidth.Max <> 0) then
      goto ex;




    Peak := TULPRObj(Peaks.Add(ULPRID));
    Peak.DoChangeLock;
    try
      Peak.X1 := PeakStart.Point.X;
      Peak.Y1 := PeakStart.Point.Y;{Peak.P1 := PeakStart;}
      Peak.Index1 := PeakStart.Index;
      Peak.X2 := pe.Point.X;
      Peak.Y2 := pe.Point.Y;
      Peak.Index2 := pe.Index;
      {Peak^.P2 := pe;}
      Peak.X := hx;{CurMax.Point.X;}
      Peak.Height := h;
      Peak.Width := hw;
      Peak.AreaSize := a;

      DoPeakLimitsChanged(Peak);
      {v0.12}
      PeakAssignParamsFromMethod(Peak);
      {/v0.12}
    finally
      Peak.DoChangeUnlock;
    end;
    Peaks.Sort;
  ex:
    CurMin := pe;
    CurMax := pe;
    IsInPeak := false;
  end;
end;

procedure TSpectrum.RecalculatePeak(Peak: TULPRObj);
var
  p:TIndexPoint;
  maxy:TYValue;
  maxx:TXValue;
  a,a1:TAreaSize;
  x1,x2: TXValue;
  y1,y2: TYValue;
begin
  maxy := -$FFFFF;
  maxx := 0;
  a := 0;
  x1 := Peak.X1;
  y1 := Peak.BaseY1;
  x2 := Peak.X2;
  y2 := Peak.BaseY2;
  {v0.26}
  AcqData.ActiveDataName := Peak.DataName;
  {/v0.26}
  AcqData.SeekPoint(Peak.Index1);
  repeat
    if not AcqData.ReadIndexPoint(P) then
      break;
    if P.Index > Peak.Index2 then
      break;
    if P.Point.Y > maxy then begin
      maxy := P.Point.Y;
      maxx := P.Point.X;
    end;
    a1 := P.Point.Y - CountYOnLine(P.Point.X, x1, y1, x2, y2);
    if a1 > 0 then
      a := a + a1;
    {(P.Point.X - LP.Point.X) * (LP.Point.Y - SpectrumOpt.BaseLine);}
  until false;

  Peak.X := maxx;
  if x1 <> x2 then
    Peak.Height := maxy - CountYOnLine(maxx, x1, y1, x2, y2);
    {AcqData.SpectrumOpt.BaseLine;}
  Peak.AreaSize := a;
  Peak.Width := Peak.X2 - Peak.X1;{not really; just for now}

  {wy := Data.SpectrumOpt.BaseLine + (Peak.Height / 2);}

  (*
  P1.Point.Y := P1.Point.Y - SpectrumOpt.BaseLine;
  LP := P1;
  Data.SeekPoint(P1.Index);
  repeat
    if not Data.ReadIndexPoint(P) then
      break;
    P.Point.Y := P.Point.Y - SpectrumOpt.BaseLine;
    if P.Point.Y > LP.Point.Y then begin
      if (LP.Point.Y <= h) and (P.Point.Y >= h) then
        if (P.Point.X <= x) and (W1.Index = NoIndexValue) then
          W1 := P
    end
    else
    if P.Point.Y < LP.Point.Y then begin
      if (LP.Point.Y >= h) and (P.Point.Y <= h) then
        if (P.Point.X >= x) then
          W2 := P;
    end;
    LP := P;
    if P.Index >= P2.Index then
      break;
    dec(cnt);
  until cnt = 0;
  w := 0;
  if W1.Index <> NoIndexValue then begin
    if W2.Index <> NoIndexValue then begin
      w:= W2.Point.x - W1.Point.x;
    end else
      w := 2 * (x - W1.Point.x);
  end else begin
    if W2.Index <> NoIndexValue then
      w := 2 * (W2.Point.x - x);
  end;
 *)
end;

procedure TSpectrum.RecalculatePeaks;
var
  cnt, i: integer;
begin
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  Peaks.DoChangeLock;
  try
    for i := 0 to cnt - 1 do begin
      RecalculatePeak(TULPRObj(Peaks.Childs[i]));
    end;
    PeaksCalculateRatios;
    Peaks.Sort;
    {v0.11}
    if AcqData.ULM.CalcAmounts then begin
      {v0.13}
      PeaksAutoCalculate;
      {/v0.13
      PeaksAmountsCalculate;}
    end;
    {/v0.11}
  finally
    Peaks.DoChangeUnlock;
    {v0.26}
    AcqData.UsrDataIndexReselect;
    {/v0.26}
  end;
end;

{procedure TSpectrum.DoPeakPoint(const APoint:TIndexPoint);
begin
end;}

procedure TSpectrum.BaseLineCreate(const P1, P2: TIndexPoint);
begin
  if P1.Index <= P2.Index then begin
    StartBaseLine(P1);
    FinishBaseLine(P2);
  end else begin
    StartBaseLine(P2);
    FinishBaseLine(P1);
  end;
end;

procedure TSpectrum.StartBaseLine(const APoint: TIndexPoint);
begin
  ClearBLPeak;
  with BLPeak do begin
    PeakStart := APoint;
    IsInPeak := true;
    CurMin := APoint;
    CurMax := APoint;
  end;
end;

procedure TSpectrum.FinishBaseLine(const APoint:TIndexPoint);
var
  BR: TULBRObj;
{  hw, hx: TXValue;
  h, a: TYValue;}
  pe: TIndexPoint;
label ex;
begin
  pe := APoint;
  with BLPeak do begin
    {h := CountHeight(PeakStart, pe, hx);
    if (h < AcqData.SpectrumOpt.PeakHeight.Min) and
      (AcqData.SpectrumOpt.PeakHeight.Min <> 0) then
      goto ex;
    if (h > AcqData.SpectrumOpt.PeakHeight.Max) and
      (AcqData.SpectrumOpt.PeakHeight.Max <> 0) then
      goto ex;

    a := CountArea(PeakStart, pe);
    if (a < AcqData.SpectrumOpt.PeakArea.Min) and
       (AcqData.SpectrumOpt.PeakArea.Min <> 0) then
      goto ex;
    if (a > AcqData.SpectrumOpt.PeakArea.Max) and
       (AcqData.SpectrumOpt.PeakArea.Max <> 0) then
      goto ex;

    hw := CountWidth(PeakStart, pe, h);
    if (hw < AcqData.SpectrumOpt.PeakWidth.Min) and
       (AcqData.SpectrumOpt.PeakWidth.Min <> 0) then
      goto ex;
    if (hw > AcqData.SpectrumOpt.PeakWidth.Max) and
      (AcqData.SpectrumOpt.PeakWidth.Max <> 0) then
      goto ex;
    }
    BR := TULBRObj(BaseLine.Add(ULBRID));

    BR.X1 := PeakStart.Point.X;
    BR.Y1 := PeakStart.Point.Y;{BR.P1 := PeakStart;}
    {BR.Index1 := PeakStart.Index;}
    BR.X2 := pe.Point.X;
    BR.Y2 := pe.Point.Y;
    {BR.Index2 := pe.Index;}
    {BR.X := hx;
    BR.Height := h;
    BR.Width := hw;
    BR.AreaSize := a;}
    DoBaseLineLimitsChanged(BR);

    BaseLine.Sort;

  ex:
    {CurMin := pe;
    CurMax := pe;}
    IsInPeak := false;
  end;
end;

function TSpectrum.MoveScreenBaseLine(OldStartX, NewStartX: TScreenX;
  const AScreenDisp: TScreenDisp; var Index: integer): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  p: TULBRObj;
  ap: TExpPoint;
{  d: TXValue;
  ap, op1,op2,
  np1,np2: TExpPoint;
  index1,index2: TPointCount;}
begin
  MoveScreenBaseLine := false;
  if not IsScreenXInBaseLine(OldStartX, AScreenDisp, pi) then
    exit;
{  if not IsScreenPeakStart(OldStartX, AScreenDisp, pi) then
    exit;}
  sp.X := NewStartX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  p := TULBRObj(BaseLine.Childs[pi]);

  p.X1 := ap.X;
  DoBaseLineLimitsChanged(p);
  {UpdateBaseLineRecY(p);}


  {
  d := ap.X - p.X1;
  op1.X := p.X1 + d;;
  op1.Y := p.Y1;
  op2.X := p.X2 + d;
  op2.Y := p.Y2;
  if not AcqData.FindDataPoint(op1, np1, index1) then
    exit;
  if not AcqData.FindDataPoint(op2, np2, index2) then
    exit;
  p.X1 := np1.X;
  p.Y1 := np1.Y;
  p.X2 := np2.X;
  p.Y2 := np2.Y;
  }
  MoveScreenBaseLine := true;
end;

procedure TSpectrum.DoBaseLineLimitsChanged(b: TULBRObj);
var
  p: TULPRObj;
  i: integer;
begin
  UpdateBaseLineRecY(b);
  Peaks.DoChangeLock;
  try
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID = ULPRID then begin
        if ((p.X1 >= b.X1) and (p.X1 <= b.X2)) or
           ((p.X2 >= b.X1) and (p.X2 <= b.X2)) then
        begin
          DoPeakLimitsChanged(p);
        end;
      end;
    end;
  finally
    Peaks.DoChangeUnlock;
  end;
end;

procedure TSpectrum.UpdateBaseLineRecY(b: TULBRObj);
var
  op, np: TExpPoint;
  index: TPointCount;
begin
 {v0.25
  exit;}
  op.X := b.X1;
  op.Y := b.Y1;
  if AcqData.FindDataPoint(op, np, index) then begin
    b.X1 := np.X;
    b.Y1 := np.Y;
  end;
  op.X := b.X2;
  op.Y := b.Y2;
  if AcqData.FindDataPoint(op, np, index) then begin
    b.X2 := np.X;
    b.Y2 := np.Y;
  end;
end;

procedure TSpectrum.BaseLineInsertPointFromScreen(const AScreenDisp: TScreenDisp; X: TScreenX);
var
  pi: integer;
  sp: TScreenPoint;
  ap,np: TExpPoint;
  p: TULBRObj;
  n: TULBRObj;
  index: integer;
begin
  if not IsScreenXInBaseLine(X, AScreenDisp, pi) then
    exit;
  sp.X := X;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  if not AcqData.FindDataPoint(ap, np, index) then
    exit;
  p := TULBRObj(BaseLine.Childs[pi]);
  n := TULBRObj(BaseLine.Add(ULBRID));
  n.X1 := np.X;
  n.Y1 := np.Y;
  n.X2 := p.X2;
  n.Y2 := P.Y2;
  n.GroupedLeft := true;
  p.X2 := np.X;
  p.Y2 := np.Y;
  p.GroupedRight := true;
  DoBaseLineLimitsChanged(p);
  DoBaseLineLimitsChanged(n);
  BaseLine.Sort;
end;

procedure TSpectrum.BaseLineMerge;
var
  i: integer;
  p, n: TULBRObj;
begin
  i := 0;
  while i < BaseLine.ChildCount do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.IsFlagSet(rfSelected) then begin
      if (i + 1 < BaseLine.ChildCount) then begin
        n := TULBRObj(BaseLine.Childs[i + 1]);
        if n.IsFlagSet(rfSelected) then begin
          if n.CanDestroy then begin
            p.X2 := n.X2;
            p.Y2 := n.Y2;
            p.GroupedRight := n.GroupedRight;
            n.Free;
            DoBaseLineLimitsChanged(p);
            continue;
          end;
        end;
      end;
    end;
    inc(i);
  end;
end;

procedure TSpectrum.BaseLineGroup;
var
  i: integer;
  p, n: TULBRObj;
begin
  i := 0;
  while i < BaseLine.ChildCount do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.IsFlagSet(rfSelected) then begin
      if (i + 1 < BaseLine.ChildCount) then begin
        n := TULBRObj(BaseLine.Childs[i + 1]);
        if n.IsFlagSet(rfSelected) then begin
          p.X2 := n.X1;
          p.Y2 := n.Y1;
          p.GroupedRight := true;
          n.GroupedLeft := true;
          DoBaseLineLimitsChanged(p);
          DoBaseLineLimitsChanged(n);
        end;
      end;
    end;
    inc(i);
  end;
end;

procedure TSpectrum.BaseLineUnGroup;
var
  i: integer;
  p{, n}: TULBRObj;
begin
  i := 0;
  while i < BaseLine.ChildCount do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.IsFlagSet(rfSelected) then begin
      p.GroupedRight := false;
      p.GroupedLeft := false;
    end;
    inc(i);
  end;
end;

function TSpectrum.SizeScreenBaseLine(OldStopX, NewStopX: TScreenX;
  const AScreenDisp: TScreenDisp; var PIndex: integer; Right: boolean;
  SizingTwo: boolean): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  ap: TExpPoint;
  p: TULBRObj;
  np: TExpPoint;
  index: TPointCount;
begin
  SizeScreenBaseLine := false;
  if not IsScreenXInBaseLine(OldStopX, AScreenDisp, pi) then
    exit;
  {
  if not IsScreenPeakStop(OldStopX, AScreenDisp, pi) then
    exit;
  }
  sp.X := NewStopX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  if not AcqData.FindDataPoint(ap, np, index) then
    exit;
  p := TULBRObj(BaseLine.Childs[pi]);
  if right then begin
    if p.X1 >= np.X{Index1 >= index} then begin
      {if p.CanDestroy then
        p.Free;}
      exit;
    end;
    p.X2 := np.X;
    p.Y2 := np.Y;
    {p.Index2 := index;}
  end else begin
    if p.X2 <= np.X{p.Index2 <= index} then begin
      {if p.CanDestroy then
        p.Free;}
      exit;
    end;
    p.X1 := np.X;
    p.Y1 := np.Y;
    {p.Index1 := index;}
  end;
  DoBaseLineLimitsChanged(p);

  if SizingTwo then begin
    { sizing effect also neighbour baseline section: }
    if right then begin
      { section on the right affected, i.e. new positin (np) is left edge of the section }
      inc(pi);
      if pi >= BaseLine.ChildCount then
        exit; { .. there is no more sections }
      p := TULBRObj(BaseLine.Childs[pi]);
      if np.X >= p.X2 then begin
        exit; {left limit can not be more or equal to the right limit of the section}
      end;
      p.X1 := np.X;
      p.Y1 := np.Y;
      {p.Index2 := index;}
    end else begin
      { section on the left affected: }
      dec(pi);
      if pi < 0 then
        exit;
      p := TULBRObj(BaseLine.Childs[pi]);
      if np.X <= p.X1 then begin
        exit;
      end;
      p.X2 := np.X;
      p.Y2 := np.Y;
      {p.Index1 := index;}
    end;
    DoBaseLineLimitsChanged(p);
  end;
  SizeScreenBaseLine := true;
end;


procedure TSpectrum.PeakCreate(const P1, P2: TIndexPoint);
begin
  if P1.Index <= P2.Index then begin
    StartPeak(P1);
    FinishPeak(P2);
  end else begin
    StartPeak(P2);
    FinishPeak(P1);
  end;
end;

procedure TSpectrum.DoPoint(const APoint: TIndexPoint);
begin
  with ProcPeak do begin
    if APoint.Point.Y > CurMax.Point.Y then begin
      CurMax := APoint;
      CurMin := APoint;
    end;

    if APoint.Point.Y < CurMin.Point.Y then
      CurMin := APoint;

    if AutoDetectPeaks then begin
      if not IsInPeak then begin
        {the peak was not started yet}
        if APoint.Point.Y > (AcqData.SpectrumOpt.BaseLine +
          AcqData.SpectrumOpt.PeakStartLevel) then
        begin
          {we came in peaks area}
          StartPeak(APoint);
        end else begin
          {we are under peaks area}
          if APoint.Point.Y > Point_1.Point.Y then begin
            {we are going up}
            if Point_1.Point.Y < Point_2.Point.Y then begin
              {but were going down before}
              {i.e. point_1 is local minimum, push min value}
              LocMin_2 := LocMin_1;
              LocMin_2 := Point_1;
    {
              if Point_1.Y < CurMin.Y then
                CurMin := Point_1;
            {  if Point_1.Y}
            end else begin
              {and were also going up or resting before}
              {i.e. the line is still raising below peeks level}
            end;
          end else begin
            {we are going down or resting below peeks level}
            if APoint.Point.Y < Point_1.Point.Y then begin
              {we are going down}
              if Point_1.Point.Y > Point_2.Point.Y then begin
                {but were going up before}
                {i.e. point_1 is local maximum, push max value}
                LocMax_2 := LocMax_1;
                LocMax_1 := Point_1;
              end else begin
                {and were also going down or resting before, below peeks level}
              end;
            end else begin
              {y did not change}
            end;
          end;
          {end of under peaks area}
        end;
      end else begin
        {we started to create a peak some time ago}
        if APoint.Point.Y <= (AcqData.SpectrumOpt.BaseLine +
          AcqData.SpectrumOpt.PeakEndLevel) then
        begin
          {we are leaving peaks area}
          FinishPeak(APoint);
        end else begin
          {we are in peaks area}
          if APoint.Point.Y > Point_1.Point.Y then begin
            {we are going up}
            if Point_1.Point.Y < Point_2.Point.Y then begin
              {but were going down before}
              {i.e. point_1 is local minimum, push min value}
              if Max_1.Point.Y - APoint.Point.Y > AcqData.SpectrumOpt.PeakHeight.Min then
              begin
                Min_2 := Min_1;
                Min_1 := Point_1;
                FinishPeak(Point_1);
              end;
              LocMin_2 := LocMin_1;
              LocMin_2 := Point_1;
            {  if Point_1.Y}
            end else begin
              {and were also going up before}
              {i.e. the line is still raising, and we are in the peek}
              if (APoint.Point.Y - CurMin.Point.Y) > AcqData.SpectrumOpt.PeakHeight.Min then
              begin
                {raised enough from the last minimum so that it could be
                 considered to be a new peak}
                if (CurMax.Point.Y - CurMin.Point.Y) > AcqData.SpectrumOpt.PeakHeight.Min then
                begin
                  {check also if the last extremes dif could belong to peak}
                  if CurMax.Point.X < CurMin.Point.X then
                    FinishPeak(CurMin);
                end;
              end;
            end;
          end else if APoint.Point.Y < Point_1.Point.Y then begin
            {we are going down}
            if Point_1.Point.Y > Point_2.Point.Y then begin
              {but were going up before}
              {i.e. point_1 is local maximum, push max value}
              LocMax_2 := LocMax_1;
              LocMax_1 := Point_1;
            end else if Point_1.Point.Y < Point_2.Point.Y then begin
                {and were also going down before}
            end;
          end;{else  y did not change}
          {end, in peak area}
        end;
        {end inpeak}
      end;

      Point_2 := Point_1;
      Point_1 := APoint;
      {if IsInPeak then
        DoPeakPoint(APoint);}
    end;{if AutoDetectPeaks}
  end;{with ProcPeak}
end;{DoPeak}

procedure TSpectrum.ClearBLPeak;
begin
  FillChar(BLPeak, sizeof(ProcPeak), 0);
end;

procedure TSpectrum.ClearProcPeak;
begin
  FillChar(ProcPeak, sizeof(ProcPeak), 0);
{
  Point_1.X := NoIndexValue;
  Point_2.X := NoIndexValue;
  LastLocExt.Min.X := NoIndexValue;
  LastLocExt.Max.X := NoIndexValue;
}
end;

procedure XYFilterGaus(points:PExpPoints;count:integer;S:single);
const
  Gbits=6;
  Gmask=(1 shl Gbits)-1;
var
  Gaus:array[0..Gmask]of TYValue;
  Yhist:array[0..Gmask]of TYValue;
  ip,ig:integer;
  Glen:integer;
  DX,X:TXValue;
  Gsum,Y:TYValue;
begin
  {v0.11}
  MathCoprMaskUnderflow; {No exception on rounding to zero}
  {/v0.11}
  if (count<16)or(S=0) then exit;
  DX:=(points^[count-1].X-points^[0].X)/(count-1);
  ig:=0; Gsum:=0; X:=0;
  repeat
    {v0.30 was causing floating point overflow}
    Y := 1/sqrt(2*pi);
    Y := Y/S;
    Y := Y*exp(-(X*X)/(S*S));
    {/v0.30
    Y:=1/sqrt(2*pi)/S*exp(-(X*X)/(S*S));}
    Gaus[ig]:=Y;
    Gsum:=Gsum+Y;
    inc(ig); X:=X+DX;
  until (ig>Gmask) or (Y<Gaus[0]*0.01);
  Glen:=ig-1;
  if Glen<=1 then exit;
  Y:=points^[0].Y;
  for ip:=0 to Gmask do Yhist[ip]:=Y;
  for ip:=0 to count-1 do
  begin
    Y:=points^[ip].Y;
    Yhist[ip and Gmask]:=Y;
    Y:=Y*Gaus[0];
    for ig:=1 to Glen do
    begin
      if ip+ig<count then
        Y:=Y+points^[ip+ig].Y*Gaus[ig]
      else
        Y:=Y+points^[count-1].Y*Gaus[ig];
      Y:=Y+Yhist[(ip-ig)and Gmask]*Gaus[ig];
    end;
    points^[ip].Y:=Y/Gsum;
  end;
end;

procedure XYDiff2Ord(points:PExpPoints;count:integer);
var
  i: integer;
  DiffX: TXValue;
  DiffA,DiffB: TYValue;
begin
  if count<3 then exit;
  DiffB:=0;
  for i:=0 to count-2 do
  begin
    DiffA:=DiffB;
    DiffX:=points^[i+1].X-points^[i].X;
    if DiffX=0 then DiffX:=0.001;
    DiffB:=(points^[i+1].Y-points^[i].Y)/DiffX;
    points^[i].Y:=DiffB-DiffA;
  end;
  points^[count-1].Y:=-DiffB;
end;

type
  BoolArray=array[0..MaxLongint div 8] of boolean;
  PBoolArray=^BoolArray;

{ Classify points as base or candidates for peaks
  and store result in isbase
    BaseMinInterval .. minimal length of couse satisfiing conditions
    BaseMaxDiff .. maximal absolute value of second order derivative
                   of course, if zero, automatic tuning is applied
}
procedure XYClassBase2Der(points:PExpPoints;count:integer;isbase:PBoolArray;
                      BaseMinInterval,BaseMaxDiff:single);
var
  diffp: PExpPoints;
  i, ib: integer;
  LastX: TXValue;
  MinDiff: TXValue;
  LocalMaxDiff: TXValue;
  zeropt,allpt: integer;
begin
  diffp:=nil;
  {v0.12}
  if Count <= 0 then
    exit;
  {/v0.12}
  try
    {Compute filtered second order derivative}
    GetMem(diffp,count*sizeof(TExpPoint));
    Move(points^,diffp^,count*sizeof(TExpPoint));
    XYDiff2Ord(diffp,count);
    XYFilterGaus(diffp,count,BaseMinInterval/2);
    {Find appropriate value when BaseMaxDiff is zero}
    if BaseMaxDiff=0 then
    begin
      MinDiff:=1e37;
      for i:=0 to count-1 do
      begin
        LocalMaxDiff:=0; ib:=i; zeropt:=0; allpt:=0;
        while (diffp^[ib].X-diffp^[i].X)<2*BaseMinInterval do
        begin
          inc(allpt);
          if diffp^[ib].Y=0 then inc(zeropt) else
            if LocalMaxDiff<abs(diffp^[ib].Y) then
              LocalMaxDiff:=abs(diffp^[ib].Y);
          inc(ib);
          if ib>count-1 then begin LocalMaxDiff:=1e37; break; end;
        end;
        if (LocalMaxDiff<MinDiff)and(zeropt<allpt/4) then
            MinDiff:=LocalMaxDiff;
      end;
      BaseMaxDiff:=10*MinDiff;
      (*Sum:=0;
      for i:=0 to count-1 do
      begin
        Sum:=Sum+Exp(-abs(diffp^[i].Y));
      end;
      BaseMaxDiff:=-Ln(Sum/count);*)
      DebLog('Computed BaseMaxDiff = '+FloatToStr(BaseMaxDiff));
    end;
    {Classify points}
    LastX:=diffp^[0].X; ib:=0;
    for i:=0 to count-1 do
    begin
      if abs(diffp^[i].Y)<=BaseMaxDiff then
      begin
        if (diffp^[i].X-LastX)>=BaseMinInterval then
        begin
          while ib<=i do
          begin
            isbase^[ib]:=true;
            inc(ib);
          end;
        end;
      end else begin
        LastX:=diffp^[i].X;
        while ib<=i do
        begin
          isbase^[ib]:=false;
          inc(ib);
        end;
      end;
    end;
  finally
    if diffp <> nil then
      FreeMem(diffp,count*sizeof(TExpPoint));
  end;
end;

{ Classify points as base or candidates for peaks
  and store result in isbase
    BaseMinInterval .. minimal length of couse satisfiing conditions
    BaseMaxSn .. maximal standart deviation of baseline course,
                 if zero, automatic tuning is applied
}
procedure XYClassBaseStdDev(points: PExpPoints; count: integer;
  isbase: PBoolArray; BaseMinInterval, BaseMaxStdDev: single);
var
  disp: PExpPoints;
  i, j, ib, iblock: integer;
  LastX, DX: TXValue;
  npoints: integer;
  SumY,SumY2:double;
  SumYNxt,SumY2Nxt:double;
  ADisp:single;
  BaseMaxDisp:single;

  {$IFNDEF CONSOLE}
  {m}f:TForm;{/m}
  {$ENDIF}
begin
  disp := nil;
  {v0.12}
  if Count <= 0 then
    exit;
  {/v0.12}
  try
    {v0.11}
    MathCoprMaskUnderflow; {No exception on rounding to zero}
    {/v0.11}
    {Consider constant sampling frequency -- It is not ptimal}
    DX:=(points^[count-1].X-points^[0].X)/(count-1);
    if DX=0 then exit;
    npoints:=round(BaseMinInterval/DX);
    if npoints<10 then npoints:=10;
    if count<2*npoints then exit;

    {Compute filtered standard deviation}
    GetMem(disp,count*sizeof(TExpPoint));
    Move(points^,disp^,count*sizeof(TExpPoint));
    XYFilterGaus(disp,count,BaseMinInterval/4);

    {Find appropriate value when BaseMaxStdDev is zero}
    if BaseMaxStdDev=0 then
    begin
      BaseMaxStdDev:=1e-5;
    end;
    BaseMaxDisp:=Sqr(BaseMaxStdDev)*Sqr(npoints);

    {Compute disperse for moving window
     Computation must be restarted after short block,
     because float format can accumulate precission errors
     A+B+C-A-B-C <> 0 }
    SumYNxt:=0;SumY2Nxt:=0;
    i:=0;
    while i<npoints do
    begin
      SumYNxt:=SumYNxt+disp^[i].Y; SumY2Nxt:=SumY2Nxt+Sqr(disp^[i].Y);
      inc(i)
    end;
    j:=0;
    while i<count do
    begin
      iblock:=i+npoints;
      if iblock>count then iblock:=count;
      SumY:=SumYNxt;SumY2:=SumY2Nxt;
      SumYNxt:=0;SumY2Nxt:=0;
      repeat
        ADisp:=SumY2*npoints-Sqr(SumY);
        SumY:=SumY-disp^[j].Y; SumY2:=SumY2-Sqr(disp^[j].Y);
        SumY:=SumY+disp^[i].Y; SumY2:=SumY2+Sqr(disp^[i].Y);
        SumYNxt:=SumYNxt+disp^[i].Y; SumY2Nxt:=SumY2Nxt+Sqr(disp^[i].Y);
        disp^[j].Y:=ADisp;
        inc(i);inc(j);
      until i>=iblock;
    end;
    npoints:=npoints div 2;
    {Center compuded disperse around corresponding X}
    dec(i);dec(j);
    while i>count-npoints do
    begin
      disp^[i].Y:=disp^[j].Y;
      dec(i);
    end;
    while j>=0 do
    begin
      disp^[i].Y:=disp^[j].Y;
      dec(i);dec(j);
    end;
    while i>=0 do
    begin
      disp^[i].Y:=disp^[i+1].Y;
      dec(i);
    end;

    {Classify points}
    LastX:=disp^[0].X; ib:=0;
    for i:=0 to count-1 do
    begin
      if disp^[i].Y<=BaseMaxDisp then
      begin
        if (disp^[i].X-LastX)>=BaseMinInterval then
        begin
          while ib<=i do
          begin
            isbase^[ib]:=true;
            inc(ib);
          end;
        end;
      end else begin
        LastX:=disp^[i].X;
        while ib<=i do
        begin
          isbase^[ib]:=false;
          inc(ib);
        end;
      end;
    end;
    {Display disperse course for debug purposes}
    if {ulanglob}(UserMode = umSysOp) {v0.12}and false {/v0.12} then {ulantype}
    begin
      {$IFNDEF CONSOLE}
      f := Application.MainForm.ActiveMDIChild;
      if (f <> nil) and (f is TSpectrumForm) then with f as TSpectrumForm do begin
        {v0.11}
        MathCoprMaskUnderflow; {No exception on rounding to zero}
        {/v0.11}
        for i := 0 to count - 1 do begin
          {v0.12}
          if disp^[i].Y < 0 then
            disp^[i].Y := 0
          else
          {/v0.12}
            disp^[i].Y := sqrt(disp^[i].Y) / npoints;
        end;
        AddData(TAcqData.CreateFromPoints(disp, count));
      end;
      {$ENDIF}
    end;
  finally
    if disp <> nil then
      FreeMem(disp,count*sizeof(TExpPoint));
  end;
end;

{ Find index of nearest [X,Y] point for given X }
function XYFindXIndex(points:PExpPoints;count:integer;
                      Xval:TXValue):integer;
var
  ib, ie, i: integer;
begin
  if count<1 then
    begin Result:=-2; exit; end;
  if points^[0].X>Xval then
    begin Result:=-1; exit; end;
  ib:=0;
  ie:=count;
  repeat
    i:=(ib+ie) div 2;
    if Xval<points^[i].X then ie:=i
                         else ib:=i;
  until ib=ie-1;
  Result:=ib;
end;

{ Autodetect baseline }
function TSpectrum.AutoDetectForBase: boolean;
var
  X, X1: TXValue;
  Y: TYValue;
  n: integer;
  ipt, ipt2, cpt: integer;
  newbaseln: TULBRObj;{ in ULBRType.PAS }
  points: TMemoryStream;
  pa: PExpPoints;{ulantype}
  isbase: PBoolArray;
  bmi,bmd: single;

  dname: string;
  fp: TULPRObj;
begin
  Result := false;
  points := nil;
  isbase := nil;
  {v0.11}
  newbaseln := nil;
  cpt := 0;
  {/v0.11}
  try
    {v0.11}
    MathCoprMaskUnderflow; {No exception on rounding to zero}
    if AcqData.{v0.24}ActiveData.Size{/v0.24 ULAD.Data.Size} = 0 then
      exit;
    {/v0.11}                         {ulbrobju}
    points := TMemoryStream.Create;
    dname := AcqData.ActiveULADrc.ULAD.DataName;{uladtype}
    AcqData.{v0.24}ActiveData{/v0.24 ULAD.Data}.Position := 0;
    points.CopyFrom(AcqData.{v0.24}ActiveData{/v0.24 ULAD.Data}, AcqData.ActiveData.Size);
    cpt := AcqData.ActiveData.Size div sizeof(TExpPoint);
    pa := PExpPoints(points.Memory);
    {v0.25}{/v0.25 BaseLine.Clear;}

    {Find candidates for base}
    GetMem(isbase, cpt * sizeof(boolean));
    {v0.12}
    FillChar(isbase^, cpt * sizeof(boolean),0);
    {/v0.12}
    bmi := AcqData.ULM.BaseMinInterval;
    bmd := AcqData.ULM.BaseMaxDiff;
    XYClassBaseStdDev(pa, cpt, isbase, bmi, bmd);
    {XYClassBase2Der(pa, cpt, isbase, bmi, bmd);}

    {Create base description}
    ipt:=0;
    while ipt<cpt do
    begin
      if isbase^[ipt] then inc(ipt)
      else
      begin
        {v0.11}
        if newbaseln <> nil then begin
          if newbaseln.X2 - newbaseln.X1 < bmi then
            newbaseln.Free;
        end;
        {/v0.11}

        newbaseln := TULBRObj(BaseLine.Add(ULBRID));
        {v0.25}
        newbaseln.DataName := AcqData.ActiveULADRc.ULAD.DataName;
        {/v0.25}
        {Find aproximate left base point}
        ipt2:=ipt; X1:=pa^[ipt2].X;
        repeat
          if ipt2=0 then break;
          if not isbase^[ipt2-1] then break;
          dec(ipt2);
        until (X1-pa^[ipt2].X>bmi/4);
        X:=0; Y:=0; n:=0;
        repeat
          X:=X+pa^[ipt2].X; Y:=Y+pa^[ipt2].Y; inc(n);
          if ipt2=0 then break;
          if not isbase^[ipt2-1] then break;
          dec(ipt2);
        until (X1-pa^[ipt2].X>bmi/2);
        newbaseln.X1:=X/n; newbaseln.Y1:=Y/n;
        {Find right base point}
        repeat
          {v0.28}
          {/v0.28 if ipt>=cpt-1 then break;}
          inc(ipt);
          {v0.28}
          if ipt>=cpt then break;
          {/v0.28}
        until isbase^[ipt];
        ipt2:=ipt-1; X1:=pa^[ipt2].X;
        repeat
          if ipt2>=cpt-1 then break;
          if not isbase^[ipt2+1] then break;
          inc(ipt2);
        until (pa^[ipt2].X-X1>bmi/4);
        X:=0; Y:=0; n:=0;
        repeat
          X:=X+pa^[ipt2].X; Y:=Y+pa^[ipt2].Y; inc(n);
          if ipt2>=cpt-1 then break;
          if not isbase^[ipt2+1] then break;
          inc(ipt2);
        until (pa^[ipt2].X-X1>bmi/2);
        newbaseln.X2 := X/n; newbaseln.Y2 := Y/n;
        newbaseln.X := (newbaseln.X1 + newbaseln.X2)/2;
       {DebLog('NewBaseLn: '+FloatToStr(newbaseln.X1)+','+FloatToStr(newbaseln.y1)
               +' - '+FloatToStr(newbaseln.X2)+','+FloatToStr(newbaseln.Y2));}

        if not PeakFindInPeaksInXRange(AcqData.ULM_ULP, dname, newbaseln.x, newbaseln.x1, newbaseln.x2, fp) then
        begin
          {v0.50}
          if AcqData.ActiveDataName <> AcqData.ULM.DefDataName then begin
            newbaseln.free;
            newbaseln := nil;
          end else
          {/v0.50
          if AcqData.DataIndex <> 0 then begin
            newbaseln.free;
            newbaseln := nil;
          end else
          }
          begin
            if PeakFindInPeaksInXRange(AcqData.ULM_ULP, '*', newbaseln.x, newbaseln.x1, newbaseln.x2, fp) then
            begin
              newbaseln.free;
              newbaseln := nil;
            end else begin
              if AcqData.ULM.NoUnknownPeaks then
              begin
                newbaseln.free;
                newbaseln := nil;
              end;
            end;
          end;
        end;
      end;
    end;
  finally
    if isbase <> nil then
      FreeMem(isbase,cpt*sizeof(boolean));
    if points <> nil then
      points.Free;
  end;
  Result := true;
end;

function TSpectrum.AutoDetectForPeaks: boolean;
var
  ibl, ipt, ipt2, ipt3, dpt, {cbl,} cpt: integer;
  baseln: TULBRObj; { in ULBRType.PAS }
  points: TMemoryStream;
  pa, tmppa: PExpPoints;{ulantype}
  slope: single;
  MinPeakWidth:single;
  MinPeakHeight:single;
  RejectLevel:single;
  {v0.25}
  noUnknownPeaks: boolean;
  cDataName: string;
  {/v0.25}

 procedure StoreNewPeak(PTX:TXValue);
 var
   pti,i:     integer;
   newpeak:   TULPRObj; { in ULPRType.PAS }
   MinX:      TXValue;
   PHHY,MinY: TYValue;
   PHHHit:    boolean;
   PHHLimX:   TXValue;
   {v0.25}
   fp: TULPRObj;
   {/v0.25}
 begin
   pti:=XYFindXIndex(tmppa, dpt, PTX);
   {Reject small peaks under MinPeakHeight}
   if abs(tmppa^[pti].Y) < MinPeakHeight then
     exit;
   {v0.25 reject peaks that are not in method (if requested)}
   if noUnknownPeaks then begin
     if not PeakFindPeakInMethodByX(PTX, fp) then
       exit; { peak not found in method, exit }

     (*v0.50*)
     if not AcqData.DataNamesEqual(fp.DataName, cDataName) then
       exit;
     (*/v0.50
     if fp.DataName <> '' then begin
       { peak is (possibly) on non default data line: }
       if fp.DataName = 'G' then begin
         { it is default in AAA }
         if (cDataName <> '') then begin
           if fp.DataName <> cDataName then
             exit;
         end;
       end else begin
         if cDataName <> fp.DataName then
           exit;
       end;
     end else begin
       { peak is on default data line (has no DataName): }
       if cDataName <> '' then begin
         { possibly nondefault data }
         if cDataName <> 'G' then begin { G is default in AAA }
           exit;
         end;
       end;
     end;
     *)
   end;
   {/v0.25}
   {Create new peak object}
   newpeak := TULPRObj(Peaks.Add(ULPRID));
   newpeak.X:= PTX;
   {v0.27}
   newpeak.DataName:=cDataName;
   {/v0.27}
   PHHY:=tmppa^[pti].Y/2;
   { Find Left Peak End }
   PHHLimX:=0;
   i := pti; PHHHit := False; MinY := tmppa^[i].Y; MinX := PTX;
   while i > 0 do
   begin
     dec(i);
     if not PHHHit then
     begin
       if tmppa^[i].Y<=PHHY then
       begin
         PHHLimX:=PTX-(PTX-tmppa^[i].X)*(2+1/4);
         PHHHit:=True;
       end
     end
     else if tmppa^[i].X<PHHLimX then break;
     if tmppa^[i].Y<=MinY then begin
       MinX:=tmppa^[i].X; MinY:=tmppa^[i].Y;
     end
     else if tmppa^[i].X<MinX-2*MinPeakWidth then break;
   end;
   newpeak.X1:=MinX;
   { Find Right Peak End }
   i:=pti; PHHHit:=False; MinY:=tmppa^[i].Y; MinX:=PTX;
   while i<dpt-1 do
   begin
     inc(i);
     if not PHHHit then
     begin
       if tmppa^[i].Y<=PHHY then
       begin
         PHHLimX:=PTX+(tmppa^[i].X-PTX)*(2+1/4);
         PHHHit:=True;
       end;
     end
     else if tmppa^[i].X>PHHLimX then break;
     if tmppa^[i].Y<=MinY then begin
       MinX:=tmppa^[i].X; MinY:=tmppa^[i].Y;
     end
     else if tmppa^[i].X>MinX+2*MinPeakWidth then break;
   end;
   newpeak.X2:=MinX;
 end;

 procedure ProcessOneReg; {imput is tmppa,dpt,MinPeakWidth}
 var
   i : integer;
   PTB,PTE: TXValue;    {Peak Top Begin/End}
   PTX: TXValue;        {Peak Top X Value}
   PTY: TYValue;        {Peak Top Y Value}
   AY:  TYValue;        {Actual Y}
   FindPT: Boolean;     {Look For Peak Top else Valey}
 begin
   i:=0;
   PTB:=tmppa^[i].X; PTE:=PTB;
   PTY:=0;
   FindPT:=True;
   repeat
     AY:=tmppa^[i].Y;
     if FindPT then
     begin
       if AY>PTY then begin PTB:=tmppa^[i].X; PTE:=PTB; PTY:=AY; end
       else if AY=PTY then begin PTE:=tmppa^[i].X; end
       else if tmppa^[i].X>=PTB+MinPeakWidth then
       begin
	 if PTY>=RejectLevel then
         begin
	   PTX:=(PTB+PTE)/2; {Retention Time of New Peak}
	   if (PTX>tmppa^[0].X+MinPeakWidth/3) and
              (PTX<tmppa^[dpt-1].X-MinPeakWidth/3) then
             StoreNewPeak(PTX);
	 end;
	 FindPT:=False;
       end;
     end else begin
       if AY<PTY then begin PTY:=AY; PTE:=tmppa^[i].X; end
       else if tmppa^[i].X>=PTE+MinPeakWidth then FindPT:=True;
     end;
     inc(i);
   until i>=dpt;
 end;

begin
  tmppa :=nil;
  points :=nil;
  cDataName := AcqData.ActiveULADRc.ULAD.DataName;
  {v0.50}
  if cDataName = AcqData.ULM.DefDataName then
  {/v0.50if AcqData.DataIndex = 0 then}
  begin
    noUnknownPeaks := AcqData.ULM.NoUnknownPeaks;
  end else begin
    noUnknownPeaks:= true;
  end;

  Peaks.DoChangeLock;
  try
    {v0.11}
    MathCoprMaskUnderflow; {No exception on rounding to zero}
    {/v0.11}
    points := TMemoryStream.Create;
    AcqData.ActiveData.Position := 0;
    points.CopyFrom(AcqData.ActiveData, AcqData.ActiveData.Size);
    cpt := AcqData.ActiveData.Size div sizeof(TExpPoint);
    pa := PExpPoints(points.Memory);
    MinPeakWidth := AcqData.ULM.MinPeakWidth;
    MinPeakHeight := AcqData.ULM.MinPeakHeight;
    {v0.25}{/v0.25 Peaks.Clear;}
    {Detect peaks above found base ranges}
    RejectLevel:=0;
    ibl:=0;
    while ibl < BaseLine.ChildCount do
    begin
      baseln := TULBRObj(BaseLine.Childs[ibl]);
      if (baseln.RecID = ULBRID) {v0.25} and
        {v0.50}
        AcqData.DataNamesEqual(baseln.DataName, AcqData.ActiveULADRc.ULAD.DataName)
        {/v0.50
        (baseln.DataName = AcqData.ActiveULADRc.ULAD.DataName)}
      then
      begin
        ipt := XYFindXIndex(pa, cpt, baseln.X1)+1;
        ipt2 := XYFindXIndex(pa, cpt, baseln.X2);
        dpt := ipt2 + 1 - ipt; {Count of points above baseline}
        {v0.59}
        if dpt > 0 then
        {/v0.59}
        begin
          GetMem(tmppa, dpt * sizeof(TExpPoint));
          Move(pa^[ipt],tmppa^,dpt*sizeof(TExpPoint));
          {Substract baseline from experiment points}
          slope := (baseln.Y2 - baseln.Y1)/(baseln.X2 - baseln.X1);
          for ipt3 := 0 to dpt - 1 do
          begin
            tmppa^[ipt3].Y := tmppa^[ipt3].Y -
               (tmppa^[ipt3].X - baseln.X1) * slope - baseln.Y1;
          end;
          XYFilterGaus(tmppa, dpt, MinPeakWidth/2); {Not needed for filtered data}
          ProcessOneReg;
          FreeMem(tmppa, dpt * sizeof(TExpPoint));
        end;
        tmppa:=nil;
      end;
      inc(ibl);
    end;
    {Sort results}
    Peaks.Sort;
    PeaksFindYAndIndexValues;
    PeaksAssignParamsFromMethod;
    RecalculatePeaks;
    Result := false;
  finally
    if tmppa <> nil then
      FreeMem(tmppa);
    points.Free;
    Peaks.DoChangeUnlock;
  end;
end;

procedure TSpectrum.PeakFindYAndIndexValues(p: TULPRObj{v0.26}; ReselectUsrData: boolean{/v0.26});
var
  sp, dp: TExpPoint;
  i: TPointCount;
  y: TYValue;
begin
  sp.X := p.X1;
  {v0.26}
  AcqData.ActiveDataName := p.DataName;
  {/v0.26}
  if AcqData.FindDataPoint(sp, dp, i) then begin
    p.X1 := dp.X;
    p.Y1 := dp.Y;
    p.Index1 := i;
    if FindBaseY(p.DataName, p.X1, y) then
      p.BaseY1 := y
    else
      p.BaseY1 := dp.Y;
  end;
  sp.X := p.X2;
  if AcqData.FindDataPoint(sp, dp, i) then begin
    p.X2 := dp.X;
    p.Y2 := dp.Y;
    p.Index2 := i;
    if FindBaseY(p.DataName, p.X2, y) then
      p.BaseY2 := y
    else
      p.BaseY2 := dp.Y;
  end;
  {v0.26}
  if ReselectUsrData then
    AcqData.UsrDataIndexReselect;
  {/v0.26}
end;

procedure TSpectrum.PeaksFindYAndIndexValues;
var
  i: integer;
  p: TULPRObj;
begin
  Peaks.DoChangeLock;
  try
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID = ULPRID then
        PeakFindYAndIndexValues(p{v0.26}, false{/v0.26});
    end;
  finally
    Peaks.DoChangeUnlock;
    {v0.26}
    AcqData.UsrDataIndexReselect;
    {/v0.26}
  end;
end;

function TSpectrum.AutoDetect: boolean;
{v0.25}
var i: integer;
{/v0.25}
{v0.50}
var
  fnd: boolean;
  gfnd: boolean;
  efnd: boolean;
  u: TULADObj;
  ai: integer;
{/v0.50}
begin
  Result := false;
  {v0.18}
  {$IFNDEF CONSOLE}
  InfoFormShow(GetTxt({#}'Autodetecting peaks...'));
  {$ENDIF}
  try
    {v0.14}
    if AcqData.ULM.MinPeakWidth = 0 then
      AcqData.ULM.MinPeakWidth := 12;
    if AcqData.ULM.MinPeakHeight = 0 then
      AcqData.ULM.MinPeakHeight := 0.01;
    {/v0.14}
    {v0.25}
    BaseLine.Clear;
    Peaks.Clear;

    {!!!
    if AcqData.DataCount > 1 then begin
      AcqData.ULAD[0].ULAD.Color := clGreen;
      AcqData.ULAD[1].ULAD.Color := clBlue;
    end;}

    {v0.50}
    if AcqData.ULAD.Count = 0 then
      exit;
    fnd := false;
    gfnd := false;
    efnd := false;
    for i := 0 to AcqData.ULAD.Count - 1 do begin
      u := AcqData.ULAD[i].ULAD;
      if u.DataName = 'G' then
        gfnd := true;
      if u.DataName = '' then
        efnd := true;
      if AcqData.ULM.DefDataName = u.DataName then begin
        fnd := true;
        break;
      end;
    end;
    if not fnd then begin
      if efnd then begin
        AcqData.ULM.DefDataName := '';
      end else begin
        if gfnd then begin
          AcqData.ULM.DefDataName := 'G';
        end else begin
          AcqData.ULM.DefDataName := AcqData.ULAD[0].ULAD.DataName;
        end;
      end;
    end;

    AcqData.ActiveDataName := AcqData.ULM.DefDataName;
    ai := AcqData.DataIndex;
    AutoDetectForBase;
    BaseLineUpdateFromMethod;
    AutoDetectForPeaks;
    {/v0.50}

    for i := 0 to AcqData.ULAD.Count - 1 do
    {/v0.25}
    begin
      {v0.50}
      if i = ai then
        continue;
      {/v0.50}
      AcqData.DataIndex := i;
      if i = 1 then begin
        {!!!}{v0.50}{/v0.50 if AcqData.ActiveULADRc.ULAD.DataName = '' then
          AcqData.ActiveULADRc.ULAD.DataName := 'B';}
      end;
      AutoDetectForBase;
      BaseLineUpdateFromMethod;
      AutoDetectForPeaks;
    end;
    {v0.28}
    AcqData.ULA.AutodetectLater := false;
    {/0.28}
    {v0.33}
    Peaks.Sort;
    BaseLine.Sort;
    {/v0.33}
    {v0.31}
    AcqData.UsrDataIndexReselect;
    {/v0.31}
    Result := true;
  finally
    {$IFNDEF CONSOLE}
    InfoFormHide;
    {$ENDIF}
  end;
end;


(*
function TSpectrum.AutoDetect: boolean;
begin  Result := false;
end;

var
  X: TXValue;
  Y: TYValue;
  ipk, ipt, cpk, cpt: integer;
  peak, newpeak: TULPRObj;{ viz ULPRType.PAS }

  points: TMemoryStream;
  pa: PExpPoints;{ulantype}
begin
  points := TMemoryStream.Create;
  AcqData.ULAD.Data.Position := 0;
  points.CopyFrom(AcqData.ULAD.Data, AcqData.ULAD.Data.Size);
  cpt := AcqData.ULAD.Data.Size div sizeof(TExpPoint);
  ipt := cpt - 1;
  pa := PExpPoints(points.Memory);
  x := pa^[ipt].X;

  newpeak := TULPRObj(Peaks.Add(ULPRID));
  cpk := Peaks.ChildCount;
  ipk := cpk - 1;
  peak := TULPRObj(Peaks.Childs[ipk]);

  Peaks.Sort;
  points.Free;
end;
*)

function TSpectrum.AutoDetectOld: boolean;
var
  APoint: TIndexPoint;
  adp: boolean;
label ex;
begin
  adp := AutoDetectPeaks;
  AutoDetectPeaks := true;
  AutoDetectOld := false;
  if AcqData.GetPointCount < 3 then
    goto ex;
  ClearProcPeak;
  AcqData.SeekPoint(0);
  with ProcPeak do begin
    if not AcqData.ReadIndexPoint(Point_2) then
      goto ex;
    if not AcqData.ReadIndexPoint(Point_1) then
      goto ex;
    if not AcqData.ReadIndexPoint(APoint) then
      goto ex;
    AcqData.SpectrumOpt.BaseLine := Point_2.Point.Y;
    LocMin_1 := Point_2;
    LocMax_1 := Point_2;
    LocMin_2 := Point_2;
    LocMax_2 := Point_2;
    CurMin := Point_2;
    CurMax := Point_2;
    Min_1 := Point_2;
    Max_1 := Point_2;
    Min_2 := Point_2;
    Max_2 := Point_2;
    while AcqData.ReadIndexPoint(APoint) do
      DoPoint(APoint);
    AutoDetectOld := true;
  end;{with ProcPeak}
  PeaksCalculateRatios;
ex:
  AutoDetectPeaks := adp;
end;

procedure TSpectrum.PeaksCalculateRatios;
begin
  if Peaks <> nil then
    CalculatePeaksRatios(Peaks);
end;

function TSpectrum.IsScreenPointInPeakEdge(X: TScreenX; Y: TScreenY;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer): boolean;
var
  sp1, sp2, spp: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
  {v0.09}
  yoffs: TScreenY;
  {/v0.09}
begin
  Result := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.BaseY1;
    ep2.X := p.X2;
    ep2.Y := p.BaseY2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons1 = 0 then begin
      {v0.09}
      ep1.Y := p.Y1;
      AcqData.ExpToScreen(ep1, AScreenDisp, spp);
      if spp.Y <= sp1.Y then
        yoffs := PointsPeakStickLen
      else
        yoffs := -PointsPeakStickLen;
      {/v0.09}
      if (X >= sp1.X) and (X <= sp1.X + PointsPeakArrowLen) and
         (Y >= sp1.Y + yoffs{PointsPeakStickLen} - PointsPeakArrowLen div 4) and
         (Y <= sp1.Y + yoffs{PointsPeakStickLen} + PointsPeakArrowLen div 4) then
      begin
        Result := true;
        PeakIndex := i;
        exit;
      end;
    end;
    if ons2 = 0 then begin
      {v0.09}
      ep2.Y := p.Y2;
      AcqData.ExpToScreen(ep2, AScreenDisp, spp);
      if spp.Y <= sp2.Y then
        yoffs := PointsPeakStickLen
      else
        yoffs := -PointsPeakStickLen;
      {/v0.09}
      if (X >= sp2.X - PointsPeakArrowLen) and (X <= sp2.X) and
         (Y >= sp2.Y + yoffs{PointsPeakStickLen} - PointsPeakArrowLen div 4) and
         (Y <= sp2.Y + yoffs{PointsPeakStickLen} + PointsPeakArrowLen div 4)
      then begin
        Result := true;
        PeakIndex := i;
        exit;
      end;
    end;
  end;
end;

function TSpectrum.IsScreenXInPeak(X: TScreenX; const AScreenDisp:TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
{  ons1, ons2: integer;}
begin
  Result := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    {ons1 := }AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    {ons2 := }AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if (X >= sp1.X) and (X <= sp2.X) then begin
      Result := true;
      PeakIndex := i;
      exit;
    end;
    {
    if ons1 = 0 then begin
      if (X >= sp1.X) then begin
        if (ons2 <> 0) then begin
          if X <= (AScreenDisp.Right) then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end else begin
      if (ons1 and opLeft) <> 0 then begin
        if (ons2 = 0) then begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if (ons2 and opRight) <> 0 then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end;
    }
  end;
end;

function TSpectrum.IsScreenPointInBaseLineEdge(var X: TScreenX; Y: TScreenY;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer; var LeftEdge:boolean): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULBRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
begin
  Result := false;
  PeakIndex := -1;
  if (BaseLine = nil) then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.RecID <> ULBRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons1 = 0 then begin
      if (X >= sp1.X - PointsBaseLineCircleRadius) and (X <= sp1.X + PointsBaseLineCircleRadius)
        and (Y >= sp1.Y - PointsBaseLineCircleRadius) and (Y <= sp1.Y + PointsBaseLineCircleRadius)
      then begin
        X := sp1.X;
        Result := true;
        PeakIndex := i;
        {v0.25}
        AcqData.UsrActiveDataName := p.DataName;
        {/v0.25}
        LeftEdge := true;
        exit;
      end;
    end;
    if ons2 = 0 then begin
      if (X >= sp2.X - PointsBaseLineCircleRadius) and (X <= sp2.X + PointsBaseLineCircleRadius)
        and (Y >= sp2.Y - PointsBaseLineCircleRadius) and (Y <= sp2.Y + PointsBaseLineCircleRadius)
      then begin
        Result := true;
        X := sp2.X;
        PeakIndex := i;
        {v0.25}
        AcqData.UsrActiveDataName := p.DataName;
        {/v0.25}
        LeftEdge := false;
        exit;
      end;
    end;
  end;
end;

function TSpectrum.IsScreenXInBaseLine(X: TScreenX; const AScreenDisp:TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULBRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
{  ons1, ons2: integer;}
begin
  Result := false;
  PeakIndex := -1;
  if (BaseLine = nil) then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.RecID <> ULBRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    {ons1 := }AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    {ons2 := }AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if (X >= sp1.X) and (X <= sp2.X) then begin
      Result := true;
      PeakIndex := i;
      exit;
    end;
    {
    if ons1 = 0 then begin
      if (X >= sp1.X) then begin
        if (ons2 <> 0) then begin
          if X <= (AScreenDisp.Right) then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end else begin
      if (ons1 and opLeft) <> 0 then begin
        if (ons2 = 0) then begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if (ons2 and opRight) <> 0 then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end;
    }
  end;
end;

function TSpectrum.IsScreenPeakStart(X: TScreenX; const AScreenDisp: TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
begin
  IsScreenPeakStart := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons1 = 0 then begin
      if (X >= sp1.X) then begin
        if (ons2 <> 0) then begin
          if X < AScreenDisp.Right{(AScreenDisp.Left + AScreenDisp.Width + sp1.X) div 2} then begin
            IsScreenPeakStart := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if X < (sp2.X + sp1.X) div 2 then begin
            IsScreenPeakStart := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end;
  end;
end;
{
var
  sc: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  pt: TExpPoint;
begin
  IsScreenPeakStart := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
   exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    pt.X := p.X1;
    pt.Y := p.Y1;
    if AcqData.ExpToScreen(pt, AScreenDisp, sc) = 0 then begin
      if abs(sc.X - X) < 2 then begin
        IsScreenPeakStart := true;
        PeakIndex := i;
        exit;
      end;
    end;
  end;
end;
}

{v0.09}
function TSpectrum.ULBRToScreenPeak(BL:TULBRObj; const AScreenDisp: TScreenDisp;
  var SP:TScreenPeak): integer;
var
  ep: TExpPoint;
  ons: integer;
begin
{  Result := -1;
  if BL = nil then
    exit;}
  ep.X := bl.X1;
  ep.Y := bl.Y1;
  ons := AcqData.ExpToScreen(ep, AScreenDisp, sp.p1);
  if ((ons and opLeft) <> 0) then
    sp.p1.X := AScreenDisp.Left;
  Result := ons;

  ep.X := bl.X2;
  ep.Y := bl.Y2;
  ons := AcqData.ExpToScreen(ep, AScreenDisp, sp.p2);
  if ((ons and opRight) <> 0) then
    sp.P2.X := AScreenDisp.Right;
  Result := Result or ons;
end;
{/v0.09}

procedure TSpectrum.GetScreenBaseLineRec(X: TScreenX; const AScreenDisp: TScreenDisp;
  var CurIndex: integer;
  var PrevBL: TScreenPeak; var CurBL: TScreenPeak; var NextBL: TScreenPeak);
var
  sp: TScreenPeak;
  {v0.09}{/v0.09
  ep1, ep, ep2: TExpPoint;
  ons1, ons2: integer;{ is on screen? }
  bl: TULBRObj;
  cnt, i: integer;
  {v0.09}
  xp: TScreenPoint;
  xep: TExpPoint; { exp point at X pos }
  mindifx: TXValue;
  difx: TXValue;{ulantype}
  pbl, cbl, nbl: TULBRObj;
  {/0.09}
begin
  FillChar(PrevBL, sizeof(PrevBL), $FF);
  FillChar(CurBL, sizeof(CurBL), $FF);
  FillChar(NextBL, sizeof(NextBL), $FF);
  CurIndex := -1;
  mindifx := MaxXValue;

  if AcqData.ExpToScreen(AcqData.SpectrumOpt.Extreme.Max, AScreenDisp, sp.p1) = 0 then
  begin
    { if last data point is on the screen, fake it as the next peak start, so
      that moving peaks does not go out of the chromatogram }
    NextBL := sp;
  end;
  if BaseLine = nil then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;

  {v0.09}
  pbl := nil;
  nbl := nil;
  cbl := nil;

  xp.X := X;
  xp.Y := 0;
  if not AcqData.ScreenToExp(xp, AScreenDisp, xep) then
    exit;
  for i := 0 to cnt - 1 do begin
    bl := TULBRObj(BaseLine.Childs[i]);
    if bl.RecID <> ULBRID then
      continue;
    if (bl.X2 < xep.X) then begin
      pbl := bl;
    end else if (bl.X1 > {v0.11} xep.X{/v0.11 X}) then begin
      nbl := bl;
      break;
    end else begin
      difx := xep.X - {v0.11} bl.X1{/v0.11 X};
      if difx < mindifx then begin
        if cbl <> nil then
          pbl := cbl;
        cbl := bl;
        CurIndex := i;
        mindifx := difx;
      end;
    end;
  end;

  if pbl <> nil then ULBRToScreenPeak(pbl, AScreenDisp, PrevBL);
  if cbl <> nil then ULBRToScreenPeak(cbl, AScreenDisp, CurBL);
  if nbl <> nil then ULBRToScreenPeak(nbl, AScreenDisp, NextBL);
  {/0.09
  for i := 0 to cnt - 1 do begin
    bl := TULBRObj(BaseLine.Childs[i]);
    if bl.RecID <> ULBRID then
      continue;
    ep1.X := bl.X1;
    ep1.Y := bl.Y1;
    ep.X := bl.X;
    ep.Y := bl.Y;
    ep2.X := bl.X2;
    ep2.Y := bl.Y2;
    FillChar(sp, sizeof(sp), $FF);
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp.p1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp.p2);
    if ons2 = 0 then begin
      if (X > sp.p2.X) then begin
        PrevBL := sp;
      end else begin
        if (ons1 <> 0) or (X >= sp.p1.x) then begin
          if CurIndex < 0 then begin
            CurBL := sp;
            CurIndex := i;
          end else begin
            NextBL := sp;
            break;
          end;
        end;
      end;
    end;
    if ons1 = 0 then begin
      if (X < sp.p1.X) then begin
        NextBL := sp;
        break;
      end else begin
        if (ons2 = 0) then begin
          if X <= sp.P2.X then begin
            if CurIndex < 0 then begin
              CurBL := sp;
              CurIndex := i;
            end else begin
              if (sp.X1 <> CurBL.X1) or (sp.X2 <> CurBL.X2) then begin
                NextBL := sp;
                break;
              end;
            end;
          end;
        end else begin
          CurBL := sp;
          CurIndex := i;
        end;
      end;
    end;

    if ((ons1 and opLeft) <> 0) and ((ons2 and opRight) <> 0) then begin
      CurBL.P1.X := AScreenDisp.Left;
      CurBL.P2.X := AScreenDisp.Right;
      CurIndex := i;
      break;
    end;

  end;
  }
end;

{v0.09}
procedure TSpectrum.GetScreenBaseLineNeighbours(Index:integer; const AScreenDisp: TScreenDisp;
  var PrevBL: TScreenPeak; var CurBL: TScreenPeak; var NextBL: TScreenPeak);
  { Returns inc CurBL screen coordinates of Index baseline section, plus
    in PrevBL and NextBL coordinates of sections on the left and on the right }
var
  sp: TScreenPeak;
  bl: TULBRObj;
  cnt, i: integer;
  pbl, cbl, nbl: TULBRObj;
begin
  FillChar(PrevBL, sizeof(PrevBL), $FF);
  FillChar(CurBL, sizeof(CurBL), $FF);
  FillChar(NextBL, sizeof(NextBL), $FF);

  if AcqData.ExpToScreen(AcqData.SpectrumOpt.Extreme.Max, AScreenDisp, sp.p1) = 0 then
  begin
    { if last data point is on the screen, fake it as the next peak start, so
      that moving peaks does not go out of the chromatogram }
    NextBL := sp;
  end;
  if BaseLine = nil then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;
  pbl := nil;
  nbl := nil;
  cbl := nil;

  for i := 0 to cnt - 1 do begin
    bl := TULBRObj(BaseLine.Childs[i]);
    if bl.RecID <> ULBRID then
      continue;
    if i < Index then
      pbl := bl
    else if i = Index then
      cbl := bl
    else begin
      nbl := bl;
      break;
    end;
  end;
  if pbl <> nil then ULBRToScreenPeak(pbl, AScreenDisp, PrevBL);
  if cbl <> nil then ULBRToScreenPeak(cbl, AScreenDisp, CurBL);
  if nbl <> nil then ULBRToScreenPeak(nbl, AScreenDisp, NextBL);
end;
{/v0.09}

procedure TSpectrum.GetScreenPeaks(X: TScreenX; const AScreenDisp: TScreenDisp;
  var PrevPeak: TScreenPeak; var CurPeak: TScreenPeak; var NextPeak: TScreenPeak);
var
  sp: TScreenPeak;
  ep1, ep, ep2: TExpPoint;
  ons1, {ons, }ons2: integer;{ is on screen? }
  p: TULPRObj;
  cnt, i: integer;
begin
  FillChar(PrevPeak, sizeof(PrevPeak), $FF);
  FillChar(CurPeak, sizeof(CurPeak), $FF);
  FillChar(NextPeak, sizeof(NextPeak), $FF);

  if AcqData.ExpToScreen(AcqData.SpectrumOpt.Extreme.Max, AScreenDisp, sp.p1) = 0 then
  begin
    { if last data point is on the screen, fake it as the next peak start, so
      that moving peaks does not go out of the chromatogram }
    NextPeak := sp;
  end;
  if Peaks = nil then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;

  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep.X := p.X;
    ep.Y := p.Height;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    FillChar(sp, sizeof(sp), $FF);
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp.p1);
    {ons := AcqData.ExpToScreen(ep, AScreenDisp, sp.p);}
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp.p2);
    if ons2 = 0 then begin
      if (X > sp.p2.X) then begin
        PrevPeak := sp;
      end else begin
        if (ons1 <> 0) or (X >= sp.p1.x) then begin
          CurPeak := sp;
          {v0.25}
          AcqData.UsrActiveDataName := p.DataName;
          {/v0.25}
        end;
      end;
    end;
    if ons1 = 0 then begin

      if (X < sp.p1.X) then begin
        NextPeak := sp;
        break;
      end else begin
        if (ons2 = 0) then begin
          if X <= sp.P2.X then begin
            CurPeak := sp;
            {v0.25}
            AcqData.UsrActiveDataName := p.DataName;
            {/v0.25}
          end;
        end else begin
          CurPeak := sp;
          {v0.25}
          AcqData.UsrActiveDataName := p.DataName;
          {/v0.25}
        end;
      end;
    end;

    if ((ons1 and opLeft) <> 0) and ((ons2 and opRight) <> 0) then begin
      CurPeak.P1.X := AScreenDisp.Left;
      CurPeak.P2.X := AScreenDisp.Right;{AScreenDisp.Left + AScreenDisp.Width;}
      break;
    end;

  end;
end;


function TSpectrum.IsScreenPeakStop(X: TScreenX; const AScreenDisp: TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
begin
  IsScreenPeakStop := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons2 = 0 then begin
      if (X <= sp2.X) then begin
        if (ons1 <> 0) or (X >= ((sp2.X + sp1.X) div 2)) then begin
          IsScreenPeakStop := true;
          PeakIndex := i;
          exit;
        end;
      end;
    end;
  end;
end;

function TSpectrum.MoveScreenPeak(OldStartX, NewStartX: TScreenX;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  ap: TExpPoint;
  p: TULPRObj;
  d: TXValue;
  op1,op2,
  np1,np2: TExpPoint;
  index1,index2: TPointCount;
begin
  MoveScreenPeak := false;
  if not IsScreenPeakStart(OldStartX, AScreenDisp, pi) then
    exit;
  sp.X := NewStartX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  p := TULPRObj(Peaks.Childs[pi]);
  d := ap.X - p.X1;
  op1.X := p.X1 + d;;
  op1.Y := p.Y1;
  op2.X := p.X2 + d;
  op2.Y := p.Y2;
  if not AcqData.FindDataPoint(op1, np1, index1) then
    exit;
  if not AcqData.FindDataPoint(op2, np2, index2) then
    exit;
  p.X1 := np1.X;
  p.Y1 := np1.Y;
  p.Index1 := index1;
  p.X2 := np2.X;
  p.Y2 := np2.Y;
  p.Index2 := index2;
  DoPeakLimitsChanged(p);
  {
  p^.P1.Point := np1;
  p^.P1.Index := index1;
  p^.P2.Point := np2;
  p^.P2.Index := index2;
  }
  MoveScreenPeak := true;
end;

function TSpectrum.SizeScreenPeak(OldStopX, NewStopX: TScreenX;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer; Right:boolean): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  ap: TExpPoint;
  p: TULPRObj;
  np: TExpPoint;
  index: TPointCount;
begin
  SizeScreenPeak := false;
  if not IsScreenXInPeak(OldStopX, AScreenDisp, pi) then
    exit;
  {
  if not IsScreenPeakStop(OldStopX, AScreenDisp, pi) then
    exit;
  }
  sp.X := NewStopX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  if not AcqData.FindDataPoint(ap, np, index) then
    exit;
  p := TULPRObj(Peaks.Childs[pi]);{PPeak(ListAt(Peaks,pi));}
  p.DoChangeLock;
  try
    if right then begin
      if (p.Index1 >= index) or (p.X1 >= np.X) then begin
        {if p.CanDestroy then
          p.Free;}
        exit;
      end;
      p.X2 := np.X;
      p.Y2 := np.Y;
      p.Index2 := index;
    end else begin
      if (p.Index2 <= index) or (p.X2 <= np.X) then begin
        {if p.CanDestroy then
          p.Free;}
        exit;
      end;
      p.X1 := np.X;
      p.Y1 := np.Y;
      p.Index1 := index;
    end;
  {  p^.P2.Point := np;
    p^.P2.Index := index;}
    DoPeakLimitsChanged(p);
    SizeScreenPeak := true;
  finally
    p.DoChangeUnlock;
  end;
end;

procedure TSpectrum.ToggleSelectScreenPeak(X: TScreenX;
  const AScreenDisp: TScreenDisp; Shift: TShiftState);
var
  index: integer;
{  p, o: TULPRObj;
  i, j, index, cc: integer;
  otherSelIndex: integer;}
begin
  if IsScreenXInPeak(X, AScreenDisp, index) then begin
    Peaks.ToggleSelectChild(index, Shift);
(*
    p := TULPRObj(Peaks.Childs[index]);
    cc := Peaks.ChildCount;
    if (ssShift in Shift) then begin

      otherSelIndex := -1;
      for i := 0 to cc - 1 do begin
        if i <> index then begin
          o := TULPRObj(Peaks.Childs[i]);
          if o.IsFlagSet(rfSelected) then begin
            otherSelIndex := i;
            break;
          end;
        end;
      end;
      if otherSelIndex <> -1 then begin
        if otherSelIndex < index then begin
          i := otherSelIndex;
        end else begin
          i := index;
          index := otherSelIndex;
        end;
        for j := 0 to i - 1 do begin
          TULPRObj(Peaks.Childs[j]).SetFlag(rfSelected, false);
        end;
        for j := i to index do begin
          TULPRObj(Peaks.Childs[j]).SetFlag(rfSelected, true);
        end;
        for j := index + 1 to cc - 1 do begin
          TULPRObj(Peaks.Childs[j]).SetFlag(rfSelected, false);
        end;

      end else begin
        p.SetFlag(rfSelected, not p.IsFlagSet(rfSelected));
      end;
    end else if (ssCtrl in Shift) then begin

      p.SetFlag(rfSelected, not p.IsFlagSet(rfSelected));

    end else begin

      for i := 0 to Peaks.ChildCount - 1 do begin
        o := TULPRObj(Peaks.Childs[i]);
        if i <> index then
          o.SetFlag(rfSelected, false);
      end;
      p.SetFlag(rfSelected, not p.IsFlagSet(rfSelected));

    end;
*)
  end;
end;

procedure TSpectrum.ToggleSelectScreenBaseLine(X: TScreenX; Y: TScreenY;
  const AScreenDisp: TScreenDisp; Shift: TShiftState);
var
  index: integer;
  leftedge:boolean;
  {
  p, o: TULPRObj;
  i, j, index, cc: integer;
  otherSelIndex: integer;}
begin
  if IsScreenPointInBaseLineEdge(X, Y, AScreenDisp, index, leftedge) then begin
    BaseLine.ToggleSelectChild(index, Shift);
  end;
end;

procedure TSpectrum.DoPeakLimitsChanged(p: TULPRObj);
{var
  y: TYValue;}
begin
  PeakFindYAndIndexValues(p{v0.26}, true{/v0.26});
  {v0.12}
  {/v0.12
  PeakAssignParamsFromMethod(p);}
{  if FindBaseY(p.X1, y) then
    p.BaseY1 := y;
  if FindBaseY(p.X2, y) then
    p.BaseY2 := y;
}
end;

function TSpectrum.FindBaseY(const ADataName: string; X: TXValue; var Y: TYValue):boolean;
var
  b: TULBRObj;
  i, cc: integer;
  maxy: TYValue;
  cy: TYValue;
  fnd:boolean;
begin
  FindBaseY := false;
  maxy := MinYValue;
  fnd := false;
  cc := AcqData.ULB.ChildCount;
  if cc > 0 then begin
    for i := 0 to cc - 1 do begin
      b := TULBRObj(AcqData.ULB.Childs[i]);
      if b.RecID <> ULBRID then
        continue;
      {v0.50}
      if not AcqData.DataNamesEqual(ADataName, b.DataName) then
        continue;
      {/v0.50
      if (ADataName <> '*') and (ADataName <> b.DataName) then
        continue;}
      if (b.X1 <= X) and (X <= b.X2) then begin
        cy := CountYOnLine(X, b.X1, b.Y1, b.X2, b.Y2);
        if (cy > maxy) then begin
          maxy := cy;
          fnd := true;
        end;
      end;
    end;
    if fnd then begin
      y := maxy;
      FindBaseY := true;
    end;
  end;
end;

procedure TSpectrum.CopySelectedPeaksToMethod;
var
  p, mp: TULPRObj;
  i: integer;
begin
  for i := 0 to AcqData.ULP.ChildCount - 1 do begin
    p := TULPRObj(AcqData.ULP.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    if p.IsFlagSet(rfSelected) then begin
      {v0.10}
      if PeakFindPeakInMethod(p, mp) then begin
        mp.Free;
      end;
      {/v0.10}
      mp := TULPRObj(AcqData.ULM_ULP.Add(ULPRID));
      mp.Assign(p);
    end;
  end;
end;

procedure TSpectrum.CopySelectedBaseLineToMethod;
var
  b, mb: TULBRObj;
  i: integer;
begin
  for i := 0 to AcqData.ULB.ChildCount - 1 do begin
    b := TULBRObj(AcqData.ULB.Childs[i]);
    if b.RecID <> ULBRID then
      continue;
    if b.IsFlagSet(rfSelected) then begin
      mb := TULBRObj(AcqData.ULM_ULB.Add(ULBRID));
      mb.Assign(b);
    end;
  end;
end;

procedure TSpectrum.PeaksAssignParamsFromMethod;
var
  i: integer;
begin
  AcqData.ULP.DoChangeLock;
  try
    for i := 0 to AcqData.ULP.ChildCount - 1 do begin
      PeakAssignParamsFromMethod(TULPRObj(AcqData.ULP.Childs[i]));
    end;
  finally
    AcqData.ULP.DoChangeUnlock;
  end;
end;

{v0.11}
procedure TSpectrum.PeakAssignPeakCharacterization(Dest, Src: TULPRObj);
begin
  Dest.PeakName := Src.PeakName;
  Dest.GroupName := Src.GroupName;
  Dest.Response := Src.Response;
  Dest.InternalStandard := Src.InternalStandard;
  Dest.Window := Src.Window;
  {v0.12}
  Dest.UsrPeakCoef := Src.UsrPeakCoef;
  Dest.Amount := Src.Amount;
  {/v0.12}
  {v0.25}
  Dest.DataName := Src.DataName;
  {/v0.25}
end;
{/v0.11}

procedure TSpectrum.PeakAssignParamsFromMethod(p: TULPRObj);
var mp: TULPRObj;
begin
  if PeakFindPeakInMethod(p, mp) then begin
    {v0.11}
    PeakAssignPeakCharacterization(p, mp);
    {/v0.11
    p.PeakName := mp.PeakName;
    p.GroupName := mp.GroupName;}
  end;
end;

{v0.25}
function TSpectrum.PeakFindInPeaksByX(pl: TULPObj; x: TXValue; var fp: TULPRObj): boolean;
var
  i: integer;
  minmp, curmp: TULPRObj;
  mindif, curdif: TXValue;
  w: TXValue;
begin
  Result := false;
  fp := nil;
  minmp := nil;
  mindif := MaxXValue;

  for i := 0 to pl.ChildCount - 1 do begin
    curmp := TULPRObj(pl.Childs[i]);
    if curmp.RecID <> ULPRID then
      continue;
    curdif := abs(X - curmp.X);
    w := curmp.Window;
    if w = 0 then begin
      w := AcqData.ULM.MinPeakWidth;
    end;
    if curdif <= (w / 2) then begin
      if curdif < mindif then begin
        mindif := curdif;
        minmp := curmp;
      end;
    end;
  end;
  if minmp <> nil then begin
    Result := true;
    fp := minmp;
  end;
end;

function TSpectrum.PeakFindInPeaksInXRange(pl: TULPObj; const ADataName: string; x, xmin, xmax: TXValue; var fp: TULPRObj): boolean;
var
  i: integer;
  minmp, curmp: TULPRObj;
  mindif, curdif: TXValue;
{  w: TXValue;}
begin
  Result := false;
  fp := nil;
  minmp := nil;
  mindif := MaxXValue;

  for i := 0 to pl.ChildCount - 1 do begin
    curmp := TULPRObj(pl.Childs[i]);
    {v0.50}
    if not AcqData.DataNamesEqual(ADataName, curmp.DataName) then
      continue;
    {/v0.50
    if (ADataName <> '*') then begin
      if curmp.DataName <> ADataName then
        continue;
    end;}
    if curmp.RecID <> ULPRID then
      continue;
    if curmp.x > xmax then
      continue;
    if curmp.x < xmin then
      continue;
    curdif := abs(X - curmp.X);
    if curdif < mindif then begin
      mindif := curdif;
      minmp := curmp;
    end;
  end;
  if minmp <> nil then begin
    Result := true;
    fp := minmp;
  end;
end;

{/v0.25}

function TSpectrum.PeakFindInPeaks(pl: TULPObj; p: TULPRObj; var fp: TULPRObj): boolean;
  { find in pl peaks the peak fp, that has p.X in fp's window interval }
var
  i: integer;
  minmp, curmp: TULPRObj;
  mindif, curdif: TXValue;
  {v0.11}
  w: TXValue;
  {/v0.11}
begin
  Result := false;
  fp := nil;
  minmp := nil;
  mindif := MaxXValue;

  for i := 0 to {v0.11}pl{/v0.11 AcqData.ULM_ULP}.ChildCount - 1 do begin
    curmp := TULPRObj({v0.11}pl{/v0.11 AcqData.ULM_ULP}.Childs[i]);
    if curmp.RecID <> ULPRID then
      continue;
    curdif := abs(p.X - curmp.X);
    {v0.11}
    w := curmp.Window;
    if w = 0 then begin
      w := AcqData.ULM.MinPeakWidth;
    end;
    {/v0.11}
    if curdif <= ({v0.11}w{/v0.11 curmp.Window} / 2) then begin
      if curdif < mindif then begin
        mindif := curdif;
        minmp := curmp;
      end;
    end;
  end;
  if minmp <> nil then begin
    Result := true;
    fp := minmp;
  end;
end;

{v0.12}
function TSpectrum.FindPeakByName(pl: TULPObj; const AName: TPeakName; var fp: TULPRObj): boolean;
 { find in pl peaks the peak fp, that has p.Name = AName }
var
  i: integer;
  curmp: TULPRObj;
begin
  Result := false;
  fp := nil;
  for i := 0 to pl.ChildCount - 1 do begin
    curmp := TULPRObj(pl.Childs[i]);
    if curmp.RecID <> ULPRID then
      continue;
    if AName = curmp.PeakName then
    begin
      Result := true;
      fp:=curmp;
      break;
    end;
  end;
end;
{/v0.12}

{v0.25}
function TSpectrum.PeakFindPeakInMethodByX(x: TXValue; var mp: TULPRObj):boolean;
begin
  Result := PeakFindInPeaksByX(AcqData.ULM_ULP, x, mp);
end;
{/v0.25}
{v0.11}
function TSpectrum.PeakFindPeakInMethod(p: TULPRObj; var mp: TULPRObj):boolean;
begin
  Result := PeakFindInPeaks(AcqData.ULM_ULP, p, mp);
end;
{/v0.11}

{v0.25}
procedure TSpectrum.BaseLineRemoveDuplicates;
var
  b, b1, b2: TULBRObj;
  i: integer;
begin
  b1 := nil;
  i := 0;
  while i < BaseLine.ChildCount do begin
    b := TULBRObj(BaseLine.Childs[i]);
    if b.RecID <> ULBRID then begin
      inc(i);
      continue;
    end;
    if b1 = nil then begin
      b1 := b
    end else begin
      b2 := b;
      if b1.X2 > b2.X1 then begin
        b2.Free;
        continue;
      end else
        b1 := b2;
    end;
    inc(i);
  end;
end;
{/v0.25}

procedure TSpectrum.BaseLineUpdateFromMethod;
var
  i, mi: integer;
  b, mb, nb: TULBRObj;
  brModified, bModified: boolean;

begin
  i := 0;
  bModified := false;
  BaseLine.DoChangeLock;
  try
    while i < BaseLine.ChildCount do begin
      b := TULBRObj(BaseLine.Childs[i]);
      if b.RecID <> ULBRID then begin
        inc(i);
        continue;
      end;

      brModified := false;
      for mi := 0 to AcqData.ULM_ULB.ChildCount - 1 do begin
        mb := TULBRObj(AcqData.ULM_ULB.Childs[mi]);
        if mb.RecID <> ULBRID then
          continue;
        if (b.X2 > mb.X1) and (b.X1 < mb.X2) then begin
          { some overlap of baseline record and method baseline record detected }

          brModified := true;
          bModified := true;
          if b.X1 < mb.X1 then begin
            if b.X2 <= mb.X2 then begin
              b.X2 := mb.X1;
            end else begin
              nb := TULBRObj(BaseLine.Add(ULBRID));
              nb.X1 := mb.X2;
              nb.X2 := b.X2;
              b.X2 := mb.X1;
            end;
          end else begin
            if b.X2 <= mb.X2 then begin
              if b.CanDestroy then begin
                b.Free;
                b := nil;
                break;
              end;
            end else begin
              b.X1 := mb.X2;
            end;
          end;
        end;
      end;
      if (b <> nil) then begin
        if brModified then begin
          DoBaseLineLimitsChanged(b);
          {UpdateBaseLineRecY(b);}
        end;
        inc(i);
      end;
    end;{/while}
    if bModified then
      BaseLine.Sort;
  finally
    BaseLine.DoChangeUnlock;
  end;
end;

destructor TSpectrum.Destroy;
begin
  {PeaksClear;}
  if AcqData <> nil then begin
    AcqData.Spectrum := nil;
  end;
  inherited Destroy;
end;

{v0.11}
function TSpectrum.PeaksResponsesCalculate: integer;
var
  i: integer;
  p: TULPRObj;
  zeroamount:integer;
  f: TFactor;
begin
  ClearResult;
  Result := 0;

  zeroamount := 0;
  {v0.28}
  f := CombineFactors(AcqData.ULM.Factor, AcqData.ULA.MultiplyFactor, AcqData.ULA.DivideFactor);
  {/v0.28}

  for i := 0 to Peaks.ChildCount - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    if p.Amount <> 0 then begin
      if p.UsrPeakCoef = 0 then
        p.UsrPeakCoef := 1;
      p.Response := (p.AreaSize / p.Amount) * p.UsrPeakCoef * f;
    end else begin
      p.Response := 0;
      inc(zeroamount);
    end;
  end;
  if zeroamount > 0 then begin
    {v0.25 not an error, if Amount set 0, then the peak should be ignored}
    {/v0.25
    SetResult(srPeakAmountZero, 'In peaks response calculate');}
  end;
end;

function TSpectrum.PeaksAmountsCalculate: integer;
var
  r: integer;
begin
  Result := 0;
  {r := 0;}
  if AcqData.ULM.Factor=0 then
    AcqData.ULM.Factor:=1;
  if AcqData.ULM.UseCalibrationFile then begin
    r := PeaksAmountsCalculateCalibrationFile;
    if Result = 0 then
      Result := r;
  end;{ulprtype ulatype}
  if AcqData.ULM.UseInternalStandard then begin
    r := PeaksAmountsCalculateInternalStandard;
    if Result = 0 then
      Result := r;
  end;
  r := PeaksAmountsCalculateValues;
  if Result = 0 then
    Result := r;
end;

{v0.65}

function TSpectrum.CheckCalFileAge: boolean;
var a: integer;
begin
  Result := false;
  if (AcqData.Cal_ULP <> nil) or (AcqData.Cal_UCP <> nil) then begin
    if AcqData.ULM.CalibrationFileName <> '' then begin
      if not AcqData.CheckCalibrationFilePresent then begin
        exit;
      end;
      a := FileAge(AcqData.ULM.CalibrationFileName);
      if (a <> -1) and (a <> AcqData.ULM.CalibrationFileAge) then
      begin
        AcqData.CalPeaksFree;
      end;
    end;
  end;
  Result := true;
end;

function TSpectrum.CheckCalPeaksLoaded: boolean;
var
  d: TAcqData;
  c: TCalData;
  i: integer;
  cp: TULPRObj;
  f: TFactor;
//  a: integer;
  fn: string;
begin
  Result := false;
  if (AcqData.Cal_ULP = nil) and (AcqData.Cal_UCP = nil) then begin
    fn := AcqData.ULM.CalibrationFileName;
    if fn = '' then begin
      SetResult(srCalibrationFileNotSpecified, '');
      exit;
    end else begin
      if not AcqData.CheckCalibrationFilePresent then begin
        ShowMessage(GetTxt('Calibration File Not Found: ') + fn, smError, 0);
        exit;
      end;

      c := nil;
      d := nil;
      if UpperCase(ExtractFileExt(fn)) = ULCExt then begin
        c := TCalData.Create(fn, omRead);
      end else begin
        d := TAcqData.Create(fn, omRead);
      end;
      try
        if c <> nil then begin
          AcqData.Cal_UCP := TUCPObj.Create(AcqData.ULA);
          AcqData.Cal_UCP.SetFlag(rfTemporary, true);
          AcqData.Cal_UCP.Assign(c.ULC_UCP);
          f := 1;
        end else begin
          AcqData.Cal_ULP := TULPObj.Create(AcqData.ULA);
          AcqData.Cal_ULP.SetFlag(rfTemporary, true);
          AcqData.Cal_ULP.Assign(d.ULP);
          f := CombineFactors(d.ULM.Factor, d.ULA.MultiplyFactor, d.ULA.DivideFactor);
        end;
      finally
        if c <> nil then begin
          c.ULF.Modified := false;
          c.Free;
        end else begin
          d.ULF.Modified := false;
          d.Free;
        end;
      end;
      AcqData.ULM.CalibrationFileAge := FileAge(AcqData.ULM.CalibrationFileName);

      if AcqData.Cal_ULP <> nil then begin
        for i := 0 to AcqData.Cal_ULP.ChildCount - 1 do begin
          cp := TULPRObj(AcqData.Cal_ULP.Childs[i]);
          if cp.RecID <> ULPRID then
            continue;
          if cp.UsrPeakCoef = 0 then
            cp.UsrPeakCoef := 1;
          if (cp.AreaSize <> 0) and (cp.Amount <> 0) then begin
            cp.Response := cp.AreaSize / cp.Amount * cp.UsrPeakCoef * f;
          end else begin
            cp.Response := 0;
          end;
        end;
      end else if AcqData.Cal_UCP <> nil then begin
        // do nothing?
      end;
    end;
  end;
  Result := true;
end;
{/v0.65}

function TSpectrum.PeaksAmountsCalculateCalibrationFile: integer;
var
  i: integer;
  p, cp: TULPRObj;

  ccp: TUCPRObj;
begin
  Result := 0;
  {v0.65}
  if not CheckCalFileAge then
    exit;
  {v0.65
  if AcqData.Cal_ULP <> nil then begin
    if AcqData.ULM.CalibrationFileName <> '' then begin
      if not AcqData.CheckCalibrationFilePresent then begin
        exit;
      end;
      a := FileAge(AcqData.ULM.CalibrationFileName);
      if (a <> -1) and (a <> AcqData.ULM.CalibrationFileAge) then
      begin
        AcqData.Cal_ULP.Free;
        AcqData.Cal_ULP := nil;
      end;
    end;
  end;
  }
  {v0.65}
  if not CheckCalPeaksLoaded then
    exit;
  {/v0.65
  if AcqData.Cal_ULP = nil then begin
    if AcqData.ULM.CalibrationFileName = '' then begin
      SetResult(srCalibrationFileNotSpecified, '');
      exit;
    end else begin
      if not AcqData.CheckCalibrationFilePresent then begin
        ShowMessage(GetTxt('Calibration File Not Found: ') + AcqData.ULM.CalibrationFileName, smError, 0);
        exit;
      end;
      AcqData.Cal_ULP := TULPObj.Create(AcqData.ULA);
      AcqData.Cal_ULP.SetFlag(rfTemporary, true);
      d := TAcqData.Create(AcqData.ULM.CalibrationFileName, omRead);
      try
        AcqData.Cal_ULP.Assign(d.ULP);
        f := CombineFactors(d.ULM.Factor, d.ULA.MultiplyFactor, d.ULA.DivideFactor);
      finally
        d.ULF.Modified := false;
        d.Free;
      end;
      AcqData.ULM.CalibrationFileAge := FileAge(AcqData.ULM.CalibrationFileName);
      for i:= 0 to AcqData.Cal_ULP.ChildCount - 1 do begin
        cp := TULPRObj(AcqData.Cal_ULP.Childs[i]);
        if cp.RecID <> ULPRID then
          continue;
        if cp.UsrPeakCoef = 0 then
          cp.UsrPeakCoef := 1;
        if (cp.AreaSize <> 0) and (cp.Amount <> 0) then begin
          cp.Response := cp.AreaSize / cp.Amount * cp.UsrPeakCoef * f;
        end else begin
          cp.Response := 0;
        end;
      end;
    end;
  end;
  }

  Peaks.DoChangeLock;
  try
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID <> ULPRID then
        continue;

      {v0.65}
      if AcqData.Cal_UCP <> nil then begin
        if AcqData.Cal_UCP.HasChildWithFieldUsrValue('PeakName', p.PeakName, TULObj(ccp)) then begin
          p.Response := ccp.Response;
        end else begin
          p.Response := 0;
        end;
      end else
      {/v0.65}
      begin
        if FindPeakByName(AcqData.Cal_ULP, p.PeakName, cp) then
        begin
          p.Response := cp.Response;
        end else begin
          p.Response := 0;
        end;
      end;
    end;
  finally
    Peaks.DoChangeUnlock;
  end;

end;

{v0.13}
function TSpectrum.PeaksAutoCalculate: integer;
begin
  if AcqData.ULA.CalibrationStandard then
    Result := PeaksResponsesCalculate
  else
    Result := PeaksAmountsCalculate;
end;
{/v0.13}


function TSpectrum.PeaksAmountsCalculateInternalStandard: integer;
var
  istd: integer;
  i: integer;
  p: TULPRObj;
  {v0.62pi}
  cf: TFactor;
  {/v0.62pi}
begin
  ClearResult;
  Result := 0;
  {Pro prepocet analyzy na interni standard
    is=find Peak[i].IntStandard
    Method.Factor=Peak[is].Amount/
		((Peak[is].Area/Peak[is].Response)*Peak[is].UserCoef); }
  {v0.31}
  Peaks.DoChangeLock;
  try
  {/v0.31}
    istd := -1;
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID <> ULPRID then
        continue;
      if p.InternalStandard then begin
        istd := i;
        if p.Amount = 0 then begin
          if AcqData.ULA.ISTDAmount = 0 then begin
            SetResult(srPeakAmountZero, p.PeakName + ' - internal standard');
            exit;{ulatype}
          end else begin
            p.Amount := AcqData.ULA.ISTDAmount;
          end;
        end;
        if p.Response = 0 then begin
          SetResult(srPeakResponseZero, p.PeakName + ' - internal standard');
          exit;
        end;
        if p.AreaSize = 0 then begin
          SetResult(srPeakAreaZero, p.PeakName + ' - internal standard');
          exit;
        end;
        if p.UsrPeakCoef = 0 then begin
          p.UsrPeakCoef := 1;
        end;
        {v0.62pi}
        if AcqData.ULM.UndilutedIS then
          cf := 1
        else
          cf := CombineFactors(1, AcqData.ULA.MultiplyFactor, AcqData.ULA.DivideFactor);
        {/v0.62pi}
        AcqData.ULM.Factor := p.Amount /( (p.AreaSize / p.Response) * p.UsrPeakCoef )
           {v0.28} {v0.62pi}/ cf;{/v0.62pi / CombineFactors(1, AcqData.ULA.MultiplyFactor, AcqData.ULA.DivideFactor);}
           {/v0.28};
        break;
      end;
    end;
    if istd = -1 then begin
      SetResult(srNoInternalStandardSpecified,' - in calculate amounts');
      exit;
    end;
  {v0.31}
  finally
    Peaks.DoChangeUnlock;
  end;
  {/v0.31}
end;

{v0.12}
function TSpectrum.PeaksAmountsCalculateValues: integer;
var
  i: integer;
  p: TULPRObj;
  f: TFactor;
begin
  { Vypocet standardni analyzy  with Peak[i] if not Peak[i].IntStandard then Amount=(Area/Response)*UserCoef*Method.Factor; }
  Result := 0;
  Peaks.DoChangeLock;
  try
    f := CombineFactors(AcqData.ULM.Factor, AcqData.ULA.MultiplyFactor, AcqData.ULA.DivideFactor);
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID <> ULPRID then
        continue;
      PeakAmountCalculate(p, f);
    end;
  finally
    Peaks.DoChangeUnlock;
  end;
end;
{/v0.12}

procedure TSpectrum.PeakAmountCalculate(p: TULPRObj; f: TFactor);
var
  ccp: TUCPRObj;
begin
  if not p.InternalStandard then begin
    if AcqData.Cal_UCP <> nil then begin
      if AcqData.Cal_UCP.HasChildWithFieldUsrValue('PeakName', p.PeakName, TULObj(ccp)) then begin
        TCalData.PeakAmountCalculate(p, ccp, f); //p.Response := ccp.Response;
      end else begin
        p.Amount := 0;
      end;
    end else begin
      if p.Response = 0 then begin
        p.Amount := 0;
      end else begin
        if p.UsrPeakCoef = 0 then
          p.UsrPeakCoef := 1;
        if f = 0 then
          f := 1;
        p.Amount := (p.AreaSize / p.Response) * p.UsrPeakCoef * f;
      end;
    end;
  end;
end;

procedure TSpectrum.ClearResult;
begin
  LastResult := 0;
end;

procedure TSpectrum.SetResult(sr: TSpectrumResult; const msg:string);
var
  s: string;
begin
  LastResult := sr;
  case sr of
    srPeakResponseZero : s := 'Peak Response Zero';
    srPeakAmountZero : s := 'Peak Amount Zero';
    srPeakAreaZero : s := 'Peak Area Zero';
    srNoInternalStandardSpecified : s := 'No Internal Standard Specified';
    srCalibrationFileNotSpecified : s := 'Calibration File Not Specified';
    {v0.55}
    srCalibrationFileNotFound: s := 'Calibration File Not Found';
    {/v0.55}
  else
    s := IntToStr(sr);
  end;
  raise ESpectrum.Create(GetTxt({#}'Data Error') + ': ' + s + ' ' + msg);
end;
{/v0.11}

{/TSpectrum.}

{TULADRc.}
function TULADRc.GetData: TStream;
begin
  {v0.50}
  if ULAD = nil then
    Result := nil
  else
  {/v0.50}
    Result := ULAD.Data;
end;

{v0.31}
constructor TULADRc.Create{v0.57}(AULADs: TULADs){/v0.57};
begin
  inherited Create;
  {v0.57}
  FULADs := AULADs;
  {/v0.57}
  PointTimeInterval := UlanPointTimeInterval;
  YCoef := 1;
end;
{/v0.31}

{v0.57}
procedure TULADRc.SetULAD(AULAD: TULADObj);
var n: string;
begin
  FULAD := AULAD;
  n := FULAD.DataName;
  if n = '' then
    n := 'noname';
  FUDVR := TUDVRObj(FULADs.FAcqData.UDVS.FindOrAdd(UDVRID, n));
  FUDVR.DataName := n;
end;
{/v0.57}

{/TULADRc}
{TULADs}
procedure TULADs.CurDataSizeUpdate;
var
  i: integer;
  r: TULADRc;
begin
  for i := 0 to Count - 1 do begin
    r := ULAD[i];
    r.CurDataSize := r.Data.Size;
    {v0.31}
    r.CurDataPos := r.Data.Position;
    {/v0.31}
  end;
end;

procedure TULADs.SetZeroSizes;
var
  ud: TULADRc;
  i: integer;
begin
  for i := 0 to Count -1 do begin
    ud := GetULADRc(i);
    ud.Data.Size := 0;
    {v0.31}
    ud.CurDataSize := 0;
    ud.CurDataPos := 0;
    {/v0.31}
  end;
end;

{v0.31}
procedure TULADs.Start;
var i: integer;
begin
  for i := 0 to Count - 1 do
    ULAD[i].CurTime := 0;
end;

function TULADs.FindULAD(AULAD: TULADObj; var Index: integer): boolean;
var i: integer;
begin
  Result := false;
  for i := 0 to Count - 1 do begin
    if ULAD[i].ULAD = AULAD then begin
      Result := true;
      Index := i;
      exit;
    end;
  end;
end;
{/v0.31}

procedure TULADs.Reset;
var
  ud: TULADRc;
  i: integer;
begin
  for i := 0 to Count -1 do begin
    ud := GetULADRc(i);
    ud.Data.Position := 0;
    {v0.31}
    ud.CurDataPos := 0;
    {/v0.31}
  end;
end;

procedure TULADs.Clear;
begin
  while Count > 0 do
    DelULAD(TULADRc(Items[0]).ULAD);
  inherited;
end;

function TULADs.GetULADRc(Index: integer):TULADRc;
begin
  Result := nil;
  if (Index >= 0) and (Index < Count) then begin
    Result := TULADRc(Items[Index]);
  end;
end;

procedure TULADs.Update;
var
  d: TULADObj;
  i, j: integer;
  fnd: boolean;
begin
  {v0.31}
  for i := 0 to FAcqData.ULA.ChildCount - 1 do begin
    d := TULADObj(FAcqData.ULA.Childs[i]);
    if d.RecID = ULADID then begin
      if not FindULAD(d, j) then begin
        AddULAD(d);
      end;
    end;
  end;

  {v0.69}
  j := 0;
  while j < Count do begin
  {/v0.69
  for j := 0 to Count - 1 do begin}
    d := ULAD[j].ULAD;
    fnd := false;
    for i := 0 to FAcqData.ULA.ChildCount - 1 do begin
      if d = FAcqData.ULA.Childs[i] then begin
        fnd := true;
        break;
      end;
    end;
    if not fnd then begin
      DelULAD(d);
    end {v0.69} else begin
      inc (j);
    end;
    {/v0.69};
  end;
  {/v0.31
  Clear;
  d0 := TULADObj(FAcqData.ULA.FindOrAdd(ULADID, ''));
  AddULAD(d0);
  for i := 0 to FAcqData.ULA.ChildCount - 1 do begin
    o := FAcqData.ULA.Childs[i];
    if (o is TULADObj) and (o <> d0) then begin
      AddULAD(TULADObj(o));
    end;
  end;   }
end;

procedure TULADs.AddULAD(AULAD: TULADObj);
var rc: TULADRc;
begin
  rc := TULADRc.Create{v0.57}(Self){/v0.57};
  rc.ULAD := AULAD;
  AULAD.SetFlag(rfCantDelete, true);
  Add(rc);
end;

procedure TULADs.DelULAD(AULAD: TULADObj);
var
  i: integer;
  ur: TULADRc;
begin
  AULAD.SetFlag(rfCantDelete, false);
  for i := 0 to Count - 1 do begin
    ur := TULADRc(Items[i]);
    if ur.ULAD = AULAD then begin
      Remove(ur);
      exit;
    end;
  end;
end;

constructor TULADs.Create(AAcqData: TAcqDAta);
begin
  inherited Create;
  FAcqData := AAcqData;
  Update;
end;

destructor TULADs.Destroy;
begin
  Clear;
  inherited;
end;
{/TULADs.}

{******************** TAcqData *********************}

{TAcqData.}
{v0.65}
procedure TAcqData.CalPeaksFree;
begin
  Cal_UCP.Free;
  Cal_UCP := nil;
  Cal_ULP.Free;
  Cal_ULP := nil;
end;
{/v0.65}


{v0.69}
procedure TAcqData.SetDefaults;
begin
  SpectrumOpt := DefSpectrumOpt;
  DoMath(moNoneOp,0);
  ClearScanInfo;
end;

procedure TAcqData.DataLoaded;
begin
  ULAD.CurDataSizeUpdate;
  ScanData;
  {$IFNDEF CONSOLE}
  if AutofixData then begin
    ChkAcqData(Self, true);
  end;
  {$ENDIF}
end;
{/v0.69}

constructor TAcqData.Create(const AFileName: string; AMode: TOpenMode);
begin
  inherited Create(AFileName, AMode);
  {v0.69}
  {/v0.69
  FCreatedOK := false;
  SpectrumOpt := DefSpectrumOpt;
  DoMath(moNoneOp,0);
  ClearScanInfo;
  FCreatedOK := true;
  }
end;

constructor TAcqData.CreateFromPoints(APoints: PExpPoints; APointCount: integer);
var
  i: integer;
  ud: TULADRc;
begin
  inherited Create('', omRead);
  {v0.69}{/v0.69
  SpectrumOpt := DefSpectrumOpt;
  DoMath(moNoneOp, 0);
  ClearScanInfo;
  {
   ULF := TULFObj.Create(nil);
  CheckULRecords;}
  ud := ULAD[ULADIndex];
  ud.ULAD.Data := TMemoryStream.Create;
  TMemoryStream(ud.Data).SetSize(APointCount * sizeof(TExpPoint));
  ud.Data.Position := 0;
  for i := 0 to APointCount - 1 do WritePoint(APoints^[i]);
  ScanData;
end;

procedure TAcqData.ULFCreated;
begin
  inherited;
  CheckULRecords;
end;

procedure TAcqData.CheckULRecords;
begin
  ULA := TULAObj(ULF.FindOrAdd(ULAID, ''));
    ULA.SetFlag(rfCantDelete, true);
  ULA.MainUser := Self;
  UDVS := TUDVSObj(ULA.FindOrAdd(UDVSID, ''));
    UDVS.SetFlag(rfCantDelete, true);
  if ULAD = nil then
    ULAD := TULADs.Create(Self)
  else
    ULAD.Update;

  ULAF := TULAFObj(ULA.FindOrAdd(ULAFID, ''));
    ULAF.SetFlag(rfCantDelete, true);

//  FAAPG := TAAPGObj(ULA.FindOrAdd(AAPGID, ''));
//    FAAPG.SetFlag(rfCantDelete, true);

  ULM := TULMObj(ULA.FindOrAdd(ULMID, ''));
    ULM.SetFlag(rfCantDelete, true);

  CheckULMRecords;

  ULI := TULIObj(ULA.FindOrAdd(ULIID, ''));
    ULI.SetFlag(rfCantDelete, true);

  ULB := TULBObj(ULA.FindOrAdd(ULBID, ''));
    ULB.SetFlag(rfCantDelete, true);
  ULP := TULPObj(ULA.FindOrAdd(ULPID, ''));
    ULP.SetFlag(rfCantDelete, true);
  ULV := TULVObj(ULA.FindOrAdd(ULVID, ''));
    ULV.SetFlag(rfCantDelete, true);
  ULVL := TULVLObj(ULV.FindOrAdd(ULVLID, ''));
    ULVL.SetFlag(rfCantDelete, true);
  ULVO := TULVOObj(ULV.FindOrAdd(ULVOID, ''));
    ULVO.SetFlag(rfCantDelete, true);
end;

procedure TAcqData.CheckULMRecords;
begin
  ULMF := TULMFObj(ULM.FindOrAdd(ULMFID, ''));
    ULMF.SetFlag(rfCantDelete, true);
  ULM_ULP := TULPObj(ULM.FindOrAdd(ULPID, ''));
    ULM_ULP.SetFlag(rfCantDelete, true);
  ULM_ULB := TULBObj(ULM.FindOrAdd(ULBID, ''));
    ULM_ULB.SetFlag(rfCantDelete, true);
end;

procedure TAcqData.AAPGAssign(AAAPG: TAAPGObj);
var
  a: TAAPGObj;
begin
  a := TAAPGObj(ULA.FindOrAdd(AAPGID, ''));
  a.Assign(AAAPG);
end;

function TAcqData.GetNonameFileName: string;
begin
  Result := DataDir + 'NONAME_' + ULI.ChannelName + GetFileExt;
end;

{v0.65}
procedure TAcqData.ULFFree;
begin
  CalPeaksFree;
  {v0.69}
  inherited;
  {/v0.69}
  ULA := nil;
  ULAF := nil;
  ULM := nil;

  ULAF := nil;
  ULM := nil;
    ULMF := nil;
    ULM_ULP := nil;
    ULM_ULB := nil;
  ULI := nil;
  ULB := nil;
  ULP := nil;
  ULV := nil;
  ULVL := nil;
  ULVO := nil;
  {v0.69}
  ULI_UDLS := nil;
  {/v0.69 inherited}
  {v0.70}
  UDVS := nil;
  ULAD.Free;
  ULAD := nil;
  {/v0.70}
//  FAAPG := nil;
end;

function TAcqData.GetTemplateExt: string;
begin
  Result := ULTExt;
end;
{/v0.65}

function TAcqData.GetFileExt: string;
begin
  Result := ULFExt;
end;


function TAcqData.GetRawFileName(Index: integer): string;
begin
  Result := GetChannelFileName(DataDir, ULI.ChannelName) + IntToStr(Index);
end;

function TAcqData.GetActiveData: TStream;
begin
  if ActiveULADRc = nil then
    Result := nil
  else
    Result := ActiveULADRc.Data;
end;

function TAcqData.GetActiveULADRc: TULADRc;
begin
  Result := ULAD[ULADIndex];
end;

function TAcqData.GetDataCount: integer;
begin
  Result := ULAD.Count;
end;

function TAcqData.GetDataIndex: integer;
begin
  Result := ULADIndex;
end;

procedure TAcqData.SetDataIndex(Index: integer);
begin
  if (Index >= 0) and (Index < ULAD.Count) then begin
    ULADIndex := Index;
  end else begin
    ULADIndex := -1;
  end;
end;

function TAcqData.IsAllowedExt(const AExt: string): boolean;
var e: string;
begin
  e := UpperCase(AExt);//ulantype ulrectyp
  Result := (e = ULFExt) or (e = ASCExt) or (e = ULTExt);// or (e = ULCExt)
end;

procedure TAcqData.SetFileName(const AFileName: string; AMode: TOpenMode);
var
  e: string;
  fn: string;
begin
  FMode := AMode;
  ClearScanInfo;
  e := UpperCase(ExtractFileExt(AFileName));
  fn := AFileName;
  if FMode <> omCreate then begin

    if not FileExists(AFileName) then
      raise EFOpenError.Create(GetTxt({#}'Can not open file') + ' ' + AFileName);
    {v0.65}
    ULFFree;
    ULFCreate;
    {/v.65
    ULF.Free;
    FULF := TULFObj.Create(nil);}
    if IsAllowedExt(e)
    then
      ULF.LoadFromFile(AFileName);
    if FMode = omReadOnly then
      ULF.ReadOnly := true;
    CheckULRecords;
    if (e <> ULFExt) and (e <> AscExt) and (e <> ULTExt) and (e <> ULCExt)
    then begin
      ULA.FindOrAdd(ULADID, '');
      ULAD.Update;
      if TryConvertFromFile(AFileName, ULA, ActiveData) then begin
        ULAD.Update;
      end else begin
        raise EInvalidFileExt.Create(GetTxt({#}'Invalid file extension') + ': ' + AFileName);
      end;
      ULF.FileName := ReplaceExt(AFileName, ULFExt, true);
    end;
    {v0.69}
    DataLoaded;
    {/v0.69
    ULAD.CurDataSizeUpdate;
    ScanData;}
    {$IFNDEF CONSOLE}
    {v0.69}{/v0.69
    if AutofixData then begin
      ChkAcqData(Self, true);
    end;}
    {$ENDIF}
  end else begin

    if (e <> ULFExt) and (e <> ULTExt) and (e <> ULCExt)
    then
      raise EOnlyULFSupported.Create(GetTxt({#}'Not .ULF or .ULT or .ULC extension') + ': ' + AFileName);
    {v0.65}
    ULFFree;
    {/v0.65
    ULF.Free;}
    ULFCreate;//FULF := TULFObj.Create(nil);
    {v0.13}
    if FileExists(fn) then begin
      ULF.LoadFromFile(fn);
    end else begin
      raise EAcqData.Create(GetTxt({#}'Template Not Found') + ': ' + fn);
    end;
    CheckULRecords;
    FTemplateULADCount := ULAD.Count;

    CheckChannel; // eventually clears ULI.ChannelName used in GetNonameFileName
    ULF.FileName := GetNonameFileName;
    (* now in CreateDataStreams, called just before starting the acquisition
    {$IFNDEF CONSOLE}
    CheckChannel;
    {$ENDIF}
    try
      for i := 0 to ULAD.Count - 1 do begin
        ud := ULAD[i].ULAD;
        rfn := RawFileName[i];
        ud.Data := TFileStream.Create(rfn, fmCreate);
      end;
    except
      on EFCreateError do begin
        raise EAcqAlreadyOpened.Create(GetTxt({#}'Acquisition window already opened for') +
          ' ' + ULI.ChannelName);
      end;
    end;
    *)
  end;
  {$IFNDEF CONSOLE}
  ObjHandler.CheckIn(ULA);
  {$ENDIF}
end;

procedure TAcqData.CreateDataStreams;
var
  i: integer;
  ud: TULADObj;
  rfn: string;
begin
  {$IFNDEF CONSOLE}
  CheckChannel;
  {$ENDIF}
  if FDataStreamsCreated then
    exit;
  try
    for i := 0 to ULAD.Count - 1 do begin
      ud := ULAD[i].ULAD;
      rfn := RawFileName[i];
      ud.Data := TFileStream.Create(rfn, fmCreate);
    end;
    FDataStreamsCreated := true;
  except
    on EFCreateError do begin
      raise EAcqAlreadyOpened.Create(GetTxt({#}'Acquisition window already opened for') +
        ' ' + ULI.ChannelName);
    end;
  end;
end;

procedure TAcqData.ObjUpdated(AObj: TULObj);
begin
  if TULIObj(AObj) = ULI then begin
    {$IFNDEF CONSOLE}
    if not FSettingULI then begin
      CheckChannel;
    end;
    {$ENDIF}
  end;
end;

procedure TAcqData.ObjDestroyed(AObj: TULObj);
begin
  if TULIObj(AObj) = ULI then
    ULI := nil;
end;


procedure TAcqData.RecreateDataLines;
begin
  if (ULI_UDLS = nil) or (ULI_UDLS.ChildCount = 0) then begin
    RecreateDataLinesByDevices;
  end else begin
    RecreateDataLinesByDefinition;
  end;
end;

procedure TAcqData.RecreateDataLinesByDefinition;
var
  i: integer;
  d: TULADObj;
  l: TUDLRObj;
begin
  {v0.69}
  i := 0;
  while i < ULAD.Count do begin
    d := ULAD[i].ULAD;
    if (d.Data.Size = 0) then begin
      ULAD.DelUlad(d);
      d.Free;
    end else
      inc(i);
  end;
  {/v0.69
  for i := 0 to ULAD.Count - 1 do begin
    d := ULAD[i].ULAD;
    if (d.Data.Size = 0) then begin
      ULAD.DelUlad(d);
      d.Free;
    end;
  end;
  }
  for i := 0 to ULI_UDLS.ChildCount - 1 do begin
    l := TUDLRObj(ULI_UDLS.Childs[i]);
    d := TULADObj(ULA.FindOrAdd(ULADID, l.DataName));
    d.AssignClass(l);
  end;
  ULAD.Update;
end;

procedure TAcqData.RecreateDataLinesByDevices;
var
  i, di: integer;
  ul: TULADObj;
  d: TULDRObj;
  prop: TULDPObj;
  fnd: boolean;
  dm: TDeviceMode;
  delcnt: integer;
  pti: single;
begin
  if FChannel = nil then begin
    raise EAcqData.Create('CreateDataLinesByDevices - Channel ' + ULI.ChannelName + ' not found.')
  end;
  dm := FChannel.DeviceMode;

  {check for added devices}
  for i := 0 to FChannel.DeviceCount - 1 do begin
    {$IFDEF USEDLL}
    d := TULDRObj(ModuleGetPropInt(FChannel.Devices[i], mpULDR));
    {$ELSE}
    d := FChannel.Devices[i].ULDR;
    {$ENDIF}
    if d.DeviceType = dtDetector then begin
      fnd := false;
      for di := 0 to ULAD.Count - 1 do begin
        if ULAD[di].Addr = 0 then
          ULAD[di].Addr := StrToInt(d.AddrStr);
        if (StrToInt(d.AddrStr) = ULAD[di].Addr) and (d.DeviceMode = dm) then begin
          fnd := true;
          break;
        end;
      end;
      if not fnd then begin
        ULA.Add(ULADID);
        ULAD.Update;
        di := ULAD.Count - 1;
        {ul.DataName := d.DeviceName;!!! fix later to work with AAA
        if di > 0 then
          ul.DataName := d.DeviceName;}
      end;
      ULAD[di].Addr := StrToInt(d.AddrStr);
      try
        if d.HasChildWithFieldUsrValue('PropDesc','ADCSAMPPER', TULObj(prop)) then
        if prop<>nil then begin

          pti := StrToInt(prop.ValueInPC);
          ULAD[di].PointTimeInterval := pti;
          if (ULI <> nil) then begin
            if ULI.DataWaitTimeout < (pti * 3 * 32 / 1000) then
              ULI.DataWaitTimeout := pti * 100 / 1000;
              { 32 .. number of points in one packet }
              { 3 .. tolerance coefficient (should be more then 2)}
              { 100 .. almost 32 * 3, but gives nicer numbers }
              { 1000 .. pti is in miliseconds, datawaittimeout in seconds }
          end;
        end;
      except
      end;
    end;
  end;
  {/check for added devices}

  {check for removed devices}
  di := 0;
  delcnt := 0;
  while di < ULAD.Count do begin
    fnd := false;
    for i := 0 to FChannel.DeviceCount - 1 do begin
      {$IFDEF USEDLL}
      d := TULDRObj(ModuleGetPropInt(FChannel.Devices[i], mpULDR));
      {$ELSE}
      d := FChannel.Devices[i].ULDR;
      {$ENDIF}
      if (d.DeviceType = dtDetector) and (d.DeviceMode = dm) then begin
        if StrToInt(d.AddrStr) = ULAD[di].Addr then begin
          fnd := true;
          break;
        end;
      end;
    end;

    if not fnd then begin
      ul := ULAD[di].ULAD;
      if (ul.Data.Size = 0) and (di > (FTemplateULADCount - 1) ) then begin
        ULAD.DelUlad(ul);
        ul.Free;
        inc(delcnt);
      end else begin
        inc(di);
      end;
    end else
      inc(di);
  end;
  if delcnt > 0 then
    ULAD.Update;
  {/check for removed devices}
end;

{$IFNDEF CONSOLE}
function TAcqData.CheckChannel: boolean;
begin
  if (Mode <> omCreate) and (not IsTemplate) then begin
    Result := true;
    exit;
  end;

  Result := false;
  if FChannel <> nil then begin
    FChannel.ULN.SetFlag(rfCantDelete, false);
  end;                                  //channelsu
  if Channels.HasChannel(ULI.ChannelName, FChannel) then begin
    {FChannel := Channels.FindChannel(ULI.ChannelName);
    if FChannel <> nil then begin}
    FChannel.ULN.SetFlag(rfCantDelete, true);
    {end;}
    if (FMode = omCreate) and (not FSettingULI) then
      RecreateDataLines;
    Result := true;
  end else begin;
    { FChannel set to nil if not Channels.HasChannel.
      Clear the ChannelName too. }
    ULI.ChannelName := '';
  end;
end;
{$ENDIF}

procedure TAcqData.ScanData;
var
  ap:TExpPoint;
  i: integer;
begin
  Reset;
  ClearScanInfo;
  ScanPeaks;
  for i := 0 to DataCount - 1 do
  begin
    DataIndex := i;
    while ReadPoint(ap) do
      CheckPoint(ap);
    Reset;
  end;
  if DataCount > 0 then
    DataIndex := 0;
end;

procedure TAcqData.FixPoint(var APoint:TExpPoint);
begin
  if (APoint.y > SpectrumOpt.Limit.Max.Y) then
    APoint.y := SpectrumOpt.Limit.Max.Y;
  if (APoint.y < SpectrumOpt.Limit.Min.Y) then
    APoint.y := SpectrumOpt.Limit.Min.Y;
end;

procedure TAcqData.ScanPeaks;
var
  ep: TExpPoint;
  p: TULPRObj;
  o: TULObj;
  i: integer;
begin
  for i := 0 to ULP.ChildCount - 1 do begin
    o := ULP.Childs[i];
    if o.RecID = ULPRID then begin
      p := TULPRObj(o);
      ep.Y := min(p.BaseY1, p.BaseY2);
      ep.X := p.X1;
      CheckPoint(ep);
      ep.X := p.X2;
      ep.Y := max(p.Y1, p.Y2) + p.Height;
      CheckPoint(ep);
    end;
  end;
end;

procedure TAcqData.CheckPoint(const APoint:TExpPoint);
begin
  if APoint.x < SpectrumOpt.Extreme.Min.X then
    SpectrumOpt.Extreme.Min.X := APoint.x;

  if APoint.y < SpectrumOpt.Extreme.Min.Y then
    SpectrumOpt.Extreme.Min.Y := APoint.y;

  if APoint.x > SpectrumOpt.Extreme.Max.X then
    SpectrumOpt.Extreme.Max.X := APoint.x;

  if APoint.y > SpectrumOpt.Extreme.Max.Y then
    SpectrumOpt.Extreme.Max.Y := APoint.y;
end;

procedure TAcqData.ClearScanInfo;
begin
  SpectrumOpt.Extreme.Max := SpectrumOpt.Limit.Min;
  SpectrumOpt.Extreme.Min := SpectrumOpt.Limit.Max;
end;

procedure TAcqData.Reset;
begin
  if ULAD <> nil then
    ULAD.Reset;
  if ULAF <> nil then
    ULAF.Data.Position := 0;
  FCurDiluteCount := 0;
  CurIndex := 0;
end;

procedure TAcqData.Seek(APos:longint);
begin
  ActiveData.Position := APos;
  ActiveULADRc.CurDataPos := APos;
  ULAF.Data.Position := APos;
  FCurDiluteCount := 0;
end;

procedure TAcqData.SeekPoint(Index:Longint);
begin
  if Index < 0 then begin
    Seek(ActiveULADRc.CurDataPos + sizeof(TExpPoint) * Index);
    CurIndex := CurIndex + Index;
    exit;
  end;
  Seek(Index * sizeof(TExpPoint));
  CurIndex := Index;
end;

function TAcqData.WritePoint(var APoint:TExpPoint):boolean;
var
  urc: TULADRc;
  s: TStream;
begin
  urc := ActiveULADRc;
  s := urc.Data;
  if s is TMemoryStream then begin
    if urc.CurDataPos + sizeof(APoint) > urc.CurDataSize then begin
      s.WriteBuffer(APoint, sizeof(APoint));
      urc.CurDataSize := s.Size;
      urc.CurDataPos := urc.CurDataSize;
    end else begin
      move(APoint, PLongByteArray(TMemoryStream(s).Memory)^[urc.CurDataPos], sizeof(APoint));
      inc(urc.CurDataPos, sizeof(APoint));
    end;
  end else begin
    s.WriteBuffer(APoint, sizeof(APoint));
    urc.CurDataSize := s.Size;
    urc.CurDataPos := urc.CurDataSize;
  end;
  Result := true;
end;

function TAcqData.ReadPoint(var APoint:TExpPoint):boolean;
var
  s: TStream;
  urc: TULADRc;
begin
  if UseFiltered then begin
    s := ULAF.Data;
  end else begin
    s := ActiveData;
  end;
  repeat
    if s is TMemoryStream then begin
      urc := ActiveULADRc;
      if urc.CurDataPos + sizeof(APoint) > urc.CurDataSize then begin
        Result := false
      end else begin
        move(PLongByteArray(TMemoryStream(s).Memory)^[urc.CurDataPos], APoint, sizeof(APoint));
        inc(urc.CurDataPos, sizeof(APoint));
        Result := true;
      end;
    end else begin
      Result := (s.Read(APoint, sizeof(APoint)) = sizeof(APoint))
    end;
    if (not FApplyMath) or (not Result) then
      break;

    inc(FCurDiluteCount);
    if FCurDiluteCount >= FDiluteCount then begin
      FCurDiluteCount := 0;
      break;
    end else begin
      continue;
    end;

    break;
  until false;

  if FApplyMath then begin
    DoApplyMath(APoint);
    {
    APoint.X := APoint.X * FMultiplyX + FAddX;
    APoint.Y := APoint.Y * FMultiplyY + FAddY;
    if FLogY then
      APoint.Y := Log10(APoint.Y);}
  end;
end;

function TAcqData.ReadIndexPoint(var APoint: TIndexPoint): boolean;
var
  p: TExpPoint;
begin
  if ReadPoint(p) then begin
    APoint.Point := p;
    APoint.Index := CurIndex;
    inc(CurIndex);
    ReadIndexPoint := true;
  end else
    ReadIndexPoint := false;
end;

function TAcqData.GetSize: longint;
begin
  GetSize := ActiveData.Size;
end;

function TAcqData.GetPointCount: longint;
begin
  Result := GetSize div sizeof(TExpPoint);
end;

procedure TAcqData.Clear;
begin
  Truncate(0);
end;


function TAcqData.GetPoint(Index: TPointCount; var APoint: TExpPoint): boolean;
var
  s: TStream;
begin
  GetPoint := false;
  if UseFiltered then
    s := ULAF.Data
  else
    s := ActiveData;
  if (Index < 0) or (s.Size <= (Index * sizeof(TExpPoint))) then
    exit;
  s.Position := Index * sizeof(TExpPoint);
  s.ReadBuffer(APoint, sizeof(TExpPoint));
  if FApplyMath then begin
    DoApplyMath(APoint);
    {
    APoint.X := APoint.X * FMultiplyX + FAddX;
    APoint.Y := APoint.Y * FMultiplyY + FAddY;
    if FLogY then
      APoint.Y := Log10(APoint.Y);
    }
  end;
  GetPoint := true;
end;

function TAcqData.GetIndexPoint(Index: LongInt; var APoint: TIndexPoint): boolean;
var
  p: TExpPoint;
begin
  if GetPoint(Index, p) then begin
    APoint.Point := p;
    APoint.Index := Index;
    GetIndexPoint := true;
  end else
    GetIndexPoint := false;
end;

procedure TAcqData.PutPoint(Index: Longint; APoint: TExpPoint);
var
  d: TULADRc;
begin
  d := ActiveULADRc;
  d.Data.Position := Index * SizeOf(APoint);;
  d.Data.WriteBuffer(APoint, sizeof(APoint));
end;

procedure TAcqData.AddPoint(var APoint: TExpPoint);
var d: TStream;
begin
  FixPoint(APoint);
  CheckPoint(APoint);
  d := ActiveData;
  PutPoint(d.Size div sizeof(TExpPoint), APoint);
end;
{
procedure TAcqData.ChangeFileName(const AFileName: string);
begin
  ULF.FileName := ChangeFileExt(AFileName, ULFExt);
end;
}
procedure TAcqData.SaveTo(const AFileName: string);
{ Saves current data to AFileName and set it as the current file. }
var
  s: TFileStream;
  e: TULEObj;
  ext: string;
begin
  ext := UpperCase(ExtractFileExt(AFileName));
  if (ext = UlfExt) or (ext = AscExt) or (ext = UltExt) or (ext = ULCExt)
  then begin
    if ext = UltExt then begin
      GetReadyForTemplate;
    end;
    if ext = ULCExt then begin
      ULA.CalibrationStandard := true;
    end;

    ULF.SaveToFile(AFileName);
  end else begin
    if ExpImp.HasChildWithFieldUsrValue(fnExtName, ext, TULObj(e)) then begin
      s := TFileStream.Create(AFileName, fmCreate);
      try
        ULConvertTo(e, ActiveData, s);
      finally
        s.Free;
      end;
    end else begin
      raise EInvalidFileExt.Create(GetTxt({#}'Unsupported file extension') + ': ' + AFileName);
    end;
  end;
end;

procedure TAcqData.GetReadyForTemplate;
begin
  ULP.Clear;
  ULB.Clear;
  ULAD.SetZeroSizes;
  ULI.SetFlag(rfWriteLocked, false);
end;

procedure TAcqData.GrowShowLimits(dX:TScreenX; dY:TScreenY);
begin
  SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.ViewLimit.Max.X + dx;
  if SpectrumOpt.ViewLimit.Max.X > SpectrumOpt.Limit.Max.X then
    SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.Limit.Max.X
  else if SpectrumOpt.ViewLimit.Min.X < SpectrumOpt.Limit.Min.X then
    SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.Limit.Min.X;

  SpectrumOpt.ViewLimit.Max.Y :=  SpectrumOpt.ViewLimit.Max.Y + dy;
  if SpectrumOpt.ViewLimit.Max.Y > SpectrumOpt.Limit.Max.Y then
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.Limit.Max.Y
  else if SpectrumOpt.ViewLimit.Max.Y < SpectrumOpt.Limit.Min.Y then
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.Limit.Min.Y;
end;

procedure TAcqData.MoveShowLimits(dx,dy:integer);
var
  curdx:TXValue;
  curdy:TYValue;
begin
  curdx := SpectrumOpt.ViewLimit.Max.X - SpectrumOpt.ViewLimit.Min.X;
  curdy := SpectrumOpt.ViewLimit.Max.Y - SpectrumOpt.ViewLimit.Min.Y;
  if dx > 0 then begin
    SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.ViewLimit.Max.X + dx;
    if SpectrumOpt.ViewLimit.Max.X > SpectrumOpt.Limit.Max.X then
      SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.Limit.Max.X;
    SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.ViewLimit.Max.X - curdx;
  end else if dx < 0 then begin
    SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.ViewLimit.Min.X + dx;
    if SpectrumOpt.ViewLimit.Min.X < SpectrumOpt.Limit.Min.X then
      SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.Limit.Min.X;
    SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.ViewLimit.Min.X + curdx;
  end;

  if dy > 0 then begin
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.ViewLimit.Max.Y + dy;
    if SpectrumOpt.ViewLimit.Max.Y > SpectrumOpt.Limit.Max.Y then
      SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.Limit.Max.Y;
    SpectrumOpt.ViewLimit.Min.Y := SpectrumOpt.ViewLimit.Max.Y - curdy;
  end else if dy < 0 then begin
    SpectrumOpt.ViewLimit.Min.Y := SpectrumOpt.ViewLimit.Max.Y + dy;
    if SpectrumOpt.ViewLimit.Min.Y < SpectrumOpt.Limit.Min.Y then
      SpectrumOpt.ViewLimit.Min.Y := SpectrumOpt.Limit.Min.Y;
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.ViewLimit.Min.Y + curdy;
  end;
end;

function TAcqData.Current: TPointCount;
begin
  if UseFiltered then
    Current := ULAF.Data.Position div sizeof(TExpPoint)
  else
    Current := ActiveULADRc.Data.Position div sizeof(TExpPoint);
end;

procedure TAcqData.GetViewLimit(var AViewLimit: TExpViewLimit);
begin
  AViewLimit := SpectrumOpt.ViewLimit;
end;

procedure TAcqData.SetViewLimit(const AViewLimit:TExpViewLimit);
var
  uvl:TUserViewLimit;
begin
  SpectrumOpt.ViewLimit := AViewLimit;
  SpectrumOpt.ViewLimit.Size.X := SpectrumOpt.ViewLimit.Max.X - SpectrumOpt.ViewLimit.Min.X;
  SpectrumOpt.ViewLimit.Size.Y := SpectrumOpt.ViewLimit.Max.Y - SpectrumOpt.ViewLimit.Min.Y;
  if (SpectrumOpt.ViewLimit.Size.X = 0) or (SpectrumOpt.ViewLimit.Size.Y = 0) then begin
    ShowMessage(GetTxt({#}'Zero spectrum ViewLimit size'), smError, 0);
  end;
  with SpectrumOpt do begin
    ExpToUser(ViewLimit.Min, uvl.Min);
    ExptoUser(ViewLimit.Max, uvl.Max);
    ULVL.MinX := uvl.Min.X;
    ULVL.MinY := uvl.Min.Y;
    ULVL.MaxX := uvl.Max.X;
    ULVL.MaxY := uvl.Max.Y;
  end;
end;

procedure TAcqData.SetLimit(const ALimit:TExpViewLimit);
begin
  SpectrumOpt.Limit := ALimit;{just for now }
  SpectrumOpt.Limit.Size.X := SpectrumOpt.Limit.Max.X - SpectrumOpt.Limit.Min.X;
  SpectrumOpt.Limit.Size.Y := SpectrumOpt.Limit.Max.Y - SpectrumOpt.Limit.Min.Y;
  if (SpectrumOpt.Limit.Size.X = 0) or (SpectrumOpt.Limit.Size.Y = 0) then begin
    ShowMessage(GetTxt({#}'Zero spectrum limit size'), smError, 0);
  end;
end;

function TAcqData.ExpToScreen(const AExpPoint:TExpPoint; const AScreenDisp: TScreenDisp;
  var AScreenPoint:TScreenPoint): integer;
{ prevod exp. hodnoty na grafickou, za pomoci graphic limit
  hodnot, returns <> 0 if AScreenPoint is out of the AScreenDisp}
begin
  Result := 0;
  if AExpPoint.X > SpectrumOpt.ViewLimit.Max.X then begin
    Result := Result or opRight;
    if AExpPoint.Y > SpectrumOpt.ViewLimit.Max.Y then
      Result := Result or opAbove
    else if AExpPoint.Y < SpectrumOpt.ViewLimit.Min.Y then
      Result := Result or opBelow;
  end else if AExpPoint.X < SpectrumOpt.ViewLimit.Min.X then begin
    Result := Result or opLeft;
    if AExpPoint.Y > SpectrumOpt.ViewLimit.Max.Y then
      Result := Result or opAbove
    else if AExpPoint.Y < SpectrumOpt.ViewLimit.Min.Y then
      Result := Result or opBelow;
  end;

  if AExpPoint.Y > SpectrumOpt.ViewLimit.Max.Y then begin
    begin
      AScreenPoint.Y := AScreenDisp.Top;
    end
  end else if AExpPoint.Y < SpectrumOpt.ViewLimit.Min.Y then begin
     begin
       AScreenPoint.Y := AScreenDisp.Bottom;
     end
  end else begin
    begin
      AScreenPoint.Y := AScreenDisp.Bottom + RectInclusive
      - round(
        (AScreenDisp.Bottom - AScreenDisp.Top + RectInclusive) *
        (AExpPoint.Y - SpectrumOpt.ViewLimit.Min.Y) /
        (SpectrumOpt.ViewLimit.Size.Y)
       );
    end;
  end;
  AScreenPoint.X := AScreenDisp.Left + round(
    (AScreenDisp.Right - AScreenDisp.Left + RectInclusive) *
    (AExpPoint.X - SpectrumOpt.ViewLimit.Min.X) / SpectrumOpt.ViewLimit.Size.X
  );
end;

function TAcqData.ExpSizeToScreenSize(const AExpPoint:TExpPoint; const AScreenDisp: TScreenDisp;
  var AScreenPoint:TScreenPoint): integer;
{ conversion of distance in experimental units to distance in screen points }
begin
  Result := 0;
  AScreenPoint.Y := round((AScreenDisp.Bottom - AScreenDisp.Top + RectInclusive) *
    AExpPoint.Y / SpectrumOpt.ViewLimit.Size.Y);
  AScreenPoint.X := round((AScreenDisp.Right - AScreenDisp.Left + RectInclusive) *
    AExpPoint.X / SpectrumOpt.ViewLimit.Size.X);
end;

function TAcqData.ScreenToExp(const ScreenPoint: TScreenPoint; const AScreenDisp: TScreenDisp;
  var AExpPoint: TExpPoint): boolean;
var
  r:real;
begin
  ScreenToExp := false;
  if AScreenDisp.Left = AScreenDisp.Right then
    exit;
  if AScreenDisp.Top = AScreenDisp.Bottom then
    exit;
  r := ScreenPoint.X - AScreenDisp.Left;
  r := r *(SpectrumOpt.ViewLimit.Size.X) / (AScreenDisp.Right - AScreenDisp.Left + RectInclusive);
  AExpPoint.X := SpectrumOpt.ViewLimit.Min.X + r;
  AExpPoint.Y := SpectrumOpt.ViewLimit.Min.Y +
    (AScreenDisp.Bottom + RectInclusive - ScreenPoint.Y) *
     SpectrumOpt.ViewLimit.Size.Y / (AScreenDisp.Bottom - AScreenDisp.Top + RectInclusive);
  ScreenToExp := true;
end;

function TAcqData.FindDataPoint(const AExpPoint:TExpPoint;
  var ADataPoint:TExpPoint; var Index:TPointCount):boolean;
var
  l, h, i, c:longint;
  p:TExpPoint;
begin
  Index := -1;
  l := 0;
  h := GetPointCount - 1;
  while l <= h do begin
    i := (l + h) shr 1;
    GetPoint(i, p);
    if p.x < AExpPoint.X then
      c := -1
    else if p.x > AExpPoint.X then
      c := 1
    else
      c := 0;
    if c < 0 then
      L := I + 1
    else begin
      h := i - 1;
      if c = 0 then begin
        l := i;
      end;
    end;
  end;
  Index := l;
  ADataPoint := p;
  FindDataPoint := true;
end;

class function TAcqData.UserToExp(const AUserPoint: TUserPoint;
  var AExpPoint: TExpPoint): boolean;
begin
  AExpPoint.X := AUserPoint.X * 60;
  AExpPoint.Y := AUserPoint.Y;
  UserToExp := true;
end;

class function TAcqData.ExpToUser(const AExpPoint:TExpPoint;
      var AUserPoint:TUserPoint):boolean;
begin
  AUserPoint.X := AExpPoint.X / 60;
  AUserPoint.Y := AExpPoint.Y;
  ExpToUser := true;
end;

function TAcqData.UserToScreen(const AUserPoint: TUserPoint;
  const AScreenDisp: TScreenDisp; var AScreenPoint: TScreenPoint): boolean;
var ap:TExpPoint;
begin
  UserToScreen := false;
  if not UserToExp(AUserPoint, ap) then
    exit;
  if ExpToScreen(ap, AScreenDisp, AScreenPoint) <> 0 then
    exit;
  UserToScreen := true;
end;

function TAcqData.UserSizeToScreenSize(const AUserPoint:TUserPoint;
  const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint):boolean;
var ap:TExpPoint;
begin
  Result := false;
  if not UserToExp(AUserPoint, ap) then
    exit;
  if ExpSizeToScreenSize(ap, AScreenDisp, AScreenPoint) <> 0 then
    exit;
  Result := true;
end;

procedure TAcqData.FilterUpdate;
begin
end;

{v0.67}
destructor TAcqData.Destroy;
var
  i: integer;
  d: TULADRc;
begin
  if (Spectrum <> nil) then begin
    if Spectrum.AcqData <> Self then begin
      ShowMessage(GetTxt('Invalid Spectrum.AcqData'), smError, 0);
    end else begin
      Spectrum.AcqData := nil;
    end;
  end;

  if FChannel <> nil then begin
    FChannel.ULN.SetFlag(rfCantDelete, false);
  end;

  try
    if ShouldBeAutoSaved then begin
      {v0.69}
      if FCreatedOK then
        ULF.Save;
      {/v0.69}
      for i := 0 to ULAD.Count - 1 do begin
        if RawFileName[i] <> '' then begin
          d := ULAD[i];
          if d.Data is TFileStream then begin
            d.ULAD.Data := nil;
            DeleteFile(RawFileName[i]);
          end;
        end;
      end;
    end;
  finally
    {v0.70}{/v0.70
    ULAD.Free;
    ULI := nil;}
  end;

  inherited Destroy;
end;

{/v0.67
destructor TAcqData.Destroy;
var
  i: integer;
  d: TULADRc;
begin
  if FChannel <> nil then begin
    if FChannel <> nil then
      FChannel.ULN.SetFlag(rfCantDelete, false);
  end;

  try
    if (ULF <> nil) and (ULF.FileName <> '') and ULF.Modified  and (not ULF.ReadOnly)
    then begin
      if FCreatedOK then
        ULF.SaveToFile('');
      for i := 0 to ULAD.Count - 1 do begin
        if RawFileName[i] <> '' then begin
          d := ULAD[i];
          if d.Data is TFileStream then begin
            d.ULAD.Data := nil;
            DeleteFile(RawFileName[i]);
          end;
        end;
      end;
    end;
  finally
    ULAD.Free;
    ULI := nil;
    ULF.Free;
  end;

  if (Spectrum <> nil) then begin
    if Spectrum.AcqData <> Self then begin
      ShowMessage(GetTxt('Invalid Spectrum.AcqData'), smError, 0);
    end else begin
      Spectrum.AcqData := nil;
    end;
  end;

  inherited Destroy;
end;
}

procedure TAcqData.MethodLoadFromFile(const AFileName: string);
var
  f: TULFObj;
  m: TULMObj;
begin
  f := TULFObj.Create(nil);
  try
    f.LoadFromFile(AFileName);
    if not f.FindObj(ULMID, foNotSelf{v0.13}{/v0.13 + foNotRecursive}, '', TULObj(m)) then begin
      ShowMessage(GetTxt({#}'No method find in file') + ' ' + AFileName, smError, 0);
      exit;
    end;
    ULM.Clear;
    ULM.Assign(m);
    ULM.MethodTemplate := AFileName;
    CheckULMRecords;
  finally
    f.Free;
  end;
end;

procedure TAcqData.MethodSaveToFile(const AFileName: string);
begin
  ULM.SaveToFile(AFileName);
  ULM.MethodFileName := AFileName;
end;

function TAcqData.GetActiveMethodFileName: string;
begin
  if ULM.MethodFileName <> '' then
    Result := ULM.MethodFileName
  else
    Result := ULM.MethodTemplate;
end;

procedure TAcqData.SpectrumGet(var sp: TSpectrum);
begin
  if Spectrum = nil then begin
    sp := TSpectrum.Create(Self);
  end else begin
    sp := Spectrum;
  end;
end;

procedure TAcqData.SpectrumRelease(sp: TSpectrum);
begin
  if sp <> Spectrum then
    sp.Free;
end;

procedure TAcqData.CalibrationFileSelect(const AFileName: string);
var sp: TSpectrum;
begin
  if ULA.CalibrationStandard then
    exit;
  if ULF.FileName = AFileName then
    exit;
  ULM.UseCalibrationFile := true;
  ULM.CalibrationFileName := AFileName;
  {v0.65}
  CalPeaksFree;
  {/v0.65
  Cal_ULP.Free;
  Cal_ULP := nil;
  }
  SpectrumGet(sp);
  try
    sp.PeaksAmountsCalculateCalibrationFile;
    sp.PeaksAmountsCalculateValues;
  finally
    SpectrumRelease(sp);
  end;
end;

procedure TAcqData.AcquiredDataDiscard;
begin
  ULAD.SetZeroSizes;
  ULAF.Data.Size := 0;
  ULP.Clear;
  ULB.Clear;
end;

procedure TAcqData.DoApplyMath(var APoint: TExpPoint);
begin
  if FLogY then
    APoint.Y := Log10(APoint.Y);
  APoint.X := APoint.X * FMultiplyX + FAddX;
  APoint.Y := APoint.Y * FMultiplyY + FAddY;
end;

procedure TAcqData.DoMath(mo:TDataMathOperation; r: single);
{ Set value of mo corresponding variable and activate FApplyMath field
  (or desactivate if mo = moNone) }
begin
  case mo of
    moNoneOp: begin
      FMultiplyX := 1;
      FMultiplyY := 1;
      FAddX := 0;
      FAddY := 0;
      FApplyMath := false;
      FDiluteCount := 1;
      FLogY := false;
      if ULAF <> nil then
        ScanData;// to fix min/max
      if Spectrum <> nil then
        Spectrum.RecalculatePeaks;
      exit;
    end;
    moMultiplyX: FMultiplyX := r;
    moMultiplyY: FMultiplyY := r;
    moAddX: FAddX := r;
    moAddY: FAddY := r;
    moDilute: begin
      FDiluteCount := round(r);
      if FDiluteCount <= 0 then
        FDiluteCount := 1;
    end;
    moLogY: FLogY := (r <> 0);
  else
    exit;
  end;
  FApplyMath := true;
  if ULAF <> nil then
    ScanData; // to fix min/max
  if Spectrum <> nil then
    Spectrum.RecalculatePeaks;
end;

procedure TAcqData.MakeMathPermanent;
var
  index: integer;
  p:TExpPoint;
begin
  index := 0;
  while GetPoint(index, p) do begin
    PutPoint(index, p);
    inc(index);
  end;
  DoMath(moNoneOp, 0);
end;

procedure TAcqData.InstrumentLoadFromFile(const AFileName: string);
var
  f: TULFObj;
  m: TULIObj;
begin
  f := TULFObj.Create(nil);
  try
    f.LoadFromFile(AFileName);
    if not f.FindObj(ULIID, foNotSelf, '', TULObj(m)) then begin
      ShowMessage(GetTxt({#}'No Instrument found in file') + ' ' + AFileName, smError, 0);
      exit;
    end;
    ULI.Clear;
    ULI.Assign(m);
    ULI.InstrumentTemplate := AFileName;
  finally
    f.Free;
  end;
end;

procedure TAcqData.InstrumentSaveToFile(const AFileName: string);
begin
  ULI.SaveToFile(AFileName);
  ULI.InstrumentFileName := AFileName;
end;

procedure TAcqData.Truncate(AfterX: TXValue);
{ Remove all acqisition data (together with peaks, baselines)
  after specified X value }
var
  i: integer;
  p: TULPRObj;
  b: TULBRObj;
  APoint: TExpPoint;
  osize:integer;
  di: integer;
  urc: TULADRc;
begin
  i := 0;
  while i < ULP.ChildCount do begin
    p := TULPRObj(ULP.Childs[i]);
    if p.X1 > AfterX then begin
      p.Free;
    end else begin
      inc(i);
    end;
  end;
  i := 0;
  while i < ULB.ChildCount do begin
    b := TULBRObj(ULB.Childs[i]);
    if b.X1 >= AfterX then
      b.Free
    else
      inc(i);
  end;

  for di := 0 to DataCount - 1 do begin
    DataIndex := di;
    urc := ActiveULADrc;
    osize := urc.Data.Size;
    urc.Data.Position := 0;
    if AfterX > 0 then
    while
      (urc.Data.Read(APoint, sizeof(APoint)) = sizeof(APoint)) and
      (APoint.X <= AfterX)
    do;
    urc.Data.Size := urc.Data.Position;
    urc.CurDataSize := urc.Data.Size;

    ULAF.Data.Size := urc.CurDataSize;

    if osize <> urc.Data.Size then
      ULF.Modified := true;
  end;
  Reset;
end;

function TAcqData.GetIsTemplate: boolean;
begin
  Result := UpperCase(ExtractFileExt(ULF.FileName)) = ULTExt;
end;

//function TAcqData.GetIsCalFile: boolean;
//begin
//  Result := UpperCase(ExtractFileExt(ULF.FileName)) = ULCExt;
//end;

function TAcqData.DataSize: integer;
begin
  if ActiveData=nil then
    Result:=0
  else
    Result := ActiveData.Size;
end;

function TAcqData.GetUsrActiveULADRc: TULADRc;
begin
  Result := ULAD[UsrDataIndex];
end;

procedure TAcqData.SetUsrActiveULADRc(AULADRc: TULADRc);
var
  i: integer;
begin
  for i := 0 to DataCount - 1 do begin
    if ULAD[i] = AULADRc then begin
      UsrDataIndex := i;
      DataIndex := i;
      break;
    end;
  end;
end;

function TAcqData.GetActiveDataName: string;
begin
  Result := ActiveULADRc.ULAD.DataName;                                              
end;

function TAcqData.GetUsrActiveDataName: string;
begin
  Result := ULAD[UsrDataIndex].ULAD.DataName;
end;

procedure TAcqData.SetActiveDataName(const ADataName: string);
var i: integer;
begin
  if ULAD[ULADIndex].ULAD.DataName <> ADataName then
  for i := 0 to ULAD.Count - 1 do begin
    if ULAD[i].ULAD.DataName = ADataName then begin
      DataIndex := i;
      break;
    end;
  end;
end;

procedure TAcqData.SetUsrActiveDataName(const ADataName: string);
var old,i: integer;
begin
  for i := 0 to ULAD.Count - 1 do begin
    if ULAD[i].ULAD.DataNAme = ADataName then begin
      old := UsrDataIndex;
      UsrDataIndex := i;
      DebLog('UsrDataIndex: old=' + IntToStr(old) + ' new=' + IntToStr(i));
      DataIndex := i;
      break;
    end;
  end;
end;

procedure TAcqData.UsrDataIndexReselect;
begin
  DataIndex := UsrDataIndex;
end;

function TAcqData.HasData: boolean;
begin
  Result := DataSize > 0;
end;

procedure TAcqData.DiscardData;
begin
  AcquiredDataDiscard;
end;


function TAcqData.GetAddrULADIndex(AAddr: integer): integer;
{ Returns ULADIndex of ULADRc, that has given AAddr; if
    not such found - returns 0 (default index) }
var
  i: integer;
begin
  Result := 0;
  for i := 0 to ULAD.Count - 1 do begin
    if ULAD[i].Addr = AAddr then begin
      Result := i;
      exit;
    end;
  end;
end;

procedure TAcqData.SetULI(AULI:TULIObj);
begin
  FSettingULI := true;
  try
    if AULI <> FULI then begin
      if FULI <> nil then
        FULI.UserUnregister(Self);
      FULI := AULI;
      if FULI <> nil then begin
        FULI.UserRegister(Self);
        {0.50}
        {$IFNDEF CONSOLE}
        FULI.FindField(fnChannelName).FldDesc.ValuesSource := Channels.ULL;
        if FULI.ChannelName = '' then
          FULI.ChannelName := Channels.ULL.ChannelName;
        {set to default channel name}
        {$ENDIF}

        ULI_UDLS := TUDLSObj(FULI.FindOrAdd(UDLSID, ''));
        FULI_USP.Free;
        FULI_USP := nil;
        {/v0.50}
      end;
    end;
  finally
    FSettingULI := false;
  end;
end;

function TAcqData.GetULI_USP: TUSPObj;{ sequence program }
begin
  if (FULI_USP = nil) and (FULI <> nil) then
    FULI_USP := TUSPObj(FULI.FindOrAdd(USPID, ''));
  Result := FULI_USP;
end;


function TAcqData.GetPrg: TSeqPrg;
var f: TULFObj;
begin
  if FPrg = nil then begin
    {v0.64 "feature"? of ULObj to load from such file data if exist. Makes troubles
     because of different owner of USP object. }
    if FileExists('USP.USP') then DeleteFile('USP.USP');
    {/v0.64}
    FPrg := TSeqPrg.Create(nil, nil, USPID);
    {v0.64}
    f := FPrg.ULF;
    FPrg.ULF := nil; FPrg.Obj := nil;
    f.Free;
    {/v0.64}
  end;
  if FPrg.Obj <> ULI_USP then begin
    FPrg.Obj := ULI_USP;
    FPrg.ClassFieldsUpdate;
  end;
  Result := FPrg;
end;

function TAcqData.GetDetAddrList: string;
var i: integer;
begin
  Result := '';
  for i := 0 to ULAD.Count - 1 do begin
    if ULAD[i].Addr <> 0 then
    begin
      if Result = '' then begin
        Result := IntToStr(ULAD[i].Addr)
      end else begin
        Result := Result + ',' + IntToStr(ULAD[i].Addr);
      end;
    end;
  end;
  if Result = '' then begin
    {$IFDEF USEDLL}
    i := ModulesGetPropInt(Modules, mpUVDetAddr);// modutype
    if i <> 0 then
      Result := IntToStr(i);
    {$ELSE}
    if (CurUVDetAddr <> 0) then
      Result := IntToStr(CurUVDetAddr);
    {$ENDIF}
  end;
end;

{$IFNDEF CONSOLE}
function TAcqData.GetChannel: TChannel;
var c: TChannel;
begin
  c := Channels.FindChannel(ULI.ChannelName);
  if c <> FChannel then
    CheckChannel;
  Result := FChannel;
end;
{$ENDIF}

function TAcqData.DataNamesEqual(ADataName1: string; ADataName2: string): boolean;
begin
  if (ADataName1 = '') then ADataName1 := ULM.DefDataName;
  if (ADataName2 = '') then ADataName2 := ULM.DefDataName;
  if (ADataName1 = '*') or (ADataName1 = '*') then begin
    Result := true;
  end else begin
    Result := ADataName1 = ADataName2;
  end;
end;

procedure TAcqData.FormRegister(AForm: TObject);
begin
  ULA.UserRegister(AForm);
  ULP.UserRegister(AForm);
  ULB.UserRegister(AForm);
end;

procedure TAcqData.FormUnregister(AForm: TObject);
begin
  ULA.UserUnregister(AForm);
  ULP.UserUnregister(AForm);
  ULB.UserUnregister(AForm);
end;

function TAcqData.CheckCalibrationFilePresent:boolean;
begin
   Result := FileExists(ULM.CalibrationFileName);
end;

procedure TAcqData.FitPeaksInView;
var
  vl: TExpViewLimit;
  ulpr: TULPRObj;
  i: integer;
begin
  GetViewLimit(vl);
  for i := 0 to ULP.ChildCount - 1 do begin
    if ULP.Childs[i].RecID = ULPRID then begin
      ulpr := TULPRObj(ULP.Childs[i]);
      if ulpr.X2 > vl.Max.X then
        vl.Max.X := ulpr.X2;
    end;
  end;
  SetViewLimit(vl);
end;

{v0.77}
{ Calculate number of theoretical plates for given peak. Returns the
  number plus the point at the half height of the peak used of the half width
  calculation. }
function TAcqData.CalcTheorPlates(APeak: TULPRObj; var ph1: TExpPoint; var ph2: TExpPoint): extended;
var
  p: TIndexPoint;
  i: TPointCount;

  hh: TYValue; { half height }
  lp: TExpPoint; { the last scanned point }
begin
  Result := 0;
  SeekPoint(APeak.Index1);{ulprtype}
  hh := APeak.Height / 2;
  lp.X := APeak.X1;
  lp.Y := APeak.Y1;

  ph1.X := 0;
  ph2.X := 0;
  i := GetPointCount;
  repeat
    if not ReadIndexPoint(p) then
      break;
    if p.Index > APeak.Index2 then
      break;
    if sign(p.Point.Y - hh) <> sign(lp.Y - hh) then begin
      if ph1.X = 0 then begin
        if p.Point.X <= APeak.X then
          ph1 := p.Point;
      end else if ph2.X = 0 then begin
        if p.Point.X >= APeak.X then
          ph2 := p.Point;
      end else
        break;
    end;
    lp := p.Point;
    dec(i);
  until i = 0;
  if (ph1.X > 0) and (ph2.X > 0) then begin
    Result := 5.54 * power(APeak.X / (ph2.X - ph1.X), 2);
  end;
end;
{/v0.77}

procedure TAcqData.ProgramBrowse;
var
  o: TULObj;
begin
  if ULA.Find(AAPGID, '', o) then
    o.Browse
  else
    Prg.Obj.Browse;
end;
{/TAcqData.}



{ Initialize data object for non-acquisition purposes }
function CreateOrOpenDataFile(AFileName: string; var AAcqData: TAcqData): boolean;
var
  d: TAcqData;
  ul: TUserViewLimit;
  fn: string;
begin
  {Result := false;}
  AAcqData := nil;
  if (AFileName = '') or (not FileExists(AFileName)) then begin
    fn := AddBackSlash(TemplateDir) + pvUlanDefaultTemplate;
    if not FileExists(fn) then
      fn := AddBackSlash(TemplateDir) + pvPasiveDefaultTemplate;
  end else begin
    fn := AFileName;
  end;
  d := TAcqData.Create(fn, omRead);
  try
    if d.ULI.Duration <> 0 then begin
      ul := NewUserViewLimit;
      d.ULVL.MinX := ul.Min.X;
      d.ULVL.MinY := ul.Min.Y;
      d.ULVL.MaxX := d.ULI.Duration / 60; //min
      d.ULVL.MaxY := ul.Max.Y;
    end;
    if AFileName <> '' then begin
      d.SaveTo(AFileName);
    end;
  except
    d.Free;
  end;
  AAcqData := d;
  Result := true;
end;


end.
