In this post spectral data analysis for the seismometer is presented. First a short explanation is given in order to understand the main concepts of the spectral data analysis. Then the analysis of the seismic data is described. Finally, some information about the the Goertzel algorithm is given.

Spectral data analysis is a kind of analysis, which is based on a special model: the wave model of data. The model assumes that the data is composed of waves. One famous example for a wave model is sound. Sound is the measurement of air pressure, measured e.g. by a microphone and an analog-to-digital converter (ADC). When a 440 Hz tuning fork is struck, it vibrates at 440 Hz and produces a tone (a-tone), which can be measured and the output of the ADC is a data set of a 440 Hz wave. If a tuning fork with 262 Hz is struck, it produces another tone (c-tone), which can be measured as a 262 Hz wave. If both tuning forks are struck at the same time, a sound is measured, which consists of the sum of a 440 Hz wave and a 262 Hz wave. If this data set is used as an input of the spectral data analysis, the output of the spectral data analysis shows two peaks: one at 262 Hz and another one at 440 Hz. The strength of each peak corresponds to the amplitude of the respective wave. The most famous kind of spectral data analysis is the Fourier transform.
The wave model states that individual waves add up to form an overall wave; this addition is called superposition. The overall wave is measured and the measurement results can be broken down into the individual waves using the spectral data analysis.

The same can be done with the seismic data of the seismometer. The measured overall velocity wave can be broken down into the individual velocity waves using the spectral data analysis. The result of the spectral data analysis are for each time value t three values: the frequency f, the amplitude a and the phase angle phi, which contains information about the phase shift of each wave. Many times, the frequency and the amplitude are the main interesting components and the phase angle is neglected.
In the present charts, the frequency and the amplitude are displayed for a time interval of 1 second each. The phase angle is not displayed, but is calculated and could be displayd if needed.
Since 2D charts are used and since three values (t, f, a) are supposed to be displayed, the first value (t) is displayd on the x-axis, the second value (f) is displayed on the y-axis and the third value (a) is displayed as a color. This kind of chart is called a heat map. On the right side of the heat map is a legend, which explains the meaning of the colors: From black (0 %) over red (40 %) to light yellow (100 %). The values are percentage values relative to the maximum amplitudes of the last 4 measurements.
The strongest amplitudes in the chart are in the range between 10 Hz and 15 Hz. There is also a small peak around 50 Hz, which is generated due to the 50 Hz AC voltage of the environment around. At the United States of America, this peak will be around 60 Hz since of the 60 Hz AC frequency.

The spectral data analysis is calculated on the Raspberry Pi almost in real time for a time window of 1 second. The Goertzel algorithm, which is fast and delivers good results, is used for the calculation. The Goertzel algorithm calculates a Fourier transform for a discrete data series. The data is supposed to be an extract from an infinite wave. It should be periodically, i.e. the window length should be an integer multiple of the period length of the signal. Since the period length of the signal can change at any time and since the period length is unknown, the window length is normally never an exact integer multiple of the period length of the signal. In order to solve this problem, a special filter is used for the input signal, which is called a window function (https://en.wikipedia.org/wiki/Window_function). This filter dampens the beginning and the end of the input signal and transforms the input signal into a periodical signal as the beginning and the end of the signal are 0. There are also other points to consider, e.g. the mean of the function should be zero and the window length should be considerably longer than a single period length, which is true in this case fortunately.
The software for the Raspberry Pi contains 5 different window functions that can be tested via the corresponding edit field. To calculate one column of the spectrum diagram, the Goertzel algorithm is executed 100 times for the frequencies between 1 Hz and 100 Hz respectively.

 

Spectral data analysis

Goertzel Project

The files main_unit.pas, main_unit.frm and project_defines.inc contain the source code for measuring and analysing the data. Again project_defines.inc offer some project options.

The file go.sh will compile and run the program.

The software for the day data project can be downloaded here and be copied to any directory. The choosen path of the used directory needs to be adjusted in go.sh.

main_unit.pas

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Main_Unit                                                                         ## }
{ ##                                                                                   ## }
{ ## Main form                                                                         ## }
{ ##                                                                                   ## }
{ ## Copyright (C) 2018-2020  : Dr. Jürgen Abel                                        ## }
{ ## Email                    : juergen@juergen-abel.info                              ## }
{ ## Internet                 : www.seismometer.info                                   ## }
{ ##                                                                                   ## }
{ ## This program is free software: you can redistribute it and/or modify              ## }
{ ## it under the terms of the GNU Lesser General Public License as published by       ## }
{ ## the Free Software Foundation, either version 3 of the License, or                 ## }
{ ## (at your option) any later version with the following modification:               ## }
{ ##                                                                                   ## }
{ ## As a special exception, the copyright holders of this library give you            ## }
{ ## permission to link this library with independent modules to produce an            ## }
{ ## executable, regardless of the license terms of these independent modules, and     ## }
{ ## to copy and distribute the resulting executable under terms of your choice,       ## }
{ ## provided that you also meet, for each linked independent module, the terms        ## }
{ ## and conditions of the license of that module. An independent module is a          ## }
{ ## module which is not derived from or based on this library. If you modify          ## }
{ ## this library, you may extend this exception to your version of the library,       ## }
{ ## but you are not obligated to do so. If you do not wish to do so, delete this      ## }
{ ## exception statement from your version.                                            ## }
{ ##                                                                                   ## }
{ ## This program is distributed in the hope that it will be useful,                   ## }
{ ## but WITHOUT ANY WARRANTY; without even the implied warranty of                    ## }
{ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                     ## }
{ ## GNU General Public License for more details.                                      ## }
{ ##                                                                                   ## }
{ ## You should have received a copy of the GNU Lesser General Public License          ## }
{ ## COPYING.LGPL.txt along with this program.                                         ## }
{ ## If not, see https://www.gnu.org/licenses/.                                        ## }
{ ##                                                                                   ## }
{ ####################################################################################### }


Unit Main_Unit;

{$mode objfpc}{$H+}

{$INCLUDE project_defines.inc}

Interface

Uses
  CThreads,
  Classes,
  SysUtils,
  Forms,
  Controls,
  Graphics,
  Dialogs,
  StdCtrls,
  ExtCtrls,
  ComCtrls,
  TAGraph,
  TASeries,
  ADS1262_Unit,
  TACustomSeries,
  TAIntervalSources,
  TATools,
  Types,
  TACustomSource,
  TASources,
  TAChartExtentLink,
  TAFuncSeries,
  Process,
  LCLType,
  LCLIntf,
  SpinEx,
  TALegend,
  TADrawUtils;

Const
  ADS1262_MODE2_GAIN_01_LIMIT    = 2.5;
  ADS1262_MODE2_GAIN_02_LIMIT    = ADS1262_MODE2_GAIN_01_LIMIT / 2;
  ADS1262_MODE2_GAIN_04_LIMIT    = ADS1262_MODE2_GAIN_01_LIMIT / 4;
  ADS1262_MODE2_GAIN_08_LIMIT    = ADS1262_MODE2_GAIN_01_LIMIT / 8;
  ADS1262_MODE2_GAIN_16_LIMIT    = ADS1262_MODE2_GAIN_01_LIMIT / 16;
  ADS1262_MODE2_GAIN_32_LIMIT    = ADS1262_MODE2_GAIN_01_LIMIT / 32;
  ADS1262_SAMPLE_RATE            = 400;
  ADS1262_INTERRUPT_TIMEOUT_MSEC = 1000;
  {$IFDEF USE_INTERRUPT}
  {$ELSE}
  DATA_INTERVAL_MS               = 2;
  {$ENDIF}
  LP_SPAN_INIT                   = 20;
  LP_SLOW_SPAN                   = 4000;
  LP_SLOW_SETTLE_FACTOR          = 4;
  LP_INTEGRAL_SPAN               = 800;
  LP_INTEGRAL_CORRECTION_DIVIDER = 8000;
  DATA_ARRAY_SIZE                = 1024 * 1024;
  DATA_CHART_EXTEND_Y            = 0.02;
  DATA_CHART_EXTEND_Y_MAX        = 2.5;
  DATA_CHART_LABEL_SIZE          = 120;
  DATA_CHART_UNIT_FORMAT         = '%0:.9g mm/s';
  TIME_UNIT_FORMAT               = '%0:.9g mm/s';
  VALUES_PER_HOUR                = 60 * 60;
  RMS_COLOR                      = $2222AA;
  RMS_BAR_WIDTH_PERCENT          = 100;
  DATA_COLOR                     = $FF4444;
  DATA_PIN_WIDTH                 = 3;
  POSITION_COLOR                 = $66EE66;
  POSITION_WIDTH                 = 11;
  CURRENT_IMAGE_FILE_NAME        = 'images/current_image.jpg';
  DAILY_IMAGE_FILE_NAME          = 'images/daily_image.jpg';
  HOUR_CHART_N                   = 24;
  HOUR_CHART_FONT_SIZE           = 6;
  STATION_NAME                   = 'SEISMOMETER_NORF_GERMANY';
  CURRENT_TIME_FORMAT_00_S       = 'Live Data  -  Station Name: ' + STATION_NAME + '  -  Date: ';
  CURRENT_TIME_FORMAT_01_S       = '  -  Time: ';
  SQR_2                          = sqrt (2);
  BIT_N_RANGE                    = 100;
  VOLTAGE_LIMIT                  = ADS1262_MODE2_GAIN_32_LIMIT;
  SM24_VELOCITY_FACTOR           = 20.9;
  DELAY_SHOW_TIME_MSEC           = 2000;
  HALT_TIME_MSEC                 = 60000;
  FORM_LEFT                      = 10;
  FORM_TOP                       = 10;
  FORM_WIDTH                     = 1920;
  FORM_HEIGHT                    = 1038;
  CLOSE_WAIT_TIME_MSEC           = 1000;
  RANDOM_N                       = 400;
  RANDOM_MAX                     = 4 * 1024 * 1024;
  SPECTRUM_MAX_FREQUENCY         = 100;
  SPECTRUM_Y_RESOLUTION          = 100;
  SPECTRUM_DATETIME_OFFSET       = 43800;
  SPECTRUM_SAMPLE_N              = 400;
  SPECTRUM_SAMPLE_RATE           = 400;
  SPECTRUM_X_ARRAY_SIZE          = SPECTRUM_SAMPLE_N;
  SPECTRUM_MAXIMUM_SPAN          = 4;
  SPECTRUM_CHART_UNIT_FORMAT     = '%0:.9g Hz';

Type
  T_Data_X_A                 = Array [0 .. DATA_ARRAY_SIZE - 1] Of TDateTime;
  T_Data_Y_A                 = Array [0 .. DATA_ARRAY_SIZE - 1] Of Int_32;
  T_Hour_Chart_A             = Array [0 .. HOUR_CHART_N - 1] Of TChart;
  T_Hour_RMS_BS_A            = Array [0 .. HOUR_CHART_N - 1] Of TBarSeries;
  T_Hour_Data_LS_A           = Array [0 .. HOUR_CHART_N - 1] Of TLineSeries;
  T_Hour_Position_CL_A       = Array [0 .. HOUR_CHART_N - 1] Of TConstantLine;
  T_Chart_Kind               = (CURRENT_CHART_CK, DAILY_CHART_CK);
  T_Spectrum_A               = Array [0 .. SPECTRUM_X_ARRAY_SIZE - 1, 0 .. SPECTRUM_Y_RESOLUTION] Of Double;
  T_Spectrum_Maximum_A       = Array [0 .. SPECTRUM_X_ARRAY_SIZE - 1] Of Double;
  T_Spectrum_Input_A         = Array Of Double;
  T_Spectrum_Window_Funktion = (TWF_None, TWF_von_Hahn, TWF_Hamming, TWF_Blackman, TWF_KaiserBessel, TWF_FlatTop);

  { ####################################################################################### }
  { ## T_Timer_Thread                                                                    ## }
  { ####################################################################################### }
  T_Timer_Thread = Class(TThread)
  Private
      M_ADS1262 :              T_ADS1262;
      M_Data_X_A :             T_Data_X_A;
      M_Data_Y_A :             T_Data_Y_A;
      M_Data_LP_Y_A :          T_Data_Y_A;
      M_Data_Integral_Y_A :    T_Data_Y_A;
      M_Data_LP_Integral_Y_A : T_Data_Y_A;
      M_Data_A_Index :         Integer;
      M_Count :                Int_64;
      M_LP_Span :              Integer;
  Public
      Property Data_X_A : T_Data_X_A Read M_Data_X_A;
      Property Data_Y_A : T_Data_Y_A Read M_Data_Y_A;
      Property Data_LP_Y_A : T_Data_Y_A Read M_Data_LP_Y_A;
      Property Data_Integral_Y_A : T_Data_Y_A Read M_Data_Integral_Y_A;
      Property Data_LP_Integral_Y_A : T_Data_Y_A Read M_Data_LP_Integral_Y_A;
      Property Data_A_Index : Integer Read M_Data_A_Index;
      Property Count : Int_64 Read M_Count;
      Property ADS1262 : T_ADS1262 Read M_ADS1262;
      Property LP_Span : Integer Read M_LP_Span Write M_LP_Span;
      Constructor Create (F_Suspended : Boolean);
      Destructor Destroy; Override;
      Procedure Execute; Override;
  End; { T_Timer_Thread }

  { ####################################################################################### }
  { ## TMain_F                                                                           ## }
  { ####################################################################################### }

  { TMain_F }

  TMain_F = Class(TForm)
      Chart_00_11_P :               TPanel;
      Chart_12_23_P :               TPanel;
      Chart_TS :                    TChartToolset;
      Chart_TS_ZoomDragTool :       TZoomDragTool;
      Chart_TS_DataPointHintTool :  TDataPointHintTool;
      Chart_TS_PanDragTool :        TPanDragTool;
      Chart_TS_ZoomMouseWheelTool : TZoomMouseWheelTool;
      Close_B :                     TButton;
      Current_Chart_C :             TChart;
      Current_Integral_LP_LS :      TLineSeries;
      Current_Integral_LS :         TLineSeries;
      Current_CL :                  TConstantLine;
      Current_EMA_LS :              TLineSeries;
      Current_LP_LS :               TLineSeries;
      Current_LS :                  TLineSeries;
      Current_RMS_LS :              TLineSeries;
      Daily_Chart_P :               TPanel;
      Data_Splitter :               TSplitter;
      Hour_00_11_P :                TPanel;
      Hour_0_C :                    TChart;
      Hour_0_Data_LS :              TLineSeries;
      Hour_0_Position_CL :          TConstantLine;
      Hour_0_RMS_BS :               TBarSeries;
      Hour_10_C :                   TChart;
      Hour_10_Data_LS :             TLineSeries;
      Hour_10_Position_CL :         TConstantLine;
      Hour_10_RMS_BS :              TBarSeries;
      Hour_11_C :                   TChart;
      Hour_11_Data_LS :             TLineSeries;
      Hour_11_Position_CL :         TConstantLine;
      Hour_11_RMS_BS :              TBarSeries;
      Hour_12_23_P :                TPanel;
      Hour_12_C :                   TChart;
      Hour_12_Data_LS :             TLineSeries;
      Hour_12_Position_CL :         TConstantLine;
      Hour_12_RMS_BS :              TBarSeries;
      Hour_13_C :                   TChart;
      Hour_13_Data_LS :             TLineSeries;
      Hour_13_Position_CL :         TConstantLine;
      Hour_13_RMS_BS :              TBarSeries;
      Hour_14_C :                   TChart;
      Hour_14_Data_LS :             TLineSeries;
      Hour_14_Position_CL :         TConstantLine;
      Hour_14_RMS_BS :              TBarSeries;
      Hour_15_C :                   TChart;
      Hour_15_Data_LS :             TLineSeries;
      Hour_15_Position_CL :         TConstantLine;
      Hour_15_RMS_BS :              TBarSeries;
      Hour_16_C :                   TChart;
      Hour_16_Data_LS :             TLineSeries;
      Hour_16_Position_CL :         TConstantLine;
      Hour_16_RMS_BS :              TBarSeries;
      Hour_17_C :                   TChart;
      Hour_17_Data_LS :             TLineSeries;
      Hour_17_Position_CL :         TConstantLine;
      Hour_17_RMS_BS :              TBarSeries;
      Hour_18_C :                   TChart;
      Hour_18_Data_LS :             TLineSeries;
      Hour_18_Position_CL :         TConstantLine;
      Hour_18_RMS_BS :              TBarSeries;
      Hour_19_C :                   TChart;
      Hour_19_Data_LS :             TLineSeries;
      Hour_19_Position_CL :         TConstantLine;
      Hour_19_RMS_BS :              TBarSeries;
      Hour_1_C :                    TChart;
      Hour_1_Data_LS :              TLineSeries;
      Hour_1_Position_CL :          TConstantLine;
      Hour_1_RMS_BS :               TBarSeries;
      Hour_20_C :                   TChart;
      Hour_20_Data_LS :             TLineSeries;
      Hour_20_Position_CL :         TConstantLine;
      Hour_20_RMS_BS :              TBarSeries;
      Hour_21_C :                   TChart;
      Hour_21_Data_LS :             TLineSeries;
      Hour_21_Position_CL :         TConstantLine;
      Hour_21_RMS_BS :              TBarSeries;
      Hour_22_C :                   TChart;
      Hour_22_Data_LS :             TLineSeries;
      Hour_22_Position_CL :         TConstantLine;
      Hour_22_RMS_BS :              TBarSeries;
      Hour_23_C :                   TChart;
      Hour_23_Data_LS :             TLineSeries;
      Hour_23_Position_CL :         TConstantLine;
      Hour_23_RMS_BS :              TBarSeries;
      Hour_2_C :                    TChart;
      Hour_2_Data_LS :              TLineSeries;
      Hour_2_Position_CL :          TConstantLine;
      Hour_2_RMS_BS :               TBarSeries;
      Hour_3_C :                    TChart;
      Hour_3_Data_LS :              TLineSeries;
      Hour_3_Position_CL :          TConstantLine;
      Hour_3_RMS_BS :               TBarSeries;
      Hour_4_C :                    TChart;
      Hour_4_Data_LS :              TLineSeries;
      Hour_4_Position_CL :          TConstantLine;
      Hour_4_RMS_BS :               TBarSeries;
      Hour_5_C :                    TChart;
      Hour_5_Data_LS :              TLineSeries;
      Hour_5_Position_CL :          TConstantLine;
      Hour_5_RMS_BS :               TBarSeries;
      Hour_6_C :                    TChart;
      Hour_6_Data_LS :              TLineSeries;
      Hour_6_Position_CL :          TConstantLine;
      Hour_6_RMS_BS :               TBarSeries;
      Hour_7_C :                    TChart;
      Hour_7_Data_LS :              TLineSeries;
      Hour_7_Position_CL :          TConstantLine;
      Hour_7_RMS_BS :               TBarSeries;
      Hour_8_C :                    TChart;
      Hour_8_Data_LS :              TLineSeries;
      Hour_8_Position_CL :          TConstantLine;
      Hour_8_RMS_BS :               TBarSeries;
      Hour_9_C :                    TChart;
      Hour_9_Data_LS :              TLineSeries;
      Hour_9_Position_CL :          TConstantLine;
      Hour_9_RMS_BS :               TBarSeries;
      Input_LP_Span_E :             TEdit;
      Input_LP_Span_L :             TLabel;
      Input_OFCAL_Value_E :         TEdit;
      Input_OFCAL_Value_L :         TLabel;
      Input_Sensivity_Bits_E :      TEdit;
      Input_Sensivity_Bits_L :      TLabel;
      Input_Window_Function_L :  TLabel;
      Input_Window_Function_SE : TSpinEditEx;
      Input_X_Axis_Extend_E :       TEdit;
      Input_Timer_Sec_E :           TEdit;
      Input_X_Axis_Extend_L :       TLabel;
      Input_Timer_Sec_L :           TLabel;
      Label_0 :                     TLabel;
      Label_1 :                     TLabel;
      Label_10 :                    TLabel;
      Label_11 :                    TLabel;
      Label_12 :                    TLabel;
      Label_13 :                    TLabel;
      Label_14 :                    TLabel;
      Label_15 :                    TLabel;
      Label_16 :                    TLabel;
      Label_17 :                    TLabel;
      Label_18 :                    TLabel;
      Label_19 :                    TLabel;
      Label_2 :                     TLabel;
      Label_20 :                    TLabel;
      Label_21 :                    TLabel;
      Label_22 :                    TLabel;
      Label_23 :                    TLabel;
      Label_3 :                     TLabel;
      Label_4 :                     TLabel;
      Label_5 :                     TLabel;
      Label_6 :                     TLabel;
      Label_7 :                     TLabel;
      Label_8 :                     TLabel;
      Label_9 :                     TLabel;
      Maint_00_11_P :               TPanel;
      Maint_12_23_P :               TPanel;
      Main_PC :                     TPageControl;
      Margin_00_11_P :              TPanel;
      Margin_12_23_P :              TPanel;
      Current_Chart_P :             TPanel;
      Output_1_P :                  TPanel;
      Red_Yellow_White_CS :         TListChartSource;
      Main_P :                      TPanel;
      Output_2_P :                  TPanel;
      Output_3_P :                  TPanel;
      Output_Bits_E :               TEdit;
      Output_Bits_L :               TLabel;
      Output_Counter_E :            TEdit;
      Output_Counter_L :            TLabel;
      Output_Freq_E :               TEdit;
      Output_Freq_L :               TLabel;
      Output_NPS_E :                TEdit;
      Output_NPS_L :                TLabel;
      Reset_B :                     TButton;
      Show_Integral_CB :            TCheckBox;
      Show_LP_Integral_CB :         TCheckBox;
      Show_Integral_L :             TLabel;
      Show_LP_Integral_L :          TLabel;
      Show_Spektrum_CB :            TCheckBox;
      Show_Spektrum_L :             TLabel;
      Spectrum_CEL :                TChartExtentLink;
      Spectrum_Chart_C :            TChart;
      Spectrum_CMS :                TColorMapSeries;
      Status_L :                    TLabel;
      Status_P :                    TPanel;
      Show_Current_Data_CB :        TCheckBox;
      Show_Data_CB :                TCheckBox;
      Show_EMA_CB :                 TCheckBox;
      Show_RMS_CB :                 TCheckBox;
      Show_EMA_L :                  TLabel;
      Show_RMS_L :                  TLabel;
      Show_LP_Data_CB :             TCheckBox;
      Show_Current_Data_L :         TLabel;
      Show_Data_L :                 TLabel;
      Show_LP_Data_L :              TLabel;
      Start_Stop_B :                TButton;
      Current_Chart_TS :            TTabSheet;
      Daily_Chart_TS :              TTabSheet;
      Daily_Chart_0_P :             TPanel;
      Time_CS :                     TDateTimeIntervalChartSource;
      Output_0_P :                  TPanel;
      Chart_T :                     TTimer;
      Procedure Chart_TS_DataPointHintToolHint (ATool : TDataPointHintTool; Const APoint : TPoint; Var AHint : String);
      Procedure Chart_TTimer (Sender : TObject);
      Procedure Close_BClick (Sender : TObject);
      Procedure FormCreate (Sender : TObject);
      Procedure FormDestroy (Sender : TObject);
      Procedure FormShow (Sender : TObject);
      Procedure Input_LP_Span_EEditingDone (Sender : TObject);
      Procedure Input_OFCAL_Value_EEditingDone (Sender : TObject);
      Procedure Input_Sensivity_Bits_EEditingDone (Sender : TObject);
      Procedure Input_Timer_Sec_EEditingDone (Sender : TObject);
      Procedure Input_X_Axis_Extend_EEditingDone (Sender : TObject);
      Procedure Reset_BClick (Sender : TObject);
      Procedure Spectrum_CMSCalculate (Const AX, AY : Double; out AZ : Double);
      Procedure Start_Stop_BClick (Sender : TObject);
  Protected
      M_Data_Start_Index :        Integer;
      M_Data_End_Index :          Integer;
      M_Data_A_Size :             Integer;
      M_Transfer_Process :        TProcess;
      M_Timer_Thread :            T_Timer_Thread;
      M_Data_A_Index_0 :          Integer;
      M_Data_A_Index_1 :          Integer;
      M_Start_Time :              TDateTime;
      M_End_Time :                TDateTime;
      M_Hour_Chart_A :            T_Hour_Chart_A;
      M_Hour_RMS_BS_A :           T_Hour_RMS_BS_A;
      M_Hour_Data_LS_A :          T_Hour_Data_LS_A;
      M_Hour_Position_CL_A :      T_Hour_Position_CL_A;
      M_Old_Data_Time :           TDateTime;
      M_Old_Data_Hour :           Integer;
      M_Old_Data_Minute :         Integer;
      M_Old_Data_Second :         Integer;
      M_Last_Saved_Minute :       Integer;
      M_Count :                   Int_64;
      M_Spectrum_A :              T_Spectrum_A;
      M_Spectrum_Maximum_A :      T_Spectrum_Maximum_A;
      M_Period_Range :            Integer;
      M_Frequency :               Double;
      M_Window_Size :             Integer;
      M_Windowing_Function :      T_Spectrum_Window_Funktion;
      M_Sensivity :               Integer;
      M_Goertzel_Window_Divider : Integer;
      M_DateTime_Start_Offset :   Integer;
      Procedure Debug_Output (F_Text : String);
      Function Calculate_Velocity (F_Data : Int_32) : Double;
      Procedure Read_Array_Data (F_Index : Integer; Var F_X : TDateTime; Var F_Y : Int_32; Var F_LP_Y : Int_32);
      Procedure Output_Current_Time;
      Procedure Output_Current_Data (F_Measurement_N : Integer; F_Zero_Crossings_N : Integer; F_End_Time : TDateTime);
      Procedure Clear_Hour_Data_LS (F_Hour_Data_LS : TLineSeries);
      Procedure Clear_Hour_RMS_BS (F_Hour_RMS_BS : TBarSeries);
      Function Get_Time_Index (F_Time_Value : TDateTime) : Integer;
      Function Get_Spectrum (F_X : TDateTime; F_Y : Integer) : Double;
      Procedure Set_Spectrum (F_X : TDateTime; F_Y : Integer; F_Value : Double);
      Procedure Set_Amplitude (F_X : Integer; F_Y : Integer; F_Value : Double);
      Procedure Set_Phi (F_X : Integer; F_Y : Integer; F_Value : Double);
      Procedure Calculate_Goertzel_DFT (F_End_Index : Integer; F_End_Time : TDateTime);
  Public
      {$IFDEF USE_CRITICAL_SECTION}
      M_Critical_Section : TCriticalSection;
      {$ENDIF}
      Property Count : Int_64 Read M_Count;
  End; { TMain_F }

Var
  Main_F : TMain_F;

Implementation

{$R *.frm}

Uses
  Math,
  DateUtils,
  LazFileUtils,
  TAChartUtils;


{#####################################################################################}
{ Utilities                                                                           }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Function Validate_Array_Index (F_Index : Integer; F_Array_Size : Integer) : Integer;
  { Validate index into array                                                               }
  { --------------------------------------------------------------------------------------- }
Begin { Validate_Array_Index }
  If F_Index >= 0 Then
    Begin { then }
      Result := F_Index mod F_Array_Size;
    End { then }
  Else
    Begin { else  }
      Result := ((F_Index mod F_Array_Size) + F_Array_Size) mod F_Array_Size;
    End; { else  }
End; { Validate_Array_Index }


{ ####################################################################################### }
{ ## T_Timer_Thread                                                                    ## }
{ ####################################################################################### }

{ --------------------------------------------------------------------------------------- }
Constructor T_Timer_Thread.Create (F_Suspended : Boolean);
  { Create timer thread                                                                     }
  { --------------------------------------------------------------------------------------- }
{$IFDEF DEBUG_ERROR}
Var
  Register_Block : T_Register_Block;
{$ENDIF}
Begin { T_Timer_Thread.Create }
  Inherited Create (F_Suspended);

  FreeOnTerminate := FALSE;

  M_ADS1262 := T_ADS1262.Create ();
  M_ADS1262.Set_Continuous_Conversion ();
  M_ADS1262.Start_Conversion ();
  Sleep (ADS1262_WAIT_TIME_MS_COMMAND);

  {$IFDEF DEBUG_ERROR}
  M_ADS1262.Read_All_Registers (Register_Block);
  {$ENDIF}

  FillByte (M_Data_X_A [0], DATA_ARRAY_SIZE * SizeOf (TDateTime), $00);
  FillByte (M_Data_Y_A [0], DATA_ARRAY_SIZE * SizeOf (Int_32), $00);
  FillByte (M_Data_LP_Y_A [0], DATA_ARRAY_SIZE * SizeOf (Int_32), $00);
  FillByte (M_Data_Integral_Y_A [0], DATA_ARRAY_SIZE * SizeOf (Int_32), $00);
  FillByte (M_Data_LP_Integral_Y_A [0], DATA_ARRAY_SIZE * SizeOf (Int_32), $00);

  M_Data_A_Index := 0;
  M_Count        := 0;
  M_LP_Span      := LP_SPAN_INIT;
End; { T_Timer_Thread.Create }


{ --------------------------------------------------------------------------------------- }
Destructor T_Timer_Thread.Destroy;
  { Free data                                                                               }
  { --------------------------------------------------------------------------------------- }
Begin { T_Timer_Thread.Destroy }
  M_ADS1262.Free;

  Inherited Destroy;
End; { T_Timer_Thread.Destroy }


{ --------------------------------------------------------------------------------------- }
Procedure T_Timer_Thread.Execute;
{ Execute thread                                                                          }
{ --------------------------------------------------------------------------------------- }
Var
  A_1 :             Double;
  B_1 :             Double;
  C_1 :             Double;
  C_2 :             Double;
  C_3 :             Double;
  A_1_LP_Integral : Double;
  B_1_LP_Integral : Double;
  C_1_LP_Integral : Double;
  C_2_LP_Integral : Double;
  C_3_LP_Integral : Double;

Begin { T_Timer_Thread.Execute }
  While (Terminated = FALSE) Do
    Begin { While }
      {$IFDEF USE_INTERRUPT}
      M_ADS1262.DRDY_Pin.Wait_For_Interrupt (ADS1262_INTERRUPT_TIMEOUT_MSEC);
      {$ELSE}
      Sleep (DATA_INTERVAL_MS);
      {$ENDIF}

      {$IFDEF USE_CRITICAL_SECTION}
      EnterCriticalSection (Main_F.M_Critical_Section);
        Try
      {$ENDIF}
          M_Data_X_A[M_Data_A_Index] := Now;
          M_Data_Y_A[M_Data_A_Index] := M_ADS1262.ADC1_Data;

          A_1                           := exp (-SQR_2 * PI / Round (M_LP_Span));
          B_1                           := 2 * A_1 * cos (SQR_2 * PI / Round (M_LP_Span));
          C_2                           := B_1;
          C_3                           := -A_1 * A_1;
          C_1                           := 1 - C_2 - C_3;
          M_Data_LP_Y_A[M_Data_A_Index] := Round ((C_1 * Round (M_Data_Y_A [M_Data_A_Index])) + (C_2 * Round (M_Data_LP_Y_A [Validate_Array_Index (M_Data_A_Index - 1, DATA_ARRAY_SIZE)])) + (C_3 * Round (M_Data_LP_Y_A [Validate_Array_Index (M_Data_A_Index - 2, DATA_ARRAY_SIZE)])));

          A_1_LP_Integral := exp (-SQR_2 * PI / Round (LP_INTEGRAL_SPAN));
          B_1_LP_Integral := 2 * A_1_LP_Integral * cos (SQR_2 * PI / Round (LP_INTEGRAL_SPAN));
          C_2_LP_Integral := B_1_LP_Integral;
          C_3_LP_Integral := -A_1_LP_Integral * A_1_LP_Integral;
          C_1_LP_Integral := 1 - C_2_LP_Integral - C_3_LP_Integral;

          If M_Count > LP_SLOW_SETTLE_FACTOR * LP_SLOW_SPAN Then
            Begin { then }
              //              M_Data_Integral_Y_A[M_Data_A_Index]    := M_Data_Integral_Y_A [Validate_Array_Index (M_Data_A_Index - 1, DATA_ARRAY_SIZE)] + M_Data_Y_A [M_Data_A_Index] - M_Data_LP_Y_A[M_Data_A_Index] - Round (M_Data_LP_Integral_Y_A [Validate_Array_Index (M_Data_A_Index - 1, DATA_ARRAY_SIZE)] / LP_INTEGRAL_CORRECTION_DIVIDER);



              M_Data_Integral_Y_A[M_Data_A_Index] := M_Data_Integral_Y_A [Validate_Array_Index (M_Data_A_Index - 1, DATA_ARRAY_SIZE)] + M_Data_Y_A [M_Data_A_Index] - Round (M_Data_LP_Integral_Y_A [Validate_Array_Index (M_Data_A_Index - 1, DATA_ARRAY_SIZE)] / LP_INTEGRAL_CORRECTION_DIVIDER);




              M_Data_LP_Integral_Y_A[M_Data_A_Index] := Round ((C_1_LP_Integral * Round (M_Data_Integral_Y_A [M_Data_A_Index])) + (C_2_LP_Integral * Round (M_Data_LP_Integral_Y_A [Validate_Array_Index (M_Data_A_Index - 1, DATA_ARRAY_SIZE)])) + (C_3_LP_Integral * Round (M_Data_LP_Integral_Y_A [Validate_Array_Index (M_Data_A_Index - 2, DATA_ARRAY_SIZE)])));
            End { then }
          Else
            Begin { else  }
              M_Data_Integral_Y_A[M_Data_A_Index]    := 0;
              M_Data_LP_Integral_Y_A[M_Data_A_Index] := 0;
            End; { else  }

          Inc (M_Count);
          M_Data_A_Index := Validate_Array_Index (M_Data_A_Index + 1, DATA_ARRAY_SIZE);
      {$IFDEF USE_CRITICAL_SECTION}
        Finally
          LeaveCriticalSection (Main_F.M_Critical_Section);
        End;
      {$ENDIF}
    End; { While }
End; { T_Timer_Thread.Execute }


{ ####################################################################################### }
{ ## TMain_F                                                                           ## }
{ ####################################################################################### }

{#####################################################################################}
{ Build form                                                                          }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.FormCreate (Sender : TObject);
{ Create main from                                                                        }
{ --------------------------------------------------------------------------------------- }
Var
  I :                Integer;
  Hour_Chart :       TChart;
  Hour_RMS_BS :      TBarSeries;
  Hour_Data_LS :     TLineSeries;
  Hour_Position_CL : TConstantLine;

Begin { TMain_F.FormCreate }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.FormCreate');
  {$ENDIF}

  { Create CriticalSection }
  {$IFDEF USE_CRITICAL_SECTION}
  InitializeCriticalSection (M_Critical_Section);
  {$ENDIF}

  { Initialize arrays }
  FillByte (M_Spectrum_A [0, 0], SPECTRUM_X_ARRAY_SIZE * SizeOf (Double) * (SPECTRUM_Y_RESOLUTION + 1), $00);
  FillByte (M_Spectrum_Maximum_A [0], SPECTRUM_X_ARRAY_SIZE * SizeOf (Double), $00);

  { Initialize daily chart }
  M_Hour_Chart_A[0]  := Hour_0_C;
  M_Hour_Chart_A[1]  := Hour_1_C;
  M_Hour_Chart_A[2]  := Hour_2_C;
  M_Hour_Chart_A[3]  := Hour_3_C;
  M_Hour_Chart_A[4]  := Hour_4_C;
  M_Hour_Chart_A[5]  := Hour_5_C;
  M_Hour_Chart_A[6]  := Hour_6_C;
  M_Hour_Chart_A[7]  := Hour_7_C;
  M_Hour_Chart_A[8]  := Hour_8_C;
  M_Hour_Chart_A[9]  := Hour_9_C;
  M_Hour_Chart_A[10] := Hour_10_C;
  M_Hour_Chart_A[11] := Hour_11_C;
  M_Hour_Chart_A[12] := Hour_12_C;
  M_Hour_Chart_A[13] := Hour_13_C;
  M_Hour_Chart_A[14] := Hour_14_C;
  M_Hour_Chart_A[15] := Hour_15_C;
  M_Hour_Chart_A[16] := Hour_16_C;
  M_Hour_Chart_A[17] := Hour_17_C;
  M_Hour_Chart_A[18] := Hour_18_C;
  M_Hour_Chart_A[19] := Hour_19_C;
  M_Hour_Chart_A[20] := Hour_20_C;
  M_Hour_Chart_A[21] := Hour_21_C;
  M_Hour_Chart_A[22] := Hour_22_C;
  M_Hour_Chart_A[23] := Hour_23_C;

  M_Hour_RMS_BS_A[0]  := Hour_0_RMS_BS;
  M_Hour_RMS_BS_A[1]  := Hour_1_RMS_BS;
  M_Hour_RMS_BS_A[2]  := Hour_2_RMS_BS;
  M_Hour_RMS_BS_A[3]  := Hour_3_RMS_BS;
  M_Hour_RMS_BS_A[4]  := Hour_4_RMS_BS;
  M_Hour_RMS_BS_A[5]  := Hour_5_RMS_BS;
  M_Hour_RMS_BS_A[6]  := Hour_6_RMS_BS;
  M_Hour_RMS_BS_A[7]  := Hour_7_RMS_BS;
  M_Hour_RMS_BS_A[8]  := Hour_8_RMS_BS;
  M_Hour_RMS_BS_A[9]  := Hour_9_RMS_BS;
  M_Hour_RMS_BS_A[10] := Hour_10_RMS_BS;
  M_Hour_RMS_BS_A[11] := Hour_11_RMS_BS;
  M_Hour_RMS_BS_A[12] := Hour_12_RMS_BS;
  M_Hour_RMS_BS_A[13] := Hour_13_RMS_BS;
  M_Hour_RMS_BS_A[14] := Hour_14_RMS_BS;
  M_Hour_RMS_BS_A[15] := Hour_15_RMS_BS;
  M_Hour_RMS_BS_A[16] := Hour_16_RMS_BS;
  M_Hour_RMS_BS_A[17] := Hour_17_RMS_BS;
  M_Hour_RMS_BS_A[18] := Hour_18_RMS_BS;
  M_Hour_RMS_BS_A[19] := Hour_19_RMS_BS;
  M_Hour_RMS_BS_A[20] := Hour_20_RMS_BS;
  M_Hour_RMS_BS_A[21] := Hour_21_RMS_BS;
  M_Hour_RMS_BS_A[22] := Hour_22_RMS_BS;
  M_Hour_RMS_BS_A[23] := Hour_23_RMS_BS;

  M_Hour_Data_LS_A[0]  := Hour_0_Data_LS;
  M_Hour_Data_LS_A[1]  := Hour_1_Data_LS;
  M_Hour_Data_LS_A[2]  := Hour_2_Data_LS;
  M_Hour_Data_LS_A[3]  := Hour_3_Data_LS;
  M_Hour_Data_LS_A[4]  := Hour_4_Data_LS;
  M_Hour_Data_LS_A[5]  := Hour_5_Data_LS;
  M_Hour_Data_LS_A[6]  := Hour_6_Data_LS;
  M_Hour_Data_LS_A[7]  := Hour_7_Data_LS;
  M_Hour_Data_LS_A[8]  := Hour_8_Data_LS;
  M_Hour_Data_LS_A[9]  := Hour_9_Data_LS;
  M_Hour_Data_LS_A[10] := Hour_10_Data_LS;
  M_Hour_Data_LS_A[11] := Hour_11_Data_LS;
  M_Hour_Data_LS_A[12] := Hour_12_Data_LS;
  M_Hour_Data_LS_A[13] := Hour_13_Data_LS;
  M_Hour_Data_LS_A[14] := Hour_14_Data_LS;
  M_Hour_Data_LS_A[15] := Hour_15_Data_LS;
  M_Hour_Data_LS_A[16] := Hour_16_Data_LS;
  M_Hour_Data_LS_A[17] := Hour_17_Data_LS;
  M_Hour_Data_LS_A[18] := Hour_18_Data_LS;
  M_Hour_Data_LS_A[19] := Hour_19_Data_LS;
  M_Hour_Data_LS_A[20] := Hour_20_Data_LS;
  M_Hour_Data_LS_A[21] := Hour_21_Data_LS;
  M_Hour_Data_LS_A[22] := Hour_22_Data_LS;
  M_Hour_Data_LS_A[23] := Hour_23_Data_LS;

  M_Hour_Position_CL_A[0]  := Hour_0_Position_CL;
  M_Hour_Position_CL_A[1]  := Hour_1_Position_CL;
  M_Hour_Position_CL_A[2]  := Hour_2_Position_CL;
  M_Hour_Position_CL_A[3]  := Hour_3_Position_CL;
  M_Hour_Position_CL_A[4]  := Hour_4_Position_CL;
  M_Hour_Position_CL_A[5]  := Hour_5_Position_CL;
  M_Hour_Position_CL_A[6]  := Hour_6_Position_CL;
  M_Hour_Position_CL_A[7]  := Hour_7_Position_CL;
  M_Hour_Position_CL_A[8]  := Hour_8_Position_CL;
  M_Hour_Position_CL_A[9]  := Hour_9_Position_CL;
  M_Hour_Position_CL_A[10] := Hour_10_Position_CL;
  M_Hour_Position_CL_A[11] := Hour_11_Position_CL;
  M_Hour_Position_CL_A[12] := Hour_12_Position_CL;
  M_Hour_Position_CL_A[13] := Hour_13_Position_CL;
  M_Hour_Position_CL_A[14] := Hour_14_Position_CL;
  M_Hour_Position_CL_A[15] := Hour_15_Position_CL;
  M_Hour_Position_CL_A[16] := Hour_16_Position_CL;
  M_Hour_Position_CL_A[17] := Hour_17_Position_CL;
  M_Hour_Position_CL_A[18] := Hour_18_Position_CL;
  M_Hour_Position_CL_A[19] := Hour_19_Position_CL;
  M_Hour_Position_CL_A[20] := Hour_20_Position_CL;
  M_Hour_Position_CL_A[21] := Hour_21_Position_CL;
  M_Hour_Position_CL_A[22] := Hour_22_Position_CL;
  M_Hour_Position_CL_A[23] := Hour_23_Position_CL;

  For I := 0 To HOUR_CHART_N - 1 Do
    Begin { For }
      Hour_Chart       := M_Hour_Chart_A [I];
      Hour_RMS_BS      := M_Hour_RMS_BS_A [I];
      Hour_Data_LS     := M_Hour_Data_LS_A [I];
      Hour_Position_CL := M_Hour_Position_CL_A [I];

      With Hour_Chart Do
        Begin { With }
          Extent.XMax    := 0;
          Extent.XMin    := 60;
          Extent.UseXMax := TRUE;
          Extent.UseXMin := TRUE;

          ExtentSizeLimit.XMax    := 0;
          ExtentSizeLimit.XMin    := 60;
          ExtentSizeLimit.UseXMax := TRUE;
          ExtentSizeLimit.UseXMin := TRUE;

          Extent.YMax    := DATA_CHART_EXTEND_Y;
          Extent.YMin    := -DATA_CHART_EXTEND_Y;
          Extent.UseYMax := TRUE;
          Extent.UseYMin := TRUE;

          ExtentSizeLimit.YMax    := DATA_CHART_EXTEND_Y;
          ExtentSizeLimit.YMin    := -DATA_CHART_EXTEND_Y;
          ExtentSizeLimit.UseYMax := TRUE;
          ExtentSizeLimit.UseYMin := TRUE;

          LeftAxis.Marks.LabelFont.Size   := HOUR_CHART_FONT_SIZE;
          BottomAxis.Marks.LabelFont.Size := HOUR_CHART_FONT_SIZE;
        End; { With }

      Clear_Hour_RMS_BS (Hour_RMS_BS);

      Clear_Hour_Data_LS (Hour_Data_LS);

      With Hour_Position_CL Do
        Begin { With }
          Active      := FALSE;
          SeriesColor := POSITION_COLOR;
          Pen.Width   := POSITION_WIDTH;
          Position    := 0;
        End; { With }
    End; { For }

  M_Old_Data_Time     := Now;
  M_Old_Data_Hour     := 0;
  M_Old_Data_Minute   := 0;
  M_Old_Data_Second   := 0;
  M_Last_Saved_Minute := 0;

  { Position main form }
  Left   := FORM_LEFT;
  Top    := FORM_TOP;
  Width  := FORM_WIDTH;
  Height := FORM_HEIGHT;

  { Initialize current chart }
  Current_LS.Clear;
  Current_LP_LS.Clear;
  Current_EMA_LS.Clear;
  Current_RMS_LS.Clear;
  Current_Integral_LS.Clear;
  Current_Integral_LP_LS.Clear;

  Current_Chart_C.Extent.UseXMin          := TRUE;
  Current_Chart_C.Extent.UseXMax          := TRUE;
  Current_Chart_C.Extent.UseYMin          := TRUE;
  Current_Chart_C.Extent.UseYMax          := TRUE;
  Current_Chart_C.ExtentSizeLimit.YMax    := DATA_CHART_EXTEND_Y_MAX * 2;
  Current_Chart_C.ExtentSizeLimit.YMin    := -DATA_CHART_EXTEND_Y_MAX * 2;
  Current_Chart_C.ExtentSizeLimit.UseYMax := TRUE;
  Current_Chart_C.ExtentSizeLimit.UseYMin := TRUE;

  Current_Chart_C.AxisList.Axes[0].LabelSize    := DATA_CHART_LABEL_SIZE;
  Current_Chart_C.AxisList.Axes[0].Marks.Format := DATA_CHART_UNIT_FORMAT;

  Spectrum_CMS.Extent.UseYMin := TRUE;
  Spectrum_CMS.Extent.UseYMax := TRUE;
  Spectrum_CMS.Extent.YMax    := SPECTRUM_MAX_FREQUENCY;
  Spectrum_CMS.Extent.YMin    := 0;
  Spectrum_CMS.OnCalculate    := NIL;

  Spectrum_Chart_C.AxisList.Axes[0].LabelSize    := DATA_CHART_LABEL_SIZE;
  Spectrum_Chart_C.AxisList.Axes[0].Marks.Format := SPECTRUM_CHART_UNIT_FORMAT;

  M_Data_Start_Index := 0;
  M_Data_End_Index   := 0;

  M_Transfer_Process := TProcess.Create (NIL);

  {$IFDEF USE_TIMER_THREAD}
  M_Timer_Thread := T_Timer_Thread.Create (TRUE);
  M_Timer_Thread.ADS1262.Set_OFCAL (StrToInt (Input_OFCAL_Value_E.Text));
  {$ELSE}
  M_Count := 0;
  {$ENDIF}

  Reset_BClick (Sender);
End; { TMain_F.FormCreate }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.FormDestroy (Sender : TObject);
{ Free data                                                                               }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.FormDestroy }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.FormDestroy');
  {$ENDIF}

  Chart_T.Enabled := FALSE;

  M_Transfer_Process.Free;

  {$IFDEF USE_TIMER_THREAD}
  M_Timer_Thread.Terminate;
  Sleep (CLOSE_WAIT_TIME_MSEC);
  M_Timer_Thread.Free;
  {$ENDIF}

  { Free CriticalSection }
  {$IFDEF USE_CRITICAL_SECTION}
  DeleteCriticalSection (M_Critical_Section);
  {$ENDIF}
End; { TMain_F.FormDestroy }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.FormShow (Sender : TObject);
{ Show main form                                                                          }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.FormShow }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.FormShow');
  {$ENDIF}

  Input_X_Axis_Extend_EEditingDone (NIL);
  Input_Timer_Sec_EEditingDone (NIL);


  M_Data_A_Index_0 := 0;
  M_Data_A_Index_1 := 0;
  M_Start_Time     := Now;

  {$IFDEF USE_TIMER_THREAD}
  M_Timer_Thread.Start;
  {$ENDIF}

  { Display data }
  Chart_T.Enabled := TRUE;
End; { TMain_F.FormShow }


{#####################################################################################}
{ Buttons and edit components                                                         }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Output_Current_Data (F_Measurement_N : Integer; F_Zero_Crossings_N : Integer; F_End_Time : TDateTime);
{ --------------------------------------------------------------------------------------- }
Var
  I :         Integer;
  Bit_N_Max : Double;

Begin { TMain_F.Output_Current_Data }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Output_Current_Data');
  {$ENDIF}

  Bit_N_Max := 0;
  For I := 0 To BIT_N_RANGE - 1 Do
    Begin { For }
      //      Bit_N_Max := Max (Bit_N_Max, Log2 (Max (Abs (M_Timer_Thread.M_Data_Y_A [Validate_Array_Index (M_Data_A_Size - 1 - I, DATA_ARRAY_SIZE)]), 1)));
    End; { For }

  Output_NPS_E.Text     := FormatFloat ('#,##0.0', Double (F_Measurement_N) / (Double (MilliSecondSpan (M_Start_Time, F_End_Time)) / 1000));
  {$IFDEF USE_TIMER_THREAD}
  Output_Counter_E.Text := FormatFloat ('#,##0', M_Timer_Thread.Count);
  {$ELSE}
  Output_Counter_E.Text := FormatFloat ('#,##0', M_Count);
  {$ENDIF}
  Output_Bits_E.Text    := FormatFloat ('#,##0.0', Bit_N_Max);
  Output_Freq_E.Text    := FormatFloat ('#,##0.0', F_Zero_Crossings_N / 2 / Double (MilliSecondSpan (M_Start_Time, F_End_Time)) * 1000);
  M_Start_Time          := F_End_Time;
End; { TMain_F.Output_Current_Data }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Output_Current_Time;
{ --------------------------------------------------------------------------------------- }
Var
  Time_Value : TDateTime;

Begin { TMain_F.Output_Current_Time }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Output_Current_Time');
  {$ENDIF}

  Time_Value       := Now;
  Status_L.Caption := CURRENT_TIME_FORMAT_00_S + FormatDateTime ('DD.MM.YYYY', Time_Value) + CURRENT_TIME_FORMAT_01_S + FormatDateTime ('hh:nn:ss', Time_Value);

  Repaint;
  Application.ProcessMessages;
End; { TMain_F.Output_Current_Time }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Close_BClick (Sender : TObject);
{ Close button pressed                                                                    }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Close_BClick }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Close_BClick');
  {$ENDIF}

  Chart_T.Enabled := FALSE;
  Sleep (CLOSE_WAIT_TIME_MSEC);

  Close;
End; { TMain_F.Close_BClick }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Start_Stop_BClick (Sender : TObject);
{ Start/Stop button pressed                                                               }
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Start_Stop_BClick }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Start_Stop_BClick');
  {$ENDIF}

  Chart_T.Enabled := not Chart_T.Enabled;
End; { TMain_F.Start_Stop_BClick }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Reset_BClick (Sender : TObject);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Reset_BClick }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Reset_BClick');
  {$ENDIF}

  Current_Chart_C.Extent.XMin := Now - (StrToInt (Input_X_Axis_Extend_E.Text) / 24 / 60 / 60);
  Current_Chart_C.Extent.XMax := Now;
  Current_Chart_C.Extent.YMax := DATA_CHART_EXTEND_Y;
  Current_Chart_C.Extent.YMin := -DATA_CHART_EXTEND_Y;
End; { TMain_F.Reset_BClick }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Input_Sensivity_Bits_EEditingDone (Sender : TObject);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Input_Sensivity_Bits_EEditingDone }
  {$IFDEF USE_TIMER_THREAD}
    Try
      M_Timer_Thread.ADS1262.Sensivity := StrToInt (Input_Sensivity_Bits_E.Text);
    Except
      M_Timer_Thread.ADS1262.Sensivity := ADS1262_INITIAL_SENSIVITY;
    End; { Try }
  {$ENDIF}
End; { TMain_F.Input_Sensivity_Bits_EEditingDone }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Input_OFCAL_Value_EEditingDone (Sender : TObject);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Input_OFCAL_Value_EEditingDone }
  {$IFDEF USE_TIMER_THREAD}
    Try
      M_Timer_Thread.ADS1262.Set_OFCAL (StrToInt (Input_OFCAL_Value_E.Text));
    Except
      M_Timer_Thread.ADS1262.Set_OFCAL (ADS1262_INITIAL_OFCAL);
    End; { Try }
  {$ENDIF}
End; { TMain_F.Input_OFCAL_Value_EEditingDone }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Input_LP_Span_EEditingDone (Sender : TObject);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Input_LP_Span_EEditingDone }
  {$IFDEF USE_TIMER_THREAD}
  M_Timer_Thread.LP_Span := StrToInt (Input_LP_Span_E.Text);
  {$ENDIF}
End; { TMain_F.Input_LP_Span_EEditingDone }

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Input_X_Axis_Extend_EEditingDone (Sender : TObject);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Input_X_Axis_Extend_EEditingDone }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Input_X_Axis_Extend_EEditingDone');
  {$ENDIF}

  Current_Chart_C.Extent.XMin := Now - (StrToInt (Input_X_Axis_Extend_E.Text) / 24 / 60 / 60);
  Current_Chart_C.Extent.XMax := Now;
End; { TMain_F.Input_X_Axis_Extend_EEditingDone }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Input_Timer_Sec_EEditingDone (Sender : TObject);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Input_Timer_Sec_EEditingDone }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Input_Timer_Sec_EEditingDone');
  {$ENDIF}

  Chart_T.Interval := Round (StrToFloat (Input_Timer_Sec_E.Text) * 1000);
End; { TMain_F.Input_Timer_Sec_EEditingDone }


{#####################################################################################}
{ Utilities                                                                           }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Debug_Output (F_Text : String);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Debug_Output }
  {$IFDEF DEBUG_INFORMATION}
  WriteLn (FormatDateTime ('DD.MM.YYYY - hh:nn:ss - ', Now) + F_Text);
  {$ENDIF}
End; { TMain_F.Debug_Output }


{ --------------------------------------------------------------------------------------- }
Function TMain_F.Calculate_Velocity (F_Data : Int_32) : Double;
  { Calculate voltage from raw data                                                         }
  { --------------------------------------------------------------------------------------- }
Begin { TMain_F.Calculate_Velocity }
  Result := F_Data * VOLTAGE_LIMIT / $7FFFFFFF / SM24_VELOCITY_FACTOR * 1000;
End; { TMain_F.Calculate_Velocity }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Read_Array_Data (F_Index : Integer; Var F_X : TDateTime; Var F_Y : Int_32; Var F_LP_Y : Int_32);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Read_Array_Data }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Read_Array_Data');
  {$ENDIF}

  {$IFDEF USE_CRITICAL_SECTION}
  EnterCriticalSection (Main_F.M_Critical_Section);
    Try
  {$ENDIF}
  {$IFDEF USE_TIMER_THREAD}
      F_X    := M_Timer_Thread.Data_X_A [F_Index];
      F_Y    := M_Timer_Thread.Data_Y_A [F_Index];
      F_LP_Y := M_Timer_Thread.Data_LP_Y_A [F_Index];
  {$ELSE}
      F_X    := Now;
      F_Y    := Random (RANDOM_MAX);
      F_LP_Y := Random (RANDOM_MAX);
  {$ENDIF}
  {$IFDEF USE_CRITICAL_SECTION}
    Finally
      LeaveCriticalSection (Main_F.M_Critical_Section);
    End;
  {$ENDIF}
End; { TMain_F.Read_Array_Data }


{#####################################################################################}
{ Chart                                                                               }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Clear_Hour_Data_LS (F_Hour_Data_LS : TLineSeries);
{ --------------------------------------------------------------------------------------- }
Var
  J : Integer;

Begin { TMain_F.Clear_Hour_Data_LS }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Clear_Hour_Data_LS');
  {$ENDIF}

  With F_Hour_Data_LS Do
    Begin { With }
      Clear;
      SeriesColor   := DATA_COLOR;
      LinePen.Width := DATA_PIN_WIDTH;
      For J := 0 To VALUES_PER_HOUR - 1 Do
        Begin { For }
          AddXY (Round (J) / 60, 0);
          AddXY (Round (J) / 60, 0);
        End; { For }
    End; { With }
End; { TMain_F.Clear_Hour_Data_LS }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Clear_Hour_RMS_BS (F_Hour_RMS_BS : TBarSeries);
{ --------------------------------------------------------------------------------------- }
Var
  J : Integer;

Begin { TMain_F.Clear_Hour_RMS_BS }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Clear_Hour_RMS_BS');
  {$ENDIF}

  With F_Hour_RMS_BS Do
    Begin { With }
      Clear;
      SeriesColor     := RMS_COLOR;
      BarWidthPercent := RMS_BAR_WIDTH_PERCENT;
      For J := 0 To VALUES_PER_HOUR - 1 Do
        Begin { For }
          AddXY (Round (J) / 60, 0.0);
        End; { For }
    End; { With }
End; { TMain_F.Clear_Hour_RMS_BS }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Chart_TS_DataPointHintToolHint (ATool : TDataPointHintTool; Const APoint : TPoint; Var AHint : String);
{ --------------------------------------------------------------------------------------- }
Const
  SPACE_START = '         ';

Var
  Time_Value : TDateTime;
  Voltage :    Double;

Begin { TMain_F.Chart_TS_DataPointHintToolHint }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Chart_TS_DataPointHintToolHint');
  {$ENDIF}

    Try
      Time_Value := TChartSeries (ATool.Series).Source.Item [ATool.PointIndex]^.X;
      Voltage    := TChartSeries (ATool.Series).Source.Item [ATool.PointIndex]^.Y;
      AHint      := SPACE_START + 'X : ' + FormatDateTime ('hh:nn:ss.zzz', Time_Value) + #13 + Format (SPACE_START + 'Y : %.8f', [Voltage]);
    Except
      AHint := '';
    End; { Try }
End; { TMain_F.Chart_TS_DataPointHintToolHint }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Chart_TTimer (Sender : TObject);
{ Repaint chart                                                                           }
{ --------------------------------------------------------------------------------------- }
Var
  Index :               Integer;
  Time_Value :          TDateTime;
  Voltage :             Double;
  Old_Voltage :         Double;
  Integral_Voltage :    Double;
  LP_Integral_Voltage : Double;
  Zero_Crossings_C :    Integer;
  Current_Extend :      TDoubleRect;
  Extend_Dif :          Double;
  J :                   Integer;
  Start_Index :         Integer;
  End_Index :           Integer;
  V :                   Double;
  LP_Voltage :          Double;
  Additional_Index :    Integer;
  Data_Time :           TDateTime;
  Data_Hour :           Integer;
  Data_Minute :         Integer;
  Data_Second :         Integer;
  Old_Data_Second :     Integer;
  RMS :                 Double;
  Upper_Peak :          Double;
  Lower_Peak :          Double;
  Hour_Data_LS :        TLineSeries;
  Hour_Position_CL :    TConstantLine;

Begin { TMain_F.Chart_TTimer }
  {$IFDEF DEBUG_INFORMATION}
  Debug_Output ('Begin of: TMain_F.Chart_TTimer');
  {$ENDIF}

  Case T_Chart_Kind (Main_PC.TabIndex) Of
      CURRENT_CHART_CK :
        Begin { CURRENT_CHART_CK }
          Current_LS.Active             := Show_Data_CB.Checked;
          Current_LP_LS.Active          := Show_LP_Data_CB.Checked;
          Current_EMA_LS.Active         := Show_EMA_CB.Checked;
          Current_RMS_LS.Active         := Show_RMS_CB.Checked;
          Current_Integral_LS.Active    := Show_Integral_CB.Checked;
          Current_Integral_LP_LS.Active := Show_LP_Integral_CB.Checked;
          Spectrum_CMS.Active           := Show_Spektrum_CB.Checked;

          Current_LS.BeginUpdate;
          Current_LP_LS.BeginUpdate;
          Current_EMA_LS.BeginUpdate;
          Current_RMS_LS.BeginUpdate;
          Current_Integral_LS.BeginUpdate;
          Current_Integral_LP_LS.BeginUpdate;
          Spectrum_CMS.OnCalculate := NIL;

          {$IFDEF CLEAR_SERIES}
          Current_LS.Clear;
          Current_LP_LS.Clear;
          Current_EMA_LS.Clear;
          Current_RMS_LS.Clear;
          Current_Integral_LS.Clear;
          Current_Integral_LP_LS.Clear;
          {$ENDIF}

          {$IFDEF USE_TIMER_THREAD}
          M_Data_A_Index_1 := M_Timer_Thread.Data_A_Index;
          {$ELSE}
          M_Data_A_Index_1 := RANDOM_N;
          {$ENDIF}
          Zero_Crossings_C := 0;
          Additional_Index := 0;
          Old_Data_Second  := 0;

          If M_Data_A_Index_1 < M_Data_A_Index_0 Then
            Begin { then }
              Start_Index := M_Data_A_Index_0;
              End_Index   := DATA_ARRAY_SIZE - 1;
              Voltage     := 0;
              Old_Voltage := Voltage;
              Index       := Start_Index;
              While Index <= End_Index Do
                Begin { While }
                  {$IFDEF USE_TIMER_THREAD}
                  Time_Value          := M_Timer_Thread.Data_X_A [Index];
                  Voltage             := Calculate_Velocity (M_Timer_Thread.Data_Y_A [Index]);
                  LP_Voltage          := Calculate_Velocity (M_Timer_Thread.Data_LP_Y_A [Index]);
                  Integral_Voltage    := Calculate_Velocity (M_Timer_Thread.Data_Integral_Y_A [Index]);
                  LP_Integral_Voltage := Calculate_Velocity (M_Timer_Thread.Data_LP_Integral_Y_A [Index]);
                  Current_LS.AddXY (Time_Value, Voltage);
                  Current_LP_LS.AddXY (Time_Value, LP_Voltage);
                  Current_Integral_LS.AddXY (Time_Value, Integral_Voltage);
                  Current_Integral_LP_LS.AddXY (Time_Value, LP_Integral_Voltage);
                  Current_EMA_LS.AddXY (Time_Value, Calculate_Velocity (M_Timer_Thread.ADS1262.EMA_Data));
                  {$ELSE}
                  Time_Value          := M_Old_Data_Time + ((Now - M_Old_Data_Time) / End_Index * Index);
                  Voltage             := Calculate_Velocity (Random (RANDOM_MAX));
                  LP_Voltage          := Calculate_Velocity (Random (RANDOM_MAX));
                  Integral_Voltage    := Calculate_Velocity (Random (RANDOM_MAX));
                  LP_Integral_Voltage := Calculate_Velocity (Random (RANDOM_MAX));
                  Current_LS.AddXY (Time_Value, Voltage);
                  Current_LP_LS.AddXY (Time_Value, LP_Voltage);
                  Current_Integral_LS.AddXY (Time_Value, Integral_Voltage);
                  Current_Integral_LP_LS.AddXY (Time_Value, LP_Integral_Voltage);
                  Current_EMA_LS.AddXY (Time_Value, Calculate_Velocity (Random (RANDOM_MAX)));
                  Inc (M_Count);
                  {$ENDIF}

                  { Calculate RMS }
                  RMS := 0;
                  For J := 0 To StrToInt (Input_LP_Span_E.Text) - 1 Do
                    Begin { For }
                      {$IFDEF USE_TIMER_THREAD}
                      V   := Calculate_Velocity (M_Timer_Thread.Data_Y_A [Validate_Array_Index (Index - J, DATA_ARRAY_SIZE)]);
                      {$ELSE}
                      V   := Calculate_Velocity (Random (RANDOM_MAX));
                      {$ENDIF}
                      RMS := RMS + Sqr (V);
                    End; { For }
                  RMS := Sqrt (RMS / (StrToInt (Input_LP_Span_E.Text)));
                  Current_RMS_LS.AddXY (Time_Value, RMS);

                  { Calculate spectrum }
                  Data_Second := SecondOf (Time_Value);
                  If Data_Second <> Old_Data_Second Then
                    Begin { then }
                      Calculate_Goertzel_DFT (Index, Time_Value);
                      Old_Data_Second := Data_Second;
                    End; { then }

                  If (Index > Start_Index) and ((Old_Voltage < 0) and (Voltage > 0) or (Old_Voltage > 0) and (Voltage < 0)) Then
                    Begin { then }
                      Inc (Zero_Crossings_C);
                    End; { then }

                  Old_Voltage := Voltage;
                  Index       := Validate_Array_Index (Index + 1, DATA_ARRAY_SIZE);
                End; { While }

              Additional_Index := End_Index - Start_Index;
              M_Data_A_Index_0 := 0;
            End; { then }

          Start_Index := M_Data_A_Index_0;
          End_Index   := M_Data_A_Index_1 - 1;
          Voltage     := 0;
          Old_Voltage := Voltage;
          Index       := Start_Index;
          While Index <= End_Index Do
            Begin { While }
              {$IFDEF USE_TIMER_THREAD}
              Time_Value          := M_Timer_Thread.Data_X_A [Index];
              Voltage             := Calculate_Velocity (M_Timer_Thread.Data_Y_A [Index]);
              LP_Voltage          := Calculate_Velocity (M_Timer_Thread.Data_LP_Y_A [Index]);
              Integral_Voltage    := Calculate_Velocity (M_Timer_Thread.Data_Integral_Y_A [Index]);
              LP_Integral_Voltage := Calculate_Velocity (M_Timer_Thread.Data_LP_Integral_Y_A [Index]);
              Current_LS.AddXY (Time_Value, Voltage);
              Current_LP_LS.AddXY (Time_Value, LP_Voltage);
              Current_Integral_LS.AddXY (Time_Value, Integral_Voltage);
              Current_Integral_LP_LS.AddXY (Time_Value, LP_Integral_Voltage);
              Current_EMA_LS.AddXY (Time_Value, Calculate_Velocity (M_Timer_Thread.ADS1262.EMA_Data));
              {$ELSE}
              Time_Value          := M_Old_Data_Time + ((Now - M_Old_Data_Time) / End_Index * Index);
              Voltage             := Calculate_Velocity (Random (RANDOM_MAX));
              LP_Voltage          := Calculate_Velocity (Random (RANDOM_MAX));
              Integral_Voltage    := Calculate_Velocity (Random (RANDOM_MAX));
              LP_Integral_Voltage := Calculate_Velocity (Random (RANDOM_MAX));
              Current_LS.AddXY (Time_Value, Voltage);
              Current_LP_LS.AddXY (Time_Value, LP_Voltage);
              Current_Integral_LS.AddXY (Time_Value, Integral_Voltage);
              Current_Integral_LP_LS.AddXY (Time_Value, LP_Integral_Voltage);
              Current_EMA_LS.AddXY (Time_Value, Calculate_Velocity (Random (RANDOM_MAX)));
              Inc (M_Count);
              {$ENDIF}

              { Calculate RMS }
              RMS := 0;
              For J := 0 To StrToInt (Input_LP_Span_E.Text) - 1 Do
                Begin { For }
                  {$IFDEF USE_TIMER_THREAD}
                  V   := Calculate_Velocity (M_Timer_Thread.Data_Y_A [Validate_Array_Index (Index - J, DATA_ARRAY_SIZE)]);
                  {$ELSE}
                  V   := Calculate_Velocity (Random (RANDOM_MAX));
                  {$ENDIF}
                  RMS := RMS + Sqr (V);
                End; { For }
              RMS := Sqrt (RMS / (StrToInt (Input_LP_Span_E.Text)));
              Current_RMS_LS.AddXY (Time_Value, RMS);

              { Calculate spectrum }
              Data_Second := SecondOf (Time_Value);
              If Data_Second <> Old_Data_Second Then
                Begin { then }
                  Calculate_Goertzel_DFT (Index, Time_Value);
                  Old_Data_Second := Data_Second;
                End; { then }

              If (Index > Start_Index) and ((Old_Voltage < 0) and (Voltage > 0) or (Old_Voltage > 0) and (Voltage < 0)) Then
                Begin { then }
                  Inc (Zero_Crossings_C);
                End; { then }

              Old_Voltage := Voltage;
              Index       := Validate_Array_Index (Index + 1, DATA_ARRAY_SIZE);
            End; { While }

          If Show_Current_Data_CB.Checked = TRUE Then
            Begin { then }
              Current_Extend                := Current_Chart_C.LogicalExtent;
              Extend_Dif                    := Current_Extend.b.X - Current_Extend.a.X;
              Current_Extend.b.X            := Now;
              Current_Extend.a.X            := Current_Extend.b.X - Extend_Dif;
              Current_Chart_C.LogicalExtent := Current_Extend;

              Current_Extend                := Current_Chart_C.LogicalExtent;
              Current_Chart_C.LogicalExtent := Current_Extend;
            End; { then }

          Spectrum_CMS.OnCalculate := @Main_F.Spectrum_CMSCalculate;
          Current_Integral_LP_LS.EndUpdate;
          Current_Integral_LS.EndUpdate;
          Current_RMS_LS.EndUpdate;
          Current_EMA_LS.EndUpdate;
          Current_LP_LS.EndUpdate;
          Current_LS.EndUpdate;

          {$IFDEF USE_TIMER_THREAD}
          Time_Value  := M_Timer_Thread.Data_X_A [End_Index];
          {$ELSE}
          Time_Value  := Now;
          {$ENDIF}
          Data_Time   := Time_Value;
          Data_Hour   := HourOf (Time_Value);
          Data_Minute := MinuteOf (Time_Value);
          Data_Second := SecondOf (Time_Value);
        End; { CURRENT_CHART_CK }
      DAILY_CHART_CK :
        Begin { DAILY_CHART_CK }

          { Calculate peak values }
          {$IFDEF USE_TIMER_THREAD}
          M_Data_A_Index_1 := M_Timer_Thread.Data_A_Index;
          {$ELSE}
          M_Data_A_Index_1 := RANDOM_N;
          {$ENDIF}
          Zero_Crossings_C := 0;
          Additional_Index := 0;
          Upper_Peak       := 0;
          Lower_Peak       := 0;

          If M_Data_A_Index_1 < M_Data_A_Index_0 Then
            Begin { then }
              Start_Index := M_Data_A_Index_0;
              End_Index   := DATA_ARRAY_SIZE - 1;
              Voltage     := 0;
              Old_Voltage := Voltage;
              Index       := Start_Index;
              While Index <= End_Index Do
                Begin { While }
                  {$IFDEF USE_TIMER_THREAD}
                  Voltage := Calculate_Velocity (M_Timer_Thread.Data_Y_A [Index]);
                  {$ELSE}
                  Voltage := Calculate_Velocity (Random (RANDOM_MAX));
                  Inc (M_Count);
                  {$ENDIF}

                  Upper_Peak := Max (Upper_Peak, Voltage);
                  Lower_Peak := Min (Lower_Peak, Voltage);

                  If (Index > Start_Index) and ((Old_Voltage < 0) and (Voltage > 0) or (Old_Voltage > 0) and (Voltage < 0)) Then
                    Begin { then }
                      Inc (Zero_Crossings_C);
                    End; { then }

                  Old_Voltage := Voltage;
                  Index       := Validate_Array_Index (Index + 1, DATA_ARRAY_SIZE);
                End; { While }
              Additional_Index := End_Index - Start_Index;
              M_Data_A_Index_0 := 0;
            End; { then }

          Start_Index := M_Data_A_Index_0;
          End_Index   := M_Data_A_Index_1 - 1;
          Voltage     := 0;
          Old_Voltage := Voltage;
          Index       := Start_Index;
          While Index <= End_Index Do
            Begin { While }
              {$IFDEF USE_TIMER_THREAD}
              Voltage := Calculate_Velocity (M_Timer_Thread.Data_Y_A [Index]);
              {$ELSE}
              Voltage := Calculate_Velocity (Random (RANDOM_MAX));
              Inc (M_Count);
              {$ENDIF}

              Upper_Peak := Max (Upper_Peak, Voltage);
              Lower_Peak := Min (Lower_Peak, Voltage);

              If (Index > Start_Index) and ((Old_Voltage < 0) and (Voltage > 0) or (Old_Voltage > 0) and (Voltage < 0)) Then
                Begin { then }
                  Inc (Zero_Crossings_C);
                End; { then }

              Old_Voltage := Voltage;
              Index       := Validate_Array_Index (Index + 1, DATA_ARRAY_SIZE);
            End; { While }

          {$IFDEF USE_TIMER_THREAD}
          Time_Value  := M_Timer_Thread.Data_X_A [End_Index];
          {$ELSE}
          Time_Value  := Now;
          {$ENDIF}
          Data_Time   := Time_Value;
          Data_Hour   := HourOf (Time_Value);
          Data_Minute := MinuteOf (Time_Value);
          Data_Second := SecondOf (Time_Value);

          Index            := (Data_Minute * 60) + Data_Second;
          Hour_Data_LS     := M_Hour_Data_LS_A [Data_Hour];
          Hour_Position_CL := M_Hour_Position_CL_A [Data_Hour];

          M_Hour_Position_CL_A[M_Old_Data_Hour].Active := FALSE;
          Hour_Position_CL.Position                    := Index / 60;
          Hour_Position_CL.Active                      := TRUE;

          { Output peak values }
          Hour_Data_LS.BeginUpdate;

          { Clear data if new hour starts }
          If Data_Hour <> M_Old_Data_Hour Then
            Begin { then }
              Clear_Hour_Data_LS (Hour_Data_LS);
            End; { then }

          Hour_Data_LS.ListSource.Item[Index shl 1]^.Y       := Upper_Peak;
          Hour_Data_LS.ListSource.Item[(Index shl 1) + 1]^.Y := Lower_Peak;

          Hour_Data_LS.EndUpdate;
        End; { DAILY_CHART_CK }
    End; { Case }

  M_Data_A_Size    := (End_Index - Start_Index) + Additional_Index;
  {$IFDEF USE_TIMER_THREAD}
  M_Data_A_Index_0 := M_Data_A_Index_1;
  {$ELSE}
  M_Data_A_Index_0 := 0;
  {$ENDIF}

  Output_Current_Data (M_Data_A_Size, Zero_Crossings_C, Time_Value);

  Output_Current_Time;

  M_Old_Data_Time   := Data_Time;
  M_Old_Data_Hour   := Data_Hour;
  M_Old_Data_Minute := Data_Minute;
  M_Old_Data_Second := Data_Second;
End; { TMain_F.Chart_TTimer }


{#####################################################################################}
{ Spectral chart                                                                      }
{#####################################################################################}

{-------------------------------------------------------------------------------------}
Function TMain_F.Get_Time_Index (F_Time_Value : TDateTime) : Integer;
  { Get number of seconds of time value                                                 }
  {-------------------------------------------------------------------------------------}
Begin { TMain_F.Get_Time_Index }
  Result := Validate_Array_Index (Round (Double (F_Time_Value - SPECTRUM_DATETIME_OFFSET) * 24 * 60 * 60) - M_DateTime_Start_Offset, SPECTRUM_X_ARRAY_SIZE);
End; { TMain_F.Get_Time_Index }


{-------------------------------------------------------------------------------------}
Procedure TMain_F.Spectrum_CMSCalculate (Const AX, AY : Double; out AZ : Double);
{ Calculate spectrum chart                                                            }
{-------------------------------------------------------------------------------------}
Begin { TMain_F.Spectrum_CMSCalculate }
  AZ := Get_Spectrum (AX, Round (AY / SPECTRUM_MAX_FREQUENCY * SPECTRUM_Y_RESOLUTION)) * 100;
End; { TMain_F.Spectrum_CMSCalculate }


{-------------------------------------------------------------------------------------}
Function TMain_F.Get_Spectrum (F_X : TDateTime; F_Y : Integer) : Double;
  { Get spectrum value                                                                  }
  {-------------------------------------------------------------------------------------}
Var
  X : Integer;

Begin { TMain_F.Get_Spectrum }
  X   := Get_Time_Index (F_X);
  //  X   := Max (0, X);
  //  X   := Min (SPECTRUM_X_ARRAY_SIZE - 1, X);
  F_Y := Max (0, F_Y);
  F_Y := Min (SPECTRUM_Y_RESOLUTION, F_Y);

  Result := M_Spectrum_A [X, F_Y];
End; { TMain_F.Get_Spectrum }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Set_Spectrum (F_X : TDateTime; F_Y : Integer; F_Value : Double);
{ --------------------------------------------------------------------------------------- }
Var
  X : Integer;

Begin { TMain_F.Set_Spectrum }
  X   := Get_Time_Index (F_X);
  //  X   := Max (0, X);
  //  X   := Min (SPECTRUM_X_ARRAY_SIZE - 1, X);
  F_Y := Max (0, F_Y);
  F_Y := Min (SPECTRUM_Y_RESOLUTION, F_Y);

  M_Spectrum_A[X, F_Y] := F_Value;


End; { TMain_F.Set_Spectrum }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Set_Amplitude (F_X : Integer; F_Y : Integer; F_Value : Double);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Set_Amplitude }
End; { TMain_F.Set_Amplitude }


{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Set_Phi (F_X : Integer; F_Y : Integer; F_Value : Double);
{ --------------------------------------------------------------------------------------- }
Begin { TMain_F.Set_Phi }
End; { TMain_F.Set_Phi }


{#####################################################################################}
{ Spectral analysis                                                                   }
{#####################################################################################}

{ --------------------------------------------------------------------------------------- }
Procedure TMain_F.Calculate_Goertzel_DFT (F_End_Index : Integer; F_End_Time : TDateTime);
{ --------------------------------------------------------------------------------------- }

  Function Windowing_Filter (F_Input : Double; F_C : Integer; F_Window_Size : Integer; F_Windowing_Function : T_Spectrum_Window_Funktion) : Double;
  Begin { Windowing_Filter }
      {---------------------------------------------}
      { Calculates a value using a windowing filter }
      {---------------------------------------------}
      F_C := F_C - (F_Window_Size div 2);

      Case F_Windowing_Function Of
          TWF_None :
            Begin { TWF_None }
              Result := F_Input;
            End; { TWF_None }
          TWF_von_Hahn :
            Begin { TWF_von_Hahn }
              Result := F_Input * (0.5 * (1 - cos ((F_C * 2 * Pi) / (F_Window_Size - 1))));
            End; { TWF_von_Hahn }
          TWF_Hamming :
            Begin { TWF_Hamming }
              Result := F_Input * (0.54 - 0.46 * cos ((F_C * 2 * Pi) / (F_Window_Size - 1)));
            End; { TWF_Hamming }
          TWF_Blackman :
            Begin { TWF_Blackman }
              Result := F_Input * (0.42 - 0.5 * cos ((F_C * 2 * Pi) / (F_Window_Size - 1)) + 0.08 * cos ((F_C * 4 * Pi) / (F_Window_Size - 1)));
            End; { TWF_Blackman }
          TWF_KaiserBessel :
            Begin { TWF_KaiserBessel }
              Result := F_Input * (0.4021 - 0.4986 * cos ((F_C * 2 * Pi) / (F_Window_Size - 1)) + 0.0981 * cos ((F_C * 4 * Pi) / (F_Window_Size - 1)) - 0.0012 * cos ((F_C * 6 * Pi) / (F_Window_Size - 1)));
            End; { TWF_KaiserBessel }
          TWF_FlatTop :
            Begin { TWF_FlatTop }
              Result := F_Input * (0.2155 - 0.4159 * cos ((F_C * 2 * Pi) / (F_Window_Size - 1)) + 0.2780 * cos ((F_C * 4 * Pi) / (F_Window_Size - 1)) - 0.0836 * cos ((F_C * 6 * Pi) / (F_Window_Size - 1)) + 0.0070 * cos ((F_C * 8 * Pi) / (F_Window_Size - 1)));
            End; { TWF_FlatTop }
        End; { Case }
  End; { Windowing_Filter }


  {-------------------------------------------------------------------------------------}
  Function Calculate_Goertzel_DFT_Amplitude (F_Sample_N : Integer; F_Target_Frequency : Integer; F_Sampling_Rate : Integer; F_Input_A : T_Spectrum_Input_A; F_Windowing_Function : T_Spectrum_Window_Funktion) : Double;
      { Calculate Goertzel DFT for one freqency                                             }
      {-------------------------------------------------------------------------------------}
  Var
      I :               Integer;
      K :               Integer;
      floatnumSamples : Double;
      omega :           Double;
      V_Sine :          Double;
      V_Cosine :        Double;
      coeff :           Double;
      q0 :              Double;
      q1 :              Double;
      q2 :              Double;
      magnitude :       Double;
      V_Real :          Double;
      V_Imag :          Double;
      scalingFactor :   Double;
      V :               Double;

  Begin { Calculate_Goertzel_DFT_Amplitude }
      scalingFactor := F_Sample_N / 2.0;

      floatnumSamples := F_Sample_N;
      k               := Trunc (0.5 + ((floatnumSamples * F_Target_Frequency) / F_Sampling_Rate));
      omega           := (2.0 * Pi * k) / floatnumSamples;
      V_Sine          := sin (omega);
      V_Cosine        := cos (omega);
      coeff           := 2.0 * V_Cosine;
      q0              := 0;
      q1              := 0;
      q2              := 0;

      For I := 0 To F_Sample_N - 1 Do
        Begin { For }
          q2 := q1;
          q1 := q0;
          V  := Windowing_Filter (F_Input_A [i], I, F_Sample_N, F_Windowing_Function);
          q0 := (coeff * q1) - q2 + V;
        End; { For }

      V_Real := (q0 - (q1 * V_Cosine)) / scalingFactor;
      V_Imag := (-q1 * V_Sine) / scalingFactor;

      magnitude := sqrt (sqr (V_Real) + sqr (V_Imag));
      Result    := magnitude;
  End; { Calculate_Goertzel_DFT_Amplitude }

Var
  V :             Double;
  I :             Integer;
  X :             Integer;
  Test_A :        T_Spectrum_Input_A;
  Amplitude_A :   Array [0 .. SPECTRUM_Y_RESOLUTION] Of Double;
  Max_Amplitude : Double;

Begin { TMain_F.Calculate_Goertzel_DFT }
  SetLength (Test_A, SPECTRUM_SAMPLE_N);
  For I := 0 To Length (Test_A) - 1 Do
    Begin { For }
      {$IFDEF USE_TIMER_THREAD}
      Test_A[I] := M_Timer_Thread.Data_Y_A [Validate_Array_Index (F_End_Index - SPECTRUM_SAMPLE_N + I + 1, DATA_ARRAY_SIZE)];
      {$ELSE}
      Test_A[I] := Random (RANDOM_MAX);
      {$ENDIF}
    End; { For }

  Amplitude_A[0] := 0;
  For I := 1 To SPECTRUM_Y_RESOLUTION Do
    Begin { For }
      V              := Calculate_Goertzel_DFT_Amplitude (SPECTRUM_SAMPLE_N, Round (SPECTRUM_MAX_FREQUENCY * I / SPECTRUM_Y_RESOLUTION), SPECTRUM_SAMPLE_RATE, Test_A, T_Spectrum_Window_Funktion (Input_Window_Function_SE.Value));
      Amplitude_A[I] := V;
    End; { For }

  Max_Amplitude := 0;
  For I := 0 To SPECTRUM_Y_RESOLUTION Do
    Begin { For }
      V             := Amplitude_A [I];
      //      Max_Amplitude := Max_Amplitude + V;
      Max_Amplitude := Max (Max_Amplitude, V);
    End; { For }
  If Max_Amplitude = 0 Then
    Begin { then }
      Max_Amplitude := 1;
    End; { then }
  M_Spectrum_Maximum_A[Get_Time_Index (F_End_Time)] := Max_Amplitude;

  Max_Amplitude := 0;
  X             := Get_Time_Index (F_End_Time);
  For I := 0 To SPECTRUM_MAXIMUM_SPAN - 1 Do
    Begin { For }
      V             := M_Spectrum_Maximum_A [X];
      Max_Amplitude := Max (Max_Amplitude, V);
      X             := Validate_Array_Index (X - 1, SPECTRUM_X_ARRAY_SIZE);
    End; { For }

  For I := 0 To SPECTRUM_Y_RESOLUTION Do
    Begin { For }
      Amplitude_A[I] := Amplitude_A [I] / Max_Amplitude;
    End; { For }

  For I := 0 To SPECTRUM_Y_RESOLUTION Do
    Begin { For }
      V := Amplitude_A [I];
      Set_Spectrum (F_End_Time, I, V);
    End; { For }
End; { TMain_F.Calculate_Goertzel_DFT }


End.

project_defines.inc

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Includes                                                                          ## }
{ ##                                                                                   ## }
{ ## Copyright (C) 2018-2019  : Dr. Jürgen Abel                                        ## }
{ ## Email                    : juergen@juergen-abel.info                              ## }
{ ## Internet                 : www.seismometer.info                                   ## }
{ ##                                                                                   ## }
{ ####################################################################################### }


{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Global defines                                                                    ## }
{ ##                                                                                   ## }
{ ####################################################################################### }

{$PACKRECORDS C}

{ ####################################################################################### }
{ ##                                                                                   ## }
{ ## Following Defines can be enabled or disabled                                      ## }
{ ##                                                                                   ## }
{ ####################################################################################### }

{   $DEFINE DEBUG_GPIO_OUTPUT}
{   $DEFINE DEBUG_SPI_OUTPUT}
{   $DEFINE DEBUG_ADS1262_OUTPUT}
{   $DEFINE DEBUG_MAIN_OUTPUT}
{   $DEFINE DEBUG_ERROR}
{   $DEFINE DEBUG_ADC1_DATA}
{   $DEFINE DEBUG_INFORMATION}
{$DEFINE SELF_CALIBRATION_ON}
{$DEFINE USE_INTERRUPT}
{   $DEFINE SAVE_IMAGE}
{   $DEFINE TRANSFER_DATA_TO_INTERNET_SERVER}
{   $DEFINE USE_CRITICAL_SECTION}
{$DEFINE USE_TIMER_THREAD}
{   $DEFINE CLEAR_SERIES}