unit uSpectrumAnalyzer;

interface

{$REGION ' Header '}
{$REGION ' -Uses '}

uses
  Windows, Messages, Variants, Classes, Graphics, Controls, Forms, Dialogs,
  FileCtrl, StdCtrls, StrUtils,
  MMSystem, Buttons, SDL_sdlbase, SDL_rchart, SDL_onoffbut, SDL_fourier,
  SDL_gradfl, SDL_NumLab, SDL_stringl,
  OleCtrls, DBOleCtl, ExtCtrls, ComCtrls, Menus, AxCtrls, AbSwitch, AbNumEdit,
  DateUtils, AbOpHour, ExtDlgs,
  Registry, AbLED, Math, uSETICommon, _GClass, VrControls,
  VrRotarySwitch, JvBaseDlg, JvSelectDirectory, JvExComCtrls, JvComCtrls, JvProgressBar,

  uROClient, uROBINMessage, uROClientIntf, uROServer,
  uROPoweredByRemObjectsButton,
  uROIndyUDPServer, uROPostMessage, uROIndyEmailServer, uROIndyTCPServer,
  uROIndyHTTPServer, uROBaseHTTPServer, uROBaseConnection, uROCustomHTTPServer, uROComponent, uROMessage,
  uROTransportChannel,
  uROBaseHTTPClient, uROIndyHTTPChannel, uROJSONMessage,
  JSON, Vcl.WinXCtrls, JvExStdCtrls, JvShapedButton, uROServerMultiMessage, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdExplicitTLSClientServerBase, IdFTP, IdFTPCommon, Jpeg, JvExControls, JvLED, WaveUtils, WaveStorage,
  WaveRecorders, WaveOut, WavePlayers, WaveIO, WaveIn, SysUtils, JvDialogs, JvExExtCtrls, JvExtComponent, JvRollOut, VrMeter,
  Vcl.TMSLogging, TMSLoggingEventLogOutputHandler, TMSLoggingUtils, TMSLoggingTextOutputHandler,
  uRiff, uRiffIF;

{$ENDREGION}
{$REGION ' -Types '}

const
  CTempWAV = 'c:\Temp\Temp.WAV';
  CSaveLocation = 'C:\www';
  SETINetSpectrumAnalyzerKey = '\SOFTWARE\SETI Net\SpectrumAnalyzer';
  SETINetFTPKey = '\SOFTWARE\SETI Net\Passwords\FTP'; // Jim - SPACE in SETI Net IS Important...
  CDirID = 0;
  CDir = CDirID + 1;
  CFileID = CDir + 1;
  CFile = CFileID + 1;
  CAlertID = CFile + 1;
  CAlert = CAlertID + 1;

  // COscRubber = 11814; // Rubber the R7000 reported frequency to (close to) the actual 2014-07-17
  COscRubber = 11009; // Rubber the original HackRF 2016-12-15
  // Rubber the R7000 reported frequency to (close to) the actual 2014-07-17

type
  PBufArray = PByteARRAY;

type
  TWaterScale = Record

    Mouse: integer;
    RangeHigh: Double;
  end;

type
  TfrmSpecAna = class(TForm)

    sbSpecAna: TStatusBar;

    ROMessage: TROBinMessage;
    pcPageControl: TPageControl;
    tsSpecAna: TTabSheet;
    pnlControls: TPanel;
    pnlControl: TPanel;
    lblWeight: TLabel;
    lblWater: TLabel;
    nlAmplitude: TNumLab;
    nlAudioFreq: TNumLab;
    lblRunning: TLabel;
    cbWindowing: TComboBox;
    knobVerticalRange: TVrRotarySwitch;
    knobFFTSize: TVrRotarySwitch;
    tsStats: TTabSheet;
    ROServer: TROIndyHTTPServer;

    IdStatusFTP: TIdFTP;
    moSML: TMemo;
    ledBufFilled: TAbLED;
    swWater: TAbToggleSwitch;
    tsLog: TTabSheet;
    moLog: TMemo;
    ledLog: TAbLED;
    FFT: TFastFourier;
    lblFFTSize: TLabel;
    lblInputRange: TLabel;
    AudioIn: TLiveAudioRecorder;
    chart: TRChart;
    nlFrequencyReference: TNumLab;
    pnlDetectionLogic: TJvRollOut;
    pnlDetect: TPanel;
    gbMouse: TGroupBox;
    nlMouseAmbiant: TNumLab;
    nlMouse: TNumLab;
    gbDetectSetup: TGroupBox;
    lblPallettSelection: TLabel;
    lblThreshold: TLabel;
    lblSegSize: TLabel;
    lblIntegration: TLabel;
    lblLine: TLabel;
    nlFrameIndex: TNumLab;
    swShowAmplitudeLimit: TAbToggleSwitch;
    lblAmplitudeLimit: TLabel;
    lblThresholdPercent: TLabel;
    lblShowAmplitudeLimit: TLabel;
    nlLineLimit: TNumLab;
    cbPalettes: TComboBox;
    nsIntegration: TAbNumSpin;
    nsThreshold: TAbNumSpin;
    nsSegSize: TAbNumSpin;
    nsLinesPerFrame: TAbNumSpin;
    nsAmplitudeLimit: TAbNumSpin;
    btnLimitCapture: TJvShapedButton;
    gbET: TGroupBox;
    nlFrequencyET: TNumLab;
    ledETBin: TAbLED;
    nlETAmplitude: TNumLab;
    neETBin: TAbNumEdit;
    btnHelpAbout: TButton;
    ledEOFrame: TAbLED;
    btnRunHitBat: TButton;
    pnlAudioRecording: TJvRollOut;

    pnlAudio: TPanel;
    mtrVu: TVrMeter;
    pbFrameIndex: TJvProgressBar;
    StockAudioPlayer: TStockAudioPlayer;
    SaveDialog: TSaveDialog;
    Format: TComboBox;
    dlgOpen: TOpenDialog;
    recordOffLine: TStockAudioRecorder;
    btnTest: TButton;
    pnlRecord: TPanel;
    btnRecordStop: TBitBtn;
    btnRecordPause: TButton;
    btnRecord: TBitBtn;
    btnSelectRecordToFile: TButton;
    pnlPlayback: TPanel;
    btnPlayStop: TButton;
    btnPlayPause: TBitBtn;
    btnPlay: TBitBtn;
    btnSelectFileToPlay: TButton;
    swVuMeterInput: TToggleSwitch;
    lblVuMeterInput: TLabel;
    recordSETI: TStockAudioRecorder;
    pnlSMLControl: TPanel;
    dlgLoadSML: TOpenDialog;
    btnLoadSML: TButton;
    Panel1: TPanel;
    btnStartLogging: TButton;
    swRun: TAbToggleSwitch;
    btnTagSML: TButton;
    btnClearLog: TButton;
    cbStopOnEOF: TCheckBox;
    cbShowLongHints: TCheckBox;
    cbShowHints: TCheckBox;
    swScan: TAbToggleSwitch;
    lblScan: TLabel;
    ledScan: TAbLED;

    procedure FormCreate(Sender: TObject);
    procedure knobVerticalRangeChange(Sender: TObject);
    procedure cbWindowingSelect(Sender: TObject);
    procedure chartMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
    procedure chartMouseMoveInChart(Sender: TObject; InChart: Boolean; Shift: TShiftState; rMousePosX, rMousePosY: Double);
    procedure mnuExitClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure knobFFTSizeChange(Sender: TObject);
    procedure nsLinesPerFrameValueChanged(Sender: TObject);
    procedure dlgSaveDirClose(Sender: TObject);
    procedure cbPalettesChange(Sender: TObject);
    procedure ledETBinClick(Sender: TObject);
    procedure nsSegSizeValueChanged(Sender: TObject);
    procedure tsStatsMouseEnter(Sender: TObject);
    procedure tsStatsMouseLeave(Sender: TObject);
    procedure nsLinesPerFrameLimit(Sender: TObject);
    procedure btnHelpAboutClick(Sender: TObject);
    procedure swWaterClick(Sender: TObject);

    procedure nsThresholdValueChanged(Sender: TObject);
    procedure nsAmplitudeLimitValueChanged(Sender: TObject);
    procedure btnLimitCaptureClick(Sender: TObject);
    procedure swRunClick(Sender: TObject);
    procedure btnTestClick(Sender: TObject);
    procedure AudioInData(Sender: TObject; const Buffer: Pointer; BufferSize: Cardinal; var FreeIt: Boolean);
    procedure FormDestroy(Sender: TObject);
    procedure AudioInDeactivate(Sender: TObject);
    procedure AudioInActivate(Sender: TObject);
    procedure pnlDetectionLogicCollapse(Sender: TObject);
    procedure pnlDetectionLogicExpand(Sender: TObject);
    procedure btnRunHitBatClick(Sender: TObject);

    function setSML(SML: ANSIstring): Boolean;
    procedure AudioRecorder1Activate(Sender: TObject);
    procedure pnlAudioRecordingCollapse(Sender: TObject);
    procedure pnlAudioRecordingExpand(Sender: TObject);
    procedure btnRecordClick(Sender: TObject);
    procedure btnPlayClick(Sender: TObject);
    procedure btnPlayPauseClick(Sender: TObject);

    procedure StockAudioPlayerActivate(Sender: TObject);
    procedure StockAudioPlayerDeactivate(Sender: TObject);
    procedure StockAudioPlayerLevel(Sender: TObject; Level: integer);
    procedure recordOffLineActivate(Sender: TObject);
    procedure recordOffLineDeactivate(Sender: TObject);
    procedure recordOffLineLevel(Sender: TObject; Level: integer);
    procedure Button1Click(Sender: TObject);
    // procedure btnSelect(Sender: TObject);
    procedure FormatChange(Sender: TObject);
    procedure btnRecordStopClick(Sender: TObject);
    procedure btnSelectFileToPlayClick(Sender: TObject);
    procedure btnPlayStopClick(Sender: TObject);
    procedure btnSelectRecordToFileClick(Sender: TObject);
    procedure btnRecordPauseClick(Sender: TObject);
    procedure swVuMeterInputClick(Sender: TObject);

    procedure FormActivate(Sender: TObject);
    procedure btnLoadSMLClick(Sender: TObject);
    procedure btnStartLoggingClick(Sender: TObject);
    procedure btnTagSMLClick(Sender: TObject);
    procedure btnClearLogClick(Sender: TObject);
    procedure nsIntegrationValueChanged(Sender: TObject);
    procedure cbShowHintsClick(Sender: TObject);
    procedure swScanClick(Sender: TObject);
    procedure recordSETIActivate(Sender: TObject);
    procedure recordSETIDeactivate(Sender: TObject);

  protected
    { protected declarations not accessible externally by class users }
    FLinesPerFrame: integer;
    FRcd: Boolean; // recording or not
    GuardLowPC, GuardHighPC: Single;
    CurrentWAV: String;
    pcm: TPCMFormat;
    FRunning: Boolean;

    FEOFrame: Boolean;
    FoundET: Boolean;

    bufLineSize: integer;

    bufLine: array of integer; // TLine; // One line of Bins
    bufLineLimit: array of integer;

    bufFrame: array of array of integer;
    FCaptureLimit: Boolean;
    FAmplitudeLimit: integer;
    FTest: Boolean;
    version: string;
    FFTPHost: String;
    FFTPUserName: String;
    FFTPPassword: String;
    FAmbiant: Single;
    FFrequencyReference: int64;
    FFrequencyET: int64;
    FSML: ANSIstring; // The SML to be hooked to the wav file
    FThreshold: integer; // alarm point
    ThresholdPercent: Single;
    FIntegration: integer; // Seconds

    FHzPerBin: Single;
    FSegmentSize: integer;

    ndxBufFrame: integer;
    ndxBufLine: integer;
    segmentAve: integer;

    LeftEdge, RightEdge: integer;
    LongScans: Boolean;

    SETINetReg: TRegistry;

    StatsOn: Boolean;

    FVerticalRange: Double;
    FUpdateRate: Single;
    FIIRa: real;

    FWaterScale: TWaterScale;

    OscRubber: integer;
    TCnt: integer;
    DataChunkIndex: integer;
    MousePosX, MousePosY: integer;
    FPeak: Double; // Maximum of integrated signal

    FSamplesPerSec: integer;

    ndxFrame: integer;

    BufferCount: integer;

    procedure DisplayHint(Sender: TObject);
    procedure setVuMeter(value: integer);
    function getLowerGuardpc: Single;
    procedure setLowerGuardpc(value: Single);
    function getUpperGuardpc: Single;
    procedure setUpperGuardpc(value: Single);

    function getReset: Boolean;
    procedure setResetRMT(value: Boolean);
    function getResetRMT: Boolean;
    procedure setReset(value: Boolean);

    function getThreshold: integer;
    function getThresholdRMT: integer;
    procedure setThreshold(value: integer);
    procedure setThresholdRMT(value: integer);

    procedure setAmplitudeLimit(value: integer);
    function getAmplitudeLimit: integer;

    procedure setVerticalRange(value: Double);
    function getVerticalRange: Double;
    procedure setVerticalRangeRMT(value: Double);
    function getVerticalRangeRMT: Double;

    function getLinesPerFrame: integer;
    procedure setLinesPerFrame(value: integer);
    function getLinesPerFrameRMT: integer;
    procedure setLinesPerFrameRMT(value: integer);

    function getIntegration: integer;
    procedure setIntegration(value: integer);
    procedure setIntegrationRMT(value: integer);

    procedure setFFTSize(value: integer);

    procedure setSegmentSize(value: integer);
    function getSegmentSize: integer;
    function getSegmentSizeRMT: integer;
    procedure setSegmentSizeRMT(value: integer);

    function getRunRMT: Boolean;
    procedure setRunRMT(value: Boolean);
    function getRun: Boolean;
    procedure setRun(value: Boolean);

    function getWaterRunRMT: Boolean;
    procedure setWaterRunRMT(value: Boolean);
    function getWaterRun: Boolean;
    procedure setWaterRun(value: Boolean);

    procedure setTerminateRMT(value: Boolean);

    procedure setWaterHeight(value: integer);

    procedure setScan(value: Boolean);
    function getScan: Boolean;
    procedure setScanRMT(value: Boolean);
    function getScanRMT: Boolean;

    procedure setFrequencyReference(value: int64);
    procedure setFrequencyReferenceRMT(value: int64);

    procedure WMsetScan(var MESSAGE: TMessage); message WM_setScan;
    procedure WMsetWaterRun(var MESSAGE: TMessage); message WM_setWATERRUN;
    procedure WMsetRun(var MESSAGE: TMessage); message WM_setRUN;
    procedure WMsetSML(var MESSAGE: TMessage); message WM_SETSML;

    procedure WMsetSnapShot(var MESSAGE: TMessage); message WM_SETSNAPSHOT;
    procedure WMsetReset(var MESSAGE: TMessage); message WM_setReset;
    procedure WMsetTerminate(var MESSAGE: TMessage); message WM_setTERMINATE;
    procedure WMDelFiles(var MESSAGE: TMessage); MESSAGE WM_DelFiles;

    procedure BuildAudioFormatList;

    // procedure saveWAV;
  private
    Riff: IRiff;
    FScan: Boolean;
    FSnapshotDir: string;
    function GetFFTSize: integer;
    procedure DoOutput(Sender: TObject; AOutputInformation: TTMSLoggerOutputInformation);
    function getBinWidth: Single;
    function getBinWidthRMT: Single;
    procedure RunHitBat;

  public
    test: Boolean;

    FWaterfallTop: integer;
    FWaterfallLeft: integer;
    FWaterfallHeight: integer;
    FWaterfallWidth: integer;

    WAVFileNameAtEOFrame: String;
    JPGFileNameAtEOFrame: String;
    bufLineAve: TLine;
    ET: TET;

    function getSML: string;
    function setSMLRMT(value: ANSIstring): Boolean;
    function setDelFiles: Boolean;
    function setDelFilesRMT: Boolean;
    procedure Displaywidths;
    function CreateWAVFileName: String;
    function CreateJPGFileName: String;

    property FFTSizeRMT: integer read GetFFTSize;
    property FFTSize: integer read GetFFTSize;

    Property FrequencyETRMT: int64 read FFrequencyET; // write FFrequencyET;
    property FrequencyET: int64 read FFrequencyET; // write setFrequencyReference;

    Property Ambiant: Single read FAmbiant;

    procedure setSnapShot(value: string);
    function getSnapshot: string;
    procedure setSnapShotRMT(value: string);
    function getSnapShotRMT: string;

    property SamplesPerSec: integer read FSamplesPerSec write FSamplesPerSec;
    property WaterScale: TWaterScale read FWaterScale write FWaterScale;

    property BinWidthRMT: Single read getBinWidthRMT;
    property BinWidth: Single read getBinWidth;

    property SegmentSize: integer read getSegmentSize write setSegmentSize;
    property SegmentSizeRMT: integer read getSegmentSizeRMT write setSegmentSizeRMT;

    property ThresholdRMT: integer read getThresholdRMT write setThresholdRMT;
    property Threshold: integer read getThreshold write setThreshold;

    property Integration: integer read getIntegration write setIntegration;
    property IntegrationRMT: integer read getIntegration write setIntegrationRMT;

    property Water: Boolean read getWaterRun write setWaterRun;
    property WaterRunRMT: Boolean read getWaterRunRMT write setWaterRunRMT;
    property AmplitudeLimit: integer read getAmplitudeLimit write setAmplitudeLimit;

    property TerminateRMT: Boolean write setTerminateRMT;

    property VerticalRange: Double read getVerticalRange write setVerticalRange;
    property VerticalRangeRMT: Double read getVerticalRangeRMT write setVerticalRangeRMT;

    property LinesPerFrame: integer read getLinesPerFrame write setLinesPerFrame;
    property LinesPerFrameRMT: integer read getLinesPerFrameRMT write setLinesPerFrameRMT;

    property ScanRMT: Boolean read getScanRMT write setScanRMT;
    property Scan: Boolean read getScan write setScan;

    property Run: Boolean read getRun write setRun;
    property RunRMT: Boolean read getRunRMT write setRunRMT;

    property Snapshot: string read getSnapshot write setSnapShot;
    property SnapshotRMT: string read getSnapShotRMT write setSnapShotRMT;

    property Reset: Boolean read getReset write setReset;
    property ResetRMT: Boolean read getResetRMT write setResetRMT;

    property FrequencyReference: int64 read FFrequencyReference write setFrequencyReference;
    property FrequencyReferenceRMT: int64 read FFrequencyReference write setFrequencyReferenceRMT;

  end;
{$ENDREGION}

var
  frmSpecAna: TfrmSpecAna;
  WaveStorage: TWaveStorage;
{$ENDREGION}

  //
implementation

{$REGION ' C R E A T E '}
{$R *.dfm}
{$SETPEFLAGS IMAGE_FILE_LARGE_ADDRESS_AWARE}

uses uWaterFall, uTime, uRORTTIServerSupport, uAbout, IOUtils, ShellApi, uRecorderPlayer;

var
  numbuffers: integer = 4;

procedure TfrmSpecAna.Displaywidths;
begin
  // pnlDetectionLogic.Caption := 'Chart: ' + intToStr(chart.Width) + ' pnlControls: ' + intToStr(pnlControls.Width) + ' Logic : ' +
  // intToStr(pnlDetectionLogic.Width) + ' Audio Recording: ' + intToStr(pnlAudioRecording.Width) + ' frm: ' + intToStr(Width);
  // pnlAudioRecording.Caption := 'Chart: ' + intToStr(chart.Width) + ' PnlControls: ' + intToStr(pnlControls.Width) + ' Logic : ' +
  // intToStr(pnlDetectionLogic.Width) + ' Audio Recording: ' + intToStr(pnlAudioRecording.Width) + ' frm: ' + intToStr(Width);
end;

procedure TfrmSpecAna.FormCreate(Sender: TObject);
var
  DisableProcessWindowsGhostingProc: procedure;

begin
  // DisableProcessWindowsGhostingProc := GetProcAddress(GetModuleHandle('user32.dll'), 'DisableProcessWindowsGhosting');
  // if Assigned(DisableProcessWindowsGhostingProc) then
  // DisableProcessWindowsGhostingProc;

  // FFrequencyReference := 1420000000;
  FFrequencyReference := 0000000000;
  // doublebuffered:=true;  //  D O N ' T   D O   T H I S
  SETINetReg := TRegistry.Create;
  SETINetReg.LazyWrite := false;
  try
    SETINetReg.RootKey := HKEY_CURRENT_User;

    (* ================== F T P ================================================= *)
    if SETINetReg.KeyExists(SETINetFTPKey) then
    begin // Key Exists
      if SETINetReg.OpenKey(SETINetFTPKey, false) then
      // This one will NOT create the key
      // OpenKey will create the key if the second parameter is TRUE and the key does not already exist
      begin // N O R M A L   R E G   E N T R I E S
        try // open key
          with SETINetReg do
          begin // Setup the GUI here - All of it
            FFTPHost := ReadString('Host');
            FFTPUserName := ReadString('UserName');
            FFTPPassword := ReadString('Password');

          end // with SETINetReg
        except // an exception is raised if there isn't an entry
          TMSLogger.Error('Password Fail FTP');

        end; // Open Key
      end; // Open key
    end; // SETINetLocalHostkey exists

    if SETINetReg.KeyExists(SETINetSpectrumAnalyzerKey) then
    begin // Key Exists
      if SETINetReg.OpenKey(SETINetSpectrumAnalyzerKey, false) then
      // OpenKey will create the key if the second parameter is TRUE and the key does not already exist
      // This one will NOT create the key
      begin // N O R M A L   R E G   E N T R I E S
        try // open key
          with SETINetReg do
          begin // Setup the GUI here - All of it
            knobFFTSize.SwitchPosition := 0;
            Integration := 0;
            knobFFTSize.SwitchPosition := ReadInteger('knobFFTSize');

            VerticalRange := ReadFloat('VerticalRange');
            FSegmentSize := ReadInteger('SegmentSize');
            LinesPerFrame := ReadInteger('LinesPerFrame');
            nsLinesPerFrame.ValueAsInt := LinesPerFrame;

            GuardLowPC := ReadFloat('LowerGuard');
            GuardHighPC := ReadFloat('UpperGuard');

            if ((GuardLowPC < 0.01) OR (GuardLowPC > 1.0)) then
              GuardLowPC := 0.05;
            if ((GuardHighPC > 1.0) OR (GuardHighPC < 0.1)) then
              GuardHighPC := 0.95;

            chart.CrossHair2.PosX := GuardLowPC * (FFT.SpectrumSize DIV 2);
            chart.CrossHair3.PosX := GuardHighPC * (FFT.SpectrumSize DIV 2);

            frmSpecAna.Left := ReadInteger('Left');
            frmSpecAna.Top := ReadInteger('Top');
            Threshold := ReadInteger('Threshold');
            Integration := ReadInteger('Integration');
            FIIRa := 1 / (Integration * FUpdateRate);

            AmplitudeLimit := ReadInteger('LineLimit');
            FWaterfallTop := ReadInteger('WaterTop');
            FWaterfallLeft := ReadInteger('WaterLeft');

            FWaterfallWidth := ReadInteger('WaterWidth');
            FWaterfallHeight := ReadInteger('WaterHeight');
          end
        except
          // an exception is raised if there isn't a version entry
          SETINetReg.DeleteKey(SETINetSpectrumAnalyzerKey);
          // If the version value didn't exist delete the whole key

        end; // SETINetExamplekey exists
      end;

      if not SETINetReg.KeyExists(SETINetSpectrumAnalyzerKey) then
      // D E F A U L T   R E G   E N T R I E S
      begin // New User or incomplete registry entries so rebuild.
        with SETINetReg do
        begin
          OpenKey(SETINetSpectrumAnalyzerKey, True);
          // OpenKey will create the key if the second parameter is true and the key does not already exist
          nsIntegration.ValueAsInt := 1;
          knobFFTSize.SwitchPosition := 5;
          Threshold := 25;
          Integration := 15;
          VerticalRange := 32768;
          FSegmentSize := 10;
          LinesPerFrame := 666;
          nsLinesPerFrame.ValueAsInt := 1000;
          chart.CrossHair2.PosX := 0.25 * (FFT.SpectrumSize DIV 2);
          chart.CrossHair3.PosX := 0.75 * (FFT.SpectrumSize DIV 2);
          frmSpecAna.Left := 100;
          // Nominal left edge
          frmSpecAna.Top := 50; // Nominal top edge
          AmplitudeLimit := 10;
          FWaterfallTop := 300;
          FWaterfallLeft := 200;
          FWaterfallHeight := 575;
          FWaterfallWidth := 1000;

        end;
      end; // Key exists

    end; // Finished with Reg
  finally // Setup Example
    SETINetReg.Free;
  end;
  TCnt := 0;
  FSML := moSML.Lines.Text; // GeneratedSML in Resource
  OscRubber := COscRubber;
  setFFTSize(knobFFTSize.SwitchPosition);

  nsSegSize.ValueAsInt := SegmentSize;
  ROServer.Active := True;
  LinesPerFrame := nsLinesPerFrame.ValueAsInt;
  pbFrameIndex.Position := 0;
  pbFrameIndex.Max := LinesPerFrame;

  If ((Screen.MonitorCount = 1) AND (Left > 1000)) then
  begin
    Left := 100; // Reset for single monitor
  end;
  FWaterfallLeft := Left;

  FSamplesPerSec := 44100;

  begin
    TMSLogger.RegisterOutputHandlerClass(TTMSLoggerEventLogOutputHandler);
    TMSLogger.OnOutput := DoOutput;
    TMSLogger.Active := false;
    TMSLogger.Clear;
    moLog.Clear;
  end;
  Caption := Caption + '  Port: ' + intToStr(ROServer.Port);
  Caption := Caption + ' ' + intToStr(SizeOf(Pointer) * 8) + ' Bit Platform';
{$IF Defined( EUREKALOG )}
  Caption := Caption + (' + EurekaLog  , ');
  btnStartLoggingClick(Self);
{$IFEND}
  pnlDetectionLogic.Collapsed := True;
  pnlAudioRecording.Collapsed := True;
  Displaywidths;
  frmSpecAna.Width := chart.Width + pnlControl.Width + pnlDetectionLogic.Width + 58;

  FileName := 'Test.WAV';

  btnRecord.Enabled := True;
  // btnPlay.Enabled := FileExists(WaveFile.Caption);
  BuildAudioFormatList;
  Format.ItemIndex := Ord(AudioIn.PCMFormat) - 1;

  btnRecord.Enabled := True;
  // btnPlay.Enabled := FileExists(FileName);

  // Application.HintShortCuts := true;
  // Application.HintColor := clRed;   // color of your choice
  Application.HintPause := 500; // 250 mSec before hint is shown
  Application.HintHidePause := 2000; // hint disappears after 3 secs

  frmSpecAna.BringToFront;
  recordSETI.Stock := WaveStorage;
  recordOffLine.Stock := WaveStorage;
end { Create };

procedure TfrmSpecAna.FormDestroy(Sender: TObject);
begin
  Riff := nil;
end;

procedure TfrmSpecAna.BuildAudioFormatList;
var
  pcm: TPCMFormat;
  WaveFormat: TWaveFormatEx;
begin
  with Format.Items do
  begin
    BeginUpdate;
    try
      Clear;
      for pcm := Succ(Low(TPCMFormat)) to High(TPCMFormat) do
      begin
        SetPCMAudioFormatS(@WaveFormat, pcm);
        Add(GetWaveAudioFormat(@WaveFormat));
      end;
    finally
      EndUpdate;
    end;
  end;
end;

procedure TfrmSpecAna.Button1Click(Sender: TObject);
begin
  with dlgOpen do
    if Execute then
    begin
      ShowMessage('Dude');
    end; // if
end;

{$ENDREGION}
//
{$REGION ' DO Log '}

procedure TfrmSpecAna.DoOutput(Sender: TObject; AOutputInformation: TTMSLoggerOutputInformation);
begin
  moLog.Lines.Insert(0, TTMSLoggerUtils.StripHTML(TTMSLoggerUtils.GetConcatenatedLogMessage(AOutputInformation, false)));
end;
{$ENDREGION}
//
{$REGION ' Create File Names '}

function TfrmSpecAna.CreateJPGFileName: String;
begin
  result := ReplaceStr(WAVFileNameAtEOFrame, '.wav', '.jpg');
end;

function TfrmSpecAna.CreateWAVFileName: String;
var
  tempDIR: string;
begin
  tempDIR := uTime.SETIDate;
  tempDIR := ReplaceStr(tempDIR, '-', '\');
  tempDIR := CCaptureDrive + tempDIR;
  if NOT { System.SysUtils. } ForceDirectories(tempDIR) then
    sbSpecAna.Panels[CDir].Text := tempDIR + '\' + uTime.SETIDateTime + '.wav';
  result := tempDIR + '\' + uTime.SETIDateTime + '.wav';
end;
{$ENDREGION}
//
{$REGION ' get/setRUN '}

procedure TfrmSpecAna.swRunClick(Sender: TObject);

begin

  RunRMT := swRun.Checked;
end;

procedure TfrmSpecAna.swScanClick(Sender: TObject);
begin
  Scan := swScan.Checked;

  end;

procedure TfrmSpecAna.swVuMeterInputClick(Sender: TObject);
begin
  if swVuMeterInput.State = tssOn then
    mtrVu.Caption := 'Record/Playback'
  else
    mtrVu.Caption := 'Spectrum';
end;

procedure TfrmSpecAna.setRun(value: Boolean);
begin
  if value then
  begin // START
    Reset := True;
    ndxFrame := 0;
    knobFFTSize.Enabled := false;
    nsLinesPerFrame.Enabled := false;
    // nsIntegration.OnValueChanged(Self);
    if NOT AudioIn.Active then
    begin
      AudioIn.PCMFormat := TPCMFormat(Format.ItemIndex + 1);
      recordOffLine.PCMFormat := TPCMFormat(Format.ItemIndex + 1);
      recordSETI.PCMFormat := recordOffLine.PCMFormat;
    end;
    TMSLogger.InfoFormat('set Run: {%b }   ', [value]);

    swRun.Checked := True;
    // swDetect.ColorOn := clSilver;
    if swWater.Checked then
      Water := True;

    // Capture := True;
    // Detect := True;

    AudioIn.Active := True;
    // EOFrame := false;

  end
  else
  begin // STOP    knobFFTSize.Enabled := True;
    nsLinesPerFrame.Enabled := True;
    knobFFTSize.Enabled := True;
    swRun.Checked := false;
    Water := false;
    Scan := false;
    ledBufFilled.Checked := false;
    AudioIn.Active := false;
    // EOFrame := True;
    TMSLogger.InfoFormat('setRun: {%b }', [value]);
    pbFrameIndex.Position := 0;
  end;
end;

function TfrmSpecAna.getRun: Boolean;
begin
  result := FRunning;
end;

function TfrmSpecAna.getRunRMT: Boolean;
begin
  result := Run;
  // result := Run;
end;

procedure TfrmSpecAna.setRunRMT(value: Boolean);
begin

  TMSLogger.InfoFormat('setRunMT: {%b}', [value]);
  try
    SendMessage(Handle, WM_setRUN, 0, integer(value));
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;

end;

procedure TfrmSpecAna.WMsetRun(var MESSAGE: TMessage); { message WM_SETRUN_MESSAGE; }

begin
  TMSLogger.InfoFormat('WMsetRun is: {%d}', [MESSAGE.LParam]);

  try
    Run := Boolean(MESSAGE.LParam);
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;

end;

procedure TfrmSpecAna.AudioInActivate(Sender: TObject);
begin
  SetLength(bufLineAve, (FFT.SpectrumSize div 2));
  SetLength(bufFrame, LinesPerFrame, Length(bufLine));
  FRunning := True
end;

procedure TfrmSpecAna.AudioInDeactivate(Sender: TObject);
begin
  SetLength(bufLineAve, 0);
  SetLength(bufFrame, 0, 0); // two dim buffer
  FRunning := false;

end;

procedure TfrmSpecAna.AudioRecorder1Activate(Sender: TObject);
begin

end;

{$ENDREGION}
//
{$REGION ' get/setScan '}
{$ENDREGION}
//
{$REGION ' get/setAmplitudeLimit '}

procedure TfrmSpecAna.setAmplitudeLimit(value: integer);
begin
  FAmplitudeLimit := value;
  nsAmplitudeLimit.ValueAsInt := FAmplitudeLimit;
end;

function TfrmSpecAna.getAmplitudeLimit: integer;
begin
  result := FAmplitudeLimit;
end;

{$ENDREGION}
//
{$REGION ' get/setReset '}

procedure TfrmSpecAna.setReset(value: Boolean);
begin

  with ET do
  begin // Clear out any Hit hanging around
    Hit := false;
    Bin := 0;
    FrequencyET := 0;
    AudioFreq := 0;
    Amplitude := 0;
    Ambiant := 0;
    FFTSize := 0;
    HzPerBin := 0;
    Threshold := 0.0;
    SegmentSize := 0;
    Integration := 0;
    LinesPerFrame := 0;
    WAVFileName := '';
    JPGFileName := '';
    AudioFreq := 0;
  end;
  ndxFrame := 0;
  ledETBin.Checked := false;
end;

function TfrmSpecAna.getReset: Boolean;
begin
  //
end;

function TfrmSpecAna.getResetRMT: Boolean;
begin
  //
end;

Procedure TfrmSpecAna.setResetRMT(value: Boolean);
begin
  try

    SendMessage(Handle, WM_setReset, 0, integer(value));
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error('setReset: ' + E.ClassName + ': ' + E.MESSAGE);
  end;
end;

procedure TfrmSpecAna.WMsetReset(var MESSAGE: TMessage); { message WM_RESETHIT; }
var
  incomming: Boolean;
begin
  try
    incomming := Boolean(MESSAGE.LParam);

  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;

  Reset := incomming;

end;

{$ENDREGION}
//
{$REGION ' get/setLinesperFrame '}

procedure TfrmSpecAna.setLinesPerFrameRMT(value: integer);
begin
  LinesPerFrame := value;
end;

procedure TfrmSpecAna.setLinesPerFrame(value: integer);
begin
  FLinesPerFrame := value;
  pbFrameIndex.Position := 0;
  pbFrameIndex.Max := LinesPerFrameRMT
end;

function TfrmSpecAna.getLinesPerFrame: integer;
begin
  result := FLinesPerFrame
end;

function TfrmSpecAna.getLinesPerFrameRMT: integer;
begin
  result := FLinesPerFrame
end;
{$ENDREGION}
//
{$REGION ' get/setIntegration '}

procedure TfrmSpecAna.nsIntegrationValueChanged(Sender: TObject);
begin
  if (nsIntegration.ValueAsInt < 0) then
    nsIntegration.ValueAsInt := 0;
  if ((nsIntegration.value = 0) or (FUpdateRate = 0)) then
    FIIRa := 1
  else
    FIIRa := 1 / (nsIntegration.value * FUpdateRate);
  if (FIIRa > 1) then
    // Limit the 'a' to less than 1
    FIIRa := 1;
  FIntegration := nsIntegration.ValueAsInt;
end;

function TfrmSpecAna.getIntegration: integer;
begin
  result := nsIntegration.ValueAsInt;
end;

procedure TfrmSpecAna.setIntegration(value: integer);
begin
  nsIntegration.ValueAsInt := value;
end;

procedure TfrmSpecAna.setIntegrationRMT(value: integer);
begin
  ShowMessage('Why setIntegrationRMT');
  Integration := (value);
end;
{$ENDREGION}
//
{$REGION ' DelFiles '}

function TfrmSpecAna.setDelFilesRMT: Boolean;

begin
  try
    SendMessage(Handle, WM_DelFiles, 0, 0);
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;

end;

procedure TfrmSpecAna.WMDelFiles(var MESSAGE: TMessage);

begin
  // TMSLogger.Info('Enter WMDelFiles ');
  try
    // setDelFiles(Boolean(Message.LParam));
    setDelFiles;
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;
  // TMSLogger.Info('Exit WMDelFiles ');
end;

function TfrmSpecAna.setDelFiles: Boolean;
var
  resWAV, resJPG: Boolean;

begin
  TMSLogger.Info('Enter DelFiles ');
  if FileExists(WAVFileNameAtEOFrame) then
  begin
    TMSLogger.Info('Enter DeleteFile(WAVFile  ');
    while FileInUse(WAVFileNameAtEOFrame) do
    begin
      TMSLogger.Info('Enter Delay for  WAVFile busy ');
      Delay(1000);
    end;
    resWAV := DeleteFile(WAVFileNameAtEOFrame);
    TMSLogger.Info('Exit DeleteFile(WAVFile  ')

  end;
  if FileExists(JPGFileNameAtEOFrame) then
  begin
    TMSLogger.Info('Enter DeleteFile(JPGFile  ');
    While FileInUse(JPGFileNameAtEOFrame) do
    begin
      TMSLogger.Info('Enter Delay for JPGFile busy ');
      Delay(1000);
    end;
    resJPG := DeleteFile(PChar(JPGFileNameAtEOFrame));
    TMSLogger.Info('Exit DeleteFile(JPGFile  ')
  end;

  if ((Not resWAV) OR (Not resJPG)) then
    result := false
  else
  begin
    result := True;
  end;
  TMSLogger.Info('Exit DelFiles ');
  TMSLogger.Info('-------------');
end;

{$ENDREGION}
//
{$REGION ' get/setSML '}

function TfrmSpecAna.setSML(SML: ANSIstring): Boolean;

var
  SMLChunkIndex: integer;
  // SML: ANSIString;
begin
  result := false;
  if FileExists(WAVFileNameAtEOFrame) then
  begin
    If Not FileInUse(WAVFileNameAtEOFrame) then
    begin
      Riff := TRiff.Create;
      try
        Riff.LoadFromFile(WAVFileNameAtEOFrame); // Exception here.
      except
        sbSpecAna.Panels[CAlert].Text := 'Error - cannot load file.'; // D:\www\seti_data\2018\05\30
        Riff.Clear;
        exit;
      end;
      SMLChunkIndex := Riff.IndexOf('SML ');
      If (SMLChunkIndex <> -1) then // Kill the SML if it exists
        Riff.DeleteChunkByIndex(SMLChunkIndex);

      Riff.AddChunk('SML ');
      SMLChunkIndex := Riff.IndexOf('SML ');
      Riff.Chunk[SMLChunkIndex].WriteBuffer(Pointer(SML)^, Length(SML));
      // ^   ---- the trick ---   ^
      Riff.SaveToFile(WAVFileNameAtEOFrame);
      sbSpecAna.Panels[CAlert].Text := ' SML Written to File: ' + ' complete. ';

      moSML.Text := SML;
      result := True;
      Riff.Clear;
    end
    else
      sbSpecAna.Panels[CAlert].Text := 'File: ' + FileName + ' IN USE. ';
  end
  else
    sbSpecAna.Panels[CAlert].Text := 'File: ' + FileName + ' Does not exist. ';
end;

function TfrmSpecAna.getSML: String;
begin
  result := FSML;
end;

function TfrmSpecAna.setSMLRMT(value: ANSIstring): Boolean;
var
  LParam: string;
  pLParam: PChar;
begin
  result := True;
  LParam := value;
  Length(LParam);
  GetMem(pLParam, (Length(LParam) + 1) * SizeOf(Char));
  Move(LParam[1], pLParam^, (Length(LParam) + 1) * SizeOf(Char));
  try
    // PostMessage(Handle, WM_SETSML, 0, integer(pLParam));
    SendMessage(Handle, WM_SETSML, 0, integer(pLParam));
  except
    on E: System.SysUtils.Exception do
    begin
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
      result := false;
    end;
  end;
  // if FileExists(WAVFileNameAtEOFrame) then
  // setWavSML(WAVFileNameAtEOFrame);
end;

procedure TfrmSpecAna.WMsetSML(var MESSAGE: TMessage); { message WM_SETSML_MESSAGE; }
var
  pLParam: PChar;

begin
  try
    pLParam := PChar(MESSAGE.LParam);
    begin
      // moSML.Lines.Add(pLParam);
      setSML(pLParam);
    end;
    Freemem(pLParam);
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;

  frmSpecAna.sbSpecAna.Panels[CAlert].Text := 'setSML';

end;
{$ENDREGION}
//
{$REGION ' get/setBinWidth '}

function TfrmSpecAna.getBinWidthRMT: Single;
begin
  result := BinWidth;
end;

function TfrmSpecAna.getBinWidth: Single;
begin
  result := FHzPerBin;
end;

{$ENDREGION}
//
{$REGION ' get/setSegmentSize '}

function TfrmSpecAna.getSegmentSizeRMT: integer;
begin
  result := FSegmentSize;
end;

function TfrmSpecAna.getSegmentSize: integer;
begin
  result := FSegmentSize;
end;

procedure TfrmSpecAna.setSegmentSize(value: integer);
begin
  FSegmentSize := value;
  nsSegSize.ValueAsInt := FSegmentSize;
end;

procedure TfrmSpecAna.setSegmentSizeRMT(value: integer);
begin
  SegmentSize := value;
end;

{$ENDREGION}
//
{$REGION ' get/setWaterRun ' }

procedure TfrmSpecAna.swWaterClick(Sender: TObject);
begin
  Water := swWater.Checked;
end;

procedure TfrmSpecAna.setWaterRun(value: Boolean);
begin
  frmWaterFall.Run := value;
end;

function TfrmSpecAna.getWaterRun: Boolean;
begin
  result := frmWaterFall.Run;
end;

function TfrmSpecAna.getWaterRunRMT: Boolean;
begin
  result := Water;
end;

Procedure TfrmSpecAna.setWaterRunRMT(value: Boolean);
begin
  try
    SendMessage(Handle, WM_setWATERRUN, 0, integer(value));
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;
end;

procedure TfrmSpecAna.WMsetWaterRun(var MESSAGE: TMessage); { message WM_SETWATERRUNNING_MESSAGE; }
var
  Run: Boolean;
begin
  try
    Run := Boolean(MESSAGE.LParam);

  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;
  swWater.Checked := Run;
  If (Water <> Run) Then
    Water := Run
end;

procedure TfrmSpecAna.setWaterHeight(value: integer);
begin
  frmWaterFall.Height := value;
end;
{$ENDREGION}
//
{$REGION ' get/setTerminate '}

Procedure TfrmSpecAna.setTerminateRMT(value: Boolean);
begin
  try
    SendMessage(Handle, WM_setTERMINATE, 0, integer(value));
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;
end;

procedure TfrmSpecAna.WMsetTerminate(var MESSAGE: TMessage);

begin
  ShowMessage('Why setTerminate');
  try
    Run := false;
    Application.Terminate;

  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;

end;
{$ENDREGION}
//
{$REGION ' get/setScan '}

function TfrmSpecAna.getScan: Boolean;
begin
  result := FScan;
end;

procedure TfrmSpecAna.setScan(value: Boolean);
begin

  if value then
  begin

    WAVFileNameAtEOFrame := CreateWAVFileName;
    JPGFileNameAtEOFrame := CreateJPGFileName;
    // recordSETI.Active := True;    // This causes a exception - so don't do it.
    recordSETI.RecordToFile(WAVFileNameAtEOFrame); // Exception thrown here.

  end
  else
    recordSETI.Active := false;
  swScan.Checked := value;

end;

function TfrmSpecAna.getScanRMT: Boolean;
begin
  result := getScan;

end;

procedure TfrmSpecAna.setScanRMT(value: Boolean);
begin
  try
    SendMessage(Handle, WM_setScan, 0, integer(value));
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;

end;

procedure TfrmSpecAna.WMsetScan(var MESSAGE: TMessage) { message WM_SETDECT_MESSAGE };
var
  incomming: Boolean;
begin

  try
    incomming := Boolean(MESSAGE.LParam);

  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;
  if (Scan <> incomming) then
    Scan := incomming;
end;
{$ENDREGION}
//
{$REGION ' get/setThreshold '}

procedure TfrmSpecAna.nsThresholdValueChanged(Sender: TObject);
begin
  if Sender = nsThreshold then
  begin
    Threshold := nsThreshold.ValueAsInt;
  end;
end;

procedure TfrmSpecAna.pnlAudioRecordingCollapse(Sender: TObject);
begin
  frmSpecAna.Width := chart.Width + pnlControl.Width + pnlAudioRecording.Width + pnlDetectionLogic.Width + 27;
  Displaywidths;
end;

procedure TfrmSpecAna.pnlAudioRecordingExpand(Sender: TObject);
begin
  frmSpecAna.Width := chart.Width + pnlControl.Width + pnlAudioRecording.Width + pnlDetectionLogic.Width + 27;
  Displaywidths;
end;

procedure TfrmSpecAna.pnlDetectionLogicCollapse(Sender: TObject);
begin
  frmSpecAna.Width := chart.Width + pnlControl.Width + pnlDetectionLogic.Width + pnlAudioRecording.Width + 27;
  Displaywidths;
end;

procedure TfrmSpecAna.pnlDetectionLogicExpand(Sender: TObject);
begin
  frmSpecAna.Width := chart.Width + pnlControl.Width + pnlDetectionLogic.Width + pnlAudioRecording.Width + 27;
  Displaywidths;
end;

function TfrmSpecAna.getThreshold: integer;
begin
  result := FThreshold;
end;

function TfrmSpecAna.getThresholdRMT: integer;
begin
  result := FThreshold;
end;

procedure TfrmSpecAna.setThresholdRMT(value: integer);
begin
  ShowMessage('Why setThresholdRMT');
  setThreshold(value);
end;

procedure TfrmSpecAna.setThreshold(value: integer);
begin
  FThreshold := value;
  nsThreshold.value := FThreshold;
  ThresholdPercent := value / 100;
end;
{$ENDREGION}
//
{$REGION ' get/setReferenceFrequency '}

procedure TfrmSpecAna.setFrequencyReference(value: int64);
begin

  nlFrequencyReference.Visible := True;
  nlFrequencyReference.value := value;
  FFrequencyReference := value;
end;

procedure TfrmSpecAna.setFrequencyReferenceRMT(value: int64);
begin
  if (FrequencyReference <> value) then
    FrequencyReference := value;
end;
{$ENDREGION}
//
{$REGION ' CollectSnapshot }

function TfrmSpecAna.getSnapshot: string;
begin
  result := FSnapshotDir;
end;

// S E T   S N A P S H O T
procedure TfrmSpecAna.setSnapShot(value: string);
var
  JPGImage: TJPEGImage;
  procedure Bmp2Jpeg(const BmpFileName, JPGFileName: string);
  var
    Bmp: TBitmap;
    Jpg: TJPEGImage;
  begin
    Bmp := TBitmap.Create;
    Jpg := TJPEGImage.Create;
    try
      Bmp.LoadFromFile(BmpFileName);
      Jpg.Assign(Bmp);
      try
        Jpg.SaveToFile(JPGFileName);
      except
        exit;
      end;
    finally
      Jpg.Free;
      Bmp.Free;
    end;
  end;

begin

  if Run then
  begin
    if NOT FileInUse(CSaveLocation + '\specana.bmp') then
    begin
      try
        chart.CopyToBMP(CSaveLocation + '\specana.bmp', false);
      except
        on E: System.SysUtils.Exception do
          TMSLogger.Error(E.ClassName + 'CopyToBMP. ' + E.MESSAGE);
      end;
      if NOT FileInUse(CSaveLocation + '\specana.jpg') then
        Bmp2Jpeg(CSaveLocation + '\specana.bmp', CSaveLocation + '\specana.jpg');
    end;
  end;

end;

function TfrmSpecAna.getSnapShotRMT: string;
begin
  //
end;

procedure TfrmSpecAna.setSnapShotRMT(value: string);
var
  LParam: string;
  pLParam: PChar;
begin // Snapshot

  LParam := value;
  Length(LParam);
  GetMem(pLParam, (Length(LParam) + 1) * SizeOf(Char));
  Move(LParam[1], pLParam^, (Length(LParam) + 1) * SizeOf(Char));
  try
    SendMessage(Handle, WM_SETSNAPSHOT, 0, integer(pLParam));
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;
end;

procedure TfrmSpecAna.WMsetSnapShot(var MESSAGE: TMessage) { message   WM_SETSNAPSHOT_MESSAGE };
var
  pLParam: PChar;

begin

  try
    pLParam := PChar(MESSAGE.LParam);
    begin
      setSnapShot(pLParam);
    end;
    Freemem(pLParam);
  except
    on E: System.SysUtils.Exception do
      TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
  end;
  TMSLogger.Info('WMSetSnapShot  ');
end;

{$ENDREGION}
//
{$REGION 'S P E C   S I Z E '}

procedure TfrmSpecAna.setFFTSize(value: integer);
begin
  chart.ActiveLayer := 1;
  case value of
    0:
      begin
        FFT.SpectrumSize := 256;
        FUpdateRate := 160;
      end;
    1:
      begin
        FFT.SpectrumSize := 512;
        FUpdateRate := 80;
      end;
    2:
      begin
        FFT.SpectrumSize := 1024;
        FUpdateRate := 40;
      end;
    3:
      begin
        FFT.SpectrumSize := 2048;
        FUpdateRate := 20;
      end;
    4:
      begin
        FFT.SpectrumSize := 4096;
        FUpdateRate := 10;
      end;
    5:
      begin
        FFT.SpectrumSize := 8192;
        FUpdateRate := 5;
      end;
    6:
      begin
        FFT.SpectrumSize := 16384;
        FUpdateRate := 2.5;
      end;
    7:
      begin
        FFT.SpectrumSize := 32768;
        FUpdateRate := 1.25;
      end;
    8:
      begin
        FFT.SpectrumSize := 64;
        // Test Entry
        FUpdateRate := 160;
      end
  else
    TMSLogger.Error('Error in setFFT size');
  end;

  chart.Scale1X.RangeHigh := FFT.SpectrumSize DIV 2;

  FHzPerBin := 22000 / (FFT.SpectrumSize / 2);
  lblFFTSize.Caption := 'FFT Size (' + FloatToStrF(22000 / (FFT.SpectrumSize / 2), ffNumber, 4, 1) + ' Hz/Bin';

  SetLength(bufLine, 0);
  SetLength(bufLine, (FFT.SpectrumSize div 2));
  SetLength(bufLineLimit, 0);
  SetLength(bufLineLimit, (FFT.SpectrumSize div 2));

  chart.CrossHair2.PosX := (FFT.SpectrumSize DIV 2) * GuardLowPC;
  chart.CrossHair3.PosX := (FFT.SpectrumSize DIV 2) * GuardHighPC;
  nsIntegrationValueChanged(Self);
  // knobFFTSize.SwitchPosition := value;
end;
{$ENDREGION}
//
{$REGION ' V E R T I C A L   R A N G E '}

procedure TfrmSpecAna.knobVerticalRangeChange(Sender: TObject);
begin
  VerticalRange := Power(2, (20 - knobVerticalRange.SwitchPosition));
  // 2^(20-SwPosition)
end;

function TfrmSpecAna.getVerticalRange: Double;
begin
  result := FVerticalRange;
end;

function TfrmSpecAna.getVerticalRangeRMT: Double;
begin
  result := FVerticalRange;
end;

procedure TfrmSpecAna.setVerticalRangeRMT(value: Double);
begin
  setVerticalRange(value);
end;

procedure TfrmSpecAna.setVerticalRange(value: Double);
var
  VertRangeIn: integer;
  procedure setKnobPosition(RangeIn: integer);
  var
    posdub: Single;
    pos: integer;
  begin
    posdub := Round(Log10(RangeIn) / Log10(2));
    // SwPos := 20 -(log RangeIn / Log 2)
    pos := 20 - Trunc(posdub);
    knobVerticalRange.SwitchPosition := pos;
  end;

begin
  with chart do
  begin
    chart.ActiveLayer := 1;
    VertRangeIn := Round(value); // 2^(20-SwPosition)
    case VertRangeIn of
      1048576:
        begin
          // 1048576:
          with Scale1Y do
          begin
            RangeHigh := 16000; // chartSpectrum.Scale1Y.RangeHigh := 16000;
            RangeLow := 0;
            DecPlaces := 2;
          end;
          with Scale2Y do
          begin
            Caption := '(V)';
            RangeHigh := 1.024;
            RangeLow := 0.00;
            DecPlaces := 2;
          end;
        end;
      524288:
        begin
          // 524288
          with Scale1Y do
          begin
            RangeHigh := 8000;
            RangeLow := 0;
            DecPlaces := 2;
          end;
          with Scale2Y do
          begin
            Caption := '(mV)';
            RangeHigh := 512;
            RangeLow := 0;
            DecPlaces := 0;
          end;
        end;
      262144:
        begin // 262144
          with Scale1Y do
          begin
            RangeHigh := 4000;
            RangeLow := 0;
            DecPlaces := 2;
          end;
          with Scale2Y do
          begin
            Caption := '(mV)';
            RangeHigh := 256;
            RangeLow := 0;
            DecPlaces := 0;
          end;
        end;
      131072:
        begin // 131072
          with Scale1Y do
          begin
            RangeHigh := 2000;
            RangeLow := 0;
            DecPlaces := 2;
          end;
          with Scale2Y do
          begin
            Caption := '(mV)';
            RangeHigh := 128;
            RangeLow := 0;
            DecPlaces := 0;
          end;
        end;
      65536:
        begin // 65536
          with Scale1Y do
          begin
            RangeHigh := 1000;
            RangeLow := 0;
            DecPlaces := 2;
          end;
          with Scale2Y do
          begin
            Caption := '(mV)';
            RangeHigh := 64;
            RangeLow := 0;
            DecPlaces := 0;
          end;
        end;
      32768:
        begin // 32768
          with Scale1Y do
          begin
            RangeHigh := 500;
            RangeLow := 0;
            DecPlaces := 2;
          end;
          with Scale2Y do
          begin
            Caption := '(mV)';
            RangeHigh := 32;
            RangeLow := 0;
            DecPlaces := 0;
          end;
        end;
      16384:
        begin // 16384
          with Scale1Y do
          begin
            RangeHigh := 250;
            RangeLow := 0;
            DecPlaces := 2;
          end;
          with Scale2Y do
          begin
            Caption := '(mV)';
            RangeHigh := 16;
            RangeLow := 0;
            DecPlaces := 0;
          end;
        end;
      8192:
        begin // 8192
          with Scale1Y do
          begin
            RangeHigh := 125;
            RangeLow := 0;
            DecPlaces := 2;
          end;
          with Scale2Y do
          begin
            Caption := '(mV)';
            RangeHigh := 8;
            RangeLow := 0;
            DecPlaces := 0;
          end;
        end;
      4096:
        begin // 4096
          with Scale1Y do
          begin
            RangeHigh := 63;
            RangeLow := 0;
            DecPlaces := 2;
          end;
          with Scale2Y do
          begin
            Caption := '(mV)';
            RangeHigh := 4;
            RangeLow := 0;
            DecPlaces := 2;
          end;
        end;
      2048:
        begin // 2048
          with Scale1Y do
          begin
            RangeHigh := 31;
            RangeLow := 0;
            DecPlaces := 2;
          end;
          with Scale2Y do
          begin
            Caption := '(mV)';
            RangeHigh := 2;
            RangeLow := 0;
            DecPlaces := 2;
          end;
        end;
      1024:
        begin // 1024
          with Scale1Y do
          begin
            RangeHigh := 16;
            RangeLow := 0;
            DecPlaces := 3;
          end;
          with Scale2Y do
          begin
            Caption := '(uV)';
            RangeHigh := 1024;
            RangeLow := 0;
            DecPlaces := 0;
          end;
        end;
      512:
        begin // 512
          with Scale1Y do
          begin
            RangeHigh := 8;
            RangeLow := 0;
            DecPlaces := 3;
          end;
          with Scale2Y do
          begin
            Caption := '(uV)';
            RangeHigh := 512;
            RangeLow := 0;
            DecPlaces := 0;
          end;
        end;
    else
      begin

        FVerticalRange := 1048576;
        exit;
      end
    end;

    FWaterScale.RangeHigh := chart.Scale1Y.RangeHigh;
    chart.CrossHair2.PosY := WaterScale.RangeHigh;
    FVerticalRange := value;

    try // Setup the Line Limit buffer
      SetLength(bufLineLimit, 0);
      SetLength(bufLineLimit, (FFT.SpectrumSize div 2));
    Except
      on E: System.SysUtils.Exception do
        TMSLogger.Error(E.ClassName + ': ' + E.MESSAGE);
    end;
    setKnobPosition(VertRangeIn);
  end;
end;
{$ENDREGION}
//
{$REGION ' A U D I O   I N '}

procedure TfrmSpecAna.AudioInData(Sender: TObject; const Buffer: Pointer; BufferSize: Cardinal; var FreeIt: Boolean);
Var
  SP: ^SmallInt;
  i, ndxBin, N, v, j: integer;
  SegAveLow, SegAveHi: integer;
  Y, Z: integer;
  FreqOffset: Double;
  intFreqOffset: int64;
  intFrequencyET: int64;
  currentAve: integer;
  currentLimit: integer;
  X: integer;
  WaveFormatSize: integer;
  WaveFormat: PWaveFormatEx;
  TstBuf: Array [0 .. 1000] of byte;
  tstIndx: ^SmallInt;
begin
  // WaveFormat :=  AudioIn.PCMFormat ;
  LeftEdge := SegmentSize + Trunc(chart.CrossHair2.PosX) { + BinShift };
  RightEdge := Trunc(chart.CrossHair3.PosX) - SegmentSize;
  ledBufFilled.Checked := Not ledBufFilled.Checked;

  N := BufferSize Div 2;
  SP := Pointer(Buffer);

  tstIndx := Pointer(Buffer);
  for i := 0 to 1000 do
  begin
    TstBuf[i] := tstIndx^;
    inc(tstIndx);

  end;

  FFT.Clear;
  for i := 1 to N do
  // Second - Move the buffer into the FFT
  begin
    v := SP^;
    FFT.RealSpec[i] := v;
    inc(SP);
  end;
  // ============= DO THE  F F T  F F T   F F T  F F T   F F T  F F T
  FFT.Transform;
  // =============

  chart.SuppressPaint := True;
  chart.ClearGraf; // initialize chart
  // chart.ActiveLayer := 1;
  chart.DataColor := clBlue;
  for ndxBin := 0 to (FFT.SpectrumSize div 2) do // Only the left half of the buffer is good to use
  begin
    bufLine[ndxBin] := Trunc((FIIRa * FFT.Magnitude[ndxBin]) + ((1 - FIIRa) * bufLine[ndxBin]));
    // bufLine[ndxBin] := Trunc((FIIRa * FFT.RealSpec[ndxBin]) + ((1 - FIIRa) * bufLine[ndxBin  ]));
    chart.DrawTo(ndxBin + 1, bufLine[ndxBin]);
  end;
  //
  if FCaptureLimit then
  begin
    for ndxBin := 0 to FFT.SpectrumSize div 2 do
      bufLineLimit[ndxBin { + BinShift } ] := AmplitudeLimit + bufLine[ndxBin];
    btnLimitCapture.Color := clSkyBLue;
    FCaptureLimit := false;
  end;

  if swShowAmplitudeLimit.Checked then
  begin
    chart.DataColor := clRed;
    for ndxBin := 0 to FFT.SpectrumSize div 2 do
    // Only the left half of the buffer is good to use
    begin
      // bufLineLimit[ndxBin + BinShift] := Trunc((FIIRa * FFT.Magnitude[ndxBin]) + ((1 - FIIRa) * bufLineLimit[ndxBin + BinShift]));
      chart.DrawTo(ndxBin + 1, bufLineLimit[ndxBin]);
    end;
  end;
  chart.SuppressPaint := false; // chart.ShowGraf;  Replaced by SuppressPaint

  if (frmWaterFall.Run) then // Fifth - Send the FFT to the Waterfall
    frmWaterFall.FillLine(bufLine);

  if Scan then
  begin
    i := Length(bufLine);
    for i := 0 to Length(bufLine) - 1 do
      bufFrame[ndxFrame, i] := bufLine[i];
    ndxFrame := ndxFrame + 1;
    nlFrameIndex.value := ndxFrame;
    pbFrameIndex.Position := ndxFrame;

    if (ndxFrame >= Length(bufFrame)) then // E N D   O F   S C A M       E N D   O F  S C A M
    begin

      Scan := false;
      if cbStopOnEOF.Checked then
      begin
        Run := false; // Shut down for the control panel
        Scan := false;

        Water := false;
      end;
      ledETBin.Checked := false;

      for Z := 0 to Length(bufFrame) - 1 do
        for Y := 0 to Length(bufLine) - 1 do
          bufLineAve[Y] := bufLineAve[Y] + bufFrame[Z, Y];
      // add up all the values in columns save in BufLineAve
      // Sum the bins by columns

      for Y := 0 to Length(bufLine) - 1 do
        bufLineAve[Y] := bufLineAve[Y] DIV Length(bufFrame); // Calculate the average weight of each bin

      for i := LeftEdge to RightEdge do
      begin
        SegAveLow := 0;
        for j := i - SegmentSize to i - 1 do
          SegAveLow := SegAveLow + bufLineAve[j];
        SegAveLow := SegAveLow DIV SegmentSize;

        SegAveHi := 0;
        for j := i + 1 to i + SegmentSize do
          SegAveHi := SegAveHi + bufLineAve[j];

        SegAveHi := SegAveHi Div SegmentSize;
        segmentAve := (SegAveLow + SegAveHi) div 2;
        currentAve := bufLineAve[i];
        currentLimit := bufLineLimit[i];

        if NOT(swShowAmplitudeLimit.Checked) then
          If (currentAve > (segmentAve + (segmentAve * (ThresholdPercent)))) then
            FoundET := True
          else
            FoundET := false;

        if (swShowAmplitudeLimit.Checked) then
          if (currentAve > (segmentAve + (segmentAve * (ThresholdPercent)))) then
          begin
            if (currentAve < currentLimit) then
              FoundET := True
            else
              FoundET := false;
          end;
        ET.WAVFileName := ExtractFileName(WAVFileNameAtEOFrame);
        if Water then
          ET.JPGFileName := ExtractFileName(JPGFileNameAtEOFrame);
        if FoundET then
        begin
          // =================   F O U N D   E.T.  =================
          ET.Hit := True;
          ledETBin.Checked := True;
          // saveWAV;
          with ET do
          begin
            Hit := True;
            ET.Bin := i;
            FreqOffset := FFT.FreqOfLine(ET.Bin, 1 / FSamplesPerSec);

            FFrequencyET := FrequencyReference + Trunc(FreqOffset);
            nlFrequencyET.value := FFrequencyET;

            ET.FrequencyET := FFrequencyET;

            ET.AudioFreq := Round(ET.Bin * FHzPerBin);

            ET.Amplitude := bufLineAve[i];

            frmSpecAna.SegmentSize := FSegmentSize;
            frmSpecAna.Integration := FIntegration;
            frmSpecAna.LinesPerFrame := FLinesPerFrame;

            sbSpecAna.Panels[CFile].Text := WAVFileName;
          end;
          nlETAmplitude.value := ET.Amplitude;

          neETBin.ValueAsInt := ET.Bin;

          ndxFrame := 0;
          RunHitBat;
          break;
          // Found ET so stop scanning left to right in the average column
        end
        else
        begin
          ET.Hit := false;

        end;
      end; // Left to Right

      BufferCount := (0);
      ndxFrame := 0;
      FoundET := false;

    end; // end of frame

  end; // if detect
  // FreeIt := true;   // WTF Jim
end;

// procedure TfrmSpecAna.saveWAV;
// var
// FiletoSave: ANSIString;
// // WAVFileName: Ansistring;
// tempDIR: ANSIString;
// begin
//
// WAVFileNameAtEOFrame := uTime.SETIDateTime + '.WAV';
// ET.WAVFile := WAVFileNameAtEOFrame; //
// tempDIR := CCaptureDrive + uTime.SETIDate;
// tempDIR := ReplaceStr(tempDIR, '-', '\');
// // if NOT DirectoryExists(CCaptureDrive + '\' + ReplaceStr(uTime.SETIDate, '-', '\')) then
// // ForceDirectories(CCaptureDrive + '\' + ReplaceStr(uTime.SETIDate, '-', '\'));
// if NOT DirectoryExists(tempDIR) then
// if SysUtils.ForceDirectories(tempDIR) then
// // ShowMessage('Dir: ' + tempDIR + ' added alright')
// else
// ShowMessage('Dir: ' + tempDIR + ' add FAILED');
//
// FiletoSave := CCaptureDrive + '\' + ReplaceStr(uTime.SETIDate, '-', '\') + '\' + WAVFileNameAtEOFrame;
//
// RiffWrite.AddChunk('SML ').WriteBuffer(Pointer(FSML)^, Length(FSML));
// RiffWrite.SaveToFile(FiletoSave);
//
// RiffWrite.Clear; // Clear Buffer for next file
// RiffWrite := CreateRiffRW;
// RiffWrite.FourCC := 'WAVE';
// with RiffWrite.AddChunk('fmt ') do
// Write(AudioIn.FWaveFmtEx, SizeOf(AudioIn.FWaveFmtEx));
// RiffWrite.AddChunk('data');
// DataChunkIndex := RiffWrite.IndexOf('data');
// end;
{$ENDREGION}
//
{$REGION ' A U D I O   R e c o r d / P l a y '}

procedure TfrmSpecAna.setVuMeter(value: integer);
begin
  mtrVu.Position := value;
end;

procedure TfrmSpecAna.btnSelectRecordToFileClick(Sender: TObject);
begin
  WAVFileNameAtEOFrame := CreateWAVFileName;
  JPGFileNameAtEOFrame := CreateJPGFileName;
  SaveDialog.InitialDir := ExtractFileDir(WAVFileNameAtEOFrame);
  SaveDialog.FileName := ExtractFileName(WAVFileNameAtEOFrame);
  if SaveDialog.Execute(Handle) then
  begin
    btnRecord.Enabled := True;
    WAVFileNameAtEOFrame := SaveDialog.FileName;
  end;
end;

procedure TfrmSpecAna.btnStartLoggingClick(Sender: TObject);
begin
  TMSLogger.Active := Not TMSLogger.Active;
  if TMSLogger.Active then
  begin

    TMSLogger.LogSystemInformation;
    TMSLogger.Info('Start Logging');
    ledLog.Checked := True;
    btnStartLogging.Caption := 'Logging ON';
  end
  else
  begin
    btnStartLogging.Caption := 'Logging OFF';
    ledLog.Checked := false;
  end;

end;

procedure TfrmSpecAna.btnRecordClick(Sender: TObject);
begin
  recordOffLine.RecordToFile(SaveDialog.FileName);
  sbSpecAna.Panels[CDir].Text := 'Record: ' + SaveDialog.FileName;
  Run := True;
  pnlControl.Enabled := false;
  // swRecord.Enabled := false;
  // swDetect.Enabled := false;

end;

procedure TfrmSpecAna.btnRecordPauseClick(Sender: TObject);
begin
  if recordOffLine.Active then
    recordOffLine.Paused := not recordOffLine.Paused
end;

procedure TfrmSpecAna.recordOffLineActivate(Sender: TObject);
begin

  Format.Enabled := false;
  btnSelectRecordToFile.Enabled := false;
  btnRecordPause.Enabled := True;
  btnRecordStop.Enabled := True;
  btnRecord.Enabled := false;
  btnPlay.Enabled := false;
  pnlPlayback.Enabled := false;
  btnRecordStop.SetFocus;
end;

procedure TfrmSpecAna.recordOffLineDeactivate(Sender: TObject);
begin
  Format.Enabled := True;
  pnlPlayback.Enabled := True;
  btnSelectRecordToFile.Enabled := True;
  btnSelectFileToPlay.Enabled := True;
  btnRecord.Enabled := True;

  btnRecordPause.Enabled := false;
  btnRecordStop.Enabled := false;

  if FileExists(WAVFileNameAtEOFrame) then
    setSML(WAVFileNameAtEOFrame);

  if Water then
  begin
    frmWaterFall.SaveJPG;

  end;

  sbSpecAna.Panels[CFile].Text := '';
  sbSpecAna.Panels[CDir].Text := '';
  pnlControl.Enabled := True;
  // swRecord.Enabled := True;
  // swDetect.Enabled := True;
end;

procedure TfrmSpecAna.recordOffLineLevel(Sender: TObject; Level: integer);
begin
  sbSpecAna.Panels[CFile].Text := MS2Str(recordOffLine.Position, msAh) + ' Sec.';
  if swVuMeterInput.State = tssOn then
    setVuMeter(Level);
end;

procedure TfrmSpecAna.recordSETIActivate(Sender: TObject);
begin
  sbSpecAna.Panels[CDir].Text := WAVFileNameAtEOFrame;
  FScan := True;
  ledScan.Checked := FScan;
  // TMSLogger.InfoFormat('RecordSETIActivate: {%b }', [True]);
end;

procedure TfrmSpecAna.recordSETIDeactivate(Sender: TObject);
begin
  FScan := false;
  if Water then

    frmWaterFall.SaveJPG;
  ledScan.Checked := FScan;
end;

procedure TfrmSpecAna.btnPlayPauseClick(Sender: TObject);
begin
  if StockAudioPlayer.Active then
    StockAudioPlayer.Paused := not StockAudioPlayer.Paused
end;

procedure TfrmSpecAna.btnPlayClick(Sender: TObject);
begin
  StockAudioPlayer.PlayFile(dlgOpen.FileName);
end;

procedure TfrmSpecAna.btnPlayStopClick(Sender: TObject);
begin
  if StockAudioPlayer.Active then
    StockAudioPlayer.Active := false
end;

procedure TfrmSpecAna.btnSelectFileToPlayClick(Sender: TObject);
begin
  if dlgOpen.Execute then
  begin
    btnPlay.Enabled := FileExists(dlgOpen.FileName);
    btnPlay.SetFocus;
  end;
end;

procedure TfrmSpecAna.StockAudioPlayerActivate(Sender: TObject);
begin
  Format.Enabled := false;
  btnSelectRecordToFile.Enabled := false;
  btnPlayPause.Enabled := True;
  btnPlayStop.Enabled := True;
  btnRecord.Enabled := false;
  btnPlay.Enabled := false;
  sbSpecAna.Panels[CDir].Text := 'Playing: ' + dlgOpen.FileName;
  pnlRecord.Enabled := false;
  btnPlayStop.SetFocus;
end;

procedure TfrmSpecAna.StockAudioPlayerDeactivate(Sender: TObject);
begin
  Format.Enabled := True;
  btnSelectRecordToFile.Enabled := True;
  btnRecord.Enabled := True;
  btnPlay.Enabled := True;
  btnPlayPause.Enabled := false;
  btnPlayStop.Enabled := false;
  sbSpecAna.Panels[CDir].Text := '';
  sbSpecAna.Panels[CFile].Text := '';
  pnlRecord.Enabled := True;
  btnPlay.SetFocus;
end;

procedure TfrmSpecAna.StockAudioPlayerLevel(Sender: TObject; Level: integer);
begin
  sbSpecAna.Panels[CFile].Text := MS2Str(StockAudioPlayer.Position, msAh) + ' Sec.';
  if swVuMeterInput.State = tssOn then
    setVuMeter(Level);
end;

{$ENDREGION}
//
{$REGION ' U I  B U T T O N S '}

function TfrmSpecAna.GetFFTSize: integer;
begin
  result := FFT.SpectrumSize;
end;

function TfrmSpecAna.getLowerGuardpc: Single;
begin

  // result := chart.CrossHair2.PosX / (FFT.SpectrumSize DIV 2);
end;

procedure TfrmSpecAna.setLowerGuardpc(value: Single);
var
  test: Single;
begin
  // Test := value * (FFT.SpectrumSize DIV 2);
  //
  // if value < 0.05 then
  // chart.CrossHair2.PosX := 0.07 * (FFT.SpectrumSize DIV 2)
  // else
  // chart.CrossHair2.PosX := value * (FFT.SpectrumSize DIV 2);
end;

function TfrmSpecAna.getUpperGuardpc: Single;
begin
  // result := chart.CrossHair3.PosX / (FFT.SpectrumSize DIV 2);
end;

procedure TfrmSpecAna.setUpperGuardpc(value: Single);
begin
  // if value > 0.95 then
  // chart.CrossHair3.PosX := 0.9 * (FFT.SpectrumSize DIV 2)
  // else
  // chart.CrossHair3.PosX := value * (FFT.SpectrumSize DIV 2);

end;

procedure TfrmSpecAna.knobFFTSizeChange(Sender: TObject);
begin
  setFFTSize(knobFFTSize.SwitchPosition);

end;

procedure TfrmSpecAna.nsAmplitudeLimitValueChanged(Sender: TObject);
begin
  AmplitudeLimit := nsAmplitudeLimit.ValueAsInt;
end;

procedure TfrmSpecAna.nsLinesPerFrameLimit(Sender: TObject);
begin
  // LinesPerFrame := 1000;
  // Beep;
end;

procedure TfrmSpecAna.nsLinesPerFrameValueChanged(Sender: TObject);
begin
  LinesPerFrame := nsLinesPerFrame.ValueAsInt;

end;

procedure TfrmSpecAna.nsSegSizeValueChanged(Sender: TObject);
begin
  SegmentSize := frmSpecAna.nsSegSize.ValueAsInt;
end;

procedure TfrmSpecAna.ledETBinClick(Sender: TObject);
begin
  ResetRMT := True; // Reset the alarm
end;

(* ==== S A V E   W A V  =============== *)

procedure TfrmSpecAna.cbPalettesChange(Sender: TObject);
type
  TMember = record
    R: byte;
    G: byte;
    B: byte;
  end;
var
  i: integer;
  Tokens: TOpenStringArray;
var
  List: TStringList;
  Stream: TResourceStream;
  cbItem: string;

begin
  cbItem := cbPalettes.Items[cbPalettes.ItemIndex];
  Stream := TResourceStream.Create(HInstance, cbItem, RT_RCDATA);
  try
    List := TStringList.Create;
    try
      List.LoadFromStream(Stream);
      i := List.Count - 1;
      while i > 0 do

      begin

        If (Tokenize(List[i], ' ', Tokens) = 3) then
        begin
          frmWaterFall.Palette[i].rgbtRed := byte(StrToInt(Tokens[0]));
          frmWaterFall.Palette[i].rgbtGreen := byte(StrToInt(Tokens[1]));
          frmWaterFall.Palette[i].rgbtBlue := byte(StrToInt(Tokens[2]));
          Dec(i);
        end
        else
        begin
          TMSLogger.Error('Palette File formatted incorrectly.');
          exit;
        end;
      end;
    finally
      List.Free
    end;
  finally
    Stream.Free;
  end;

end;

procedure TfrmSpecAna.cbWindowingSelect(Sender: TObject);
begin
  FFT.WeightingWindow := TFastFourierWgtWin(cbWindowing.ItemIndex);
end;

procedure TfrmSpecAna.chartMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
var
  percent: real;
begin
  if Run then
  begin
    if Button = mbRight then
    begin
      chart.CrossHair4.PosY := MousePosY;
      FWaterScale.RangeHigh := chart.CrossHair4.PosY;
    end;
    if Button = mbLeft then
    begin
      percent := ((MousePosX - 20) / (FFTSize DIV 2));
      // nlTest.value := frmWaterFall.HorzScrollBar.Size;
      frmWaterFall.HorzScrollBar.Position := Trunc(frmWaterFall.ImageRGBWidth * percent);
    end;
  end;
end;

procedure TfrmSpecAna.chartMouseMoveInChart(Sender: TObject; InChart: Boolean; Shift: TShiftState; rMousePosX, rMousePosY: Double);

begin
  if InChart then
  begin

    MousePosX := Round(rMousePosX);
    MousePosY := Trunc(rMousePosY);
    chart.CrossHair4.PosX := rMousePosX;
    chart.CrossHair4.PosY := rMousePosY;

    nlAudioFreq.value := FFT.FreqOfLine(MousePosX, 1 / FSamplesPerSec); //
    nlFrequencyReference.value := FrequencyReference + nlAudioFreq.value;

    frmWaterFall.FMouseFreq := Trunc(nlAudioFreq.value);
    nlAmplitude.value := FFT.Magnitude[MousePosX];
    if Run then
      nlMouseAmbiant.value := frmSpecAna.bufLineAve[MousePosX];

    if swVuMeterInput.State = tssOff then
      // setVuMeter(Trunc(FFT.Magnitude[MousePosX]));
      // setVuMeter(Trunc(FFT.PowerSpec[MousePosX]));
      // setVuMeter(Trunc(FFT.RealSpec[MousePosX]));
      setVuMeter(Trunc(FFT.RMS(MousePosX - 5, MousePosX + 5)));
    // nlTest.value := MousePosX;
    nlMouse.value := MousePosX;

  end;
end;

procedure TfrmSpecAna.dlgSaveDirClose(Sender: TObject);
begin
  // sbSpecAna.Panels[CDir].Text := MinimizeName(ReplaceStr(uTime.SETIDate, '-', '\'), sbSpecAna.Canvas, 250);
  // sbSpecAna.Panels[CFile].Text := MinimizeName(uTime.SETITime, sbSpecAna.Canvas, 250);
end;

procedure TfrmSpecAna.mnuExitClick(Sender: TObject);
begin
  Close
end;

procedure TfrmSpecAna.tsStatsMouseEnter(Sender: TObject);
begin
  StatsOn := True;
end;

procedure TfrmSpecAna.tsStatsMouseLeave(Sender: TObject);
begin
  StatsOn := false;
end;

procedure TfrmSpecAna.btnClearLogClick(Sender: TObject);
begin
  moLog.Clear;
end;

procedure TfrmSpecAna.btnHelpAboutClick(Sender: TObject);
begin
  frmAbout.ShowVersion;
end;

procedure TfrmSpecAna.btnLimitCaptureClick(Sender: TObject);
begin
  FCaptureLimit := True;
  btnLimitCapture.Color := clRed;
end;

procedure TfrmSpecAna.btnLoadSMLClick(Sender: TObject);
begin
  if dlgLoadSML.Execute then
  begin
    moSML.Lines.LoadFromFile(dlgLoadSML.FileName);
  end;
end;

procedure TfrmSpecAna.RunHitBat;
var
  BatName: string;
begin
  BatName := 'HitBat.bat';
  if FileExists(BatName) then
  begin
    ShellExecute(Application.Handle, 'open', PChar(BatName), '', '', SW_SHOWNORMAL);
  end
  else
  begin
    sbSpecAna.Panels[CAlert].Text := 'Cannot find ' + BatName + ' in the installation dir '
  end;
end;

procedure TfrmSpecAna.btnRunHitBatClick(Sender: TObject);

begin
  RunHitBat;
end;

procedure TfrmSpecAna.btnRecordStopClick(Sender: TObject);
begin
  recordOffLine.Active := false;

end;

procedure TfrmSpecAna.btnTagSMLClick(Sender: TObject);
begin
  setSML(WAVFileNameAtEOFrame);
end;

procedure CropBitmap(InBitmap, OutBitMap: TBitmap; X, Y, W, H: integer);
begin
  OutBitMap.PixelFormat := InBitmap.PixelFormat;
  OutBitMap.Width := W;
  OutBitMap.Height := H;
  BitBlt(OutBitMap.Canvas.Handle, 0, 0, W, H, InBitmap.Canvas.Handle, X, Y, SRCCOPY);
end;

procedure TfrmSpecAna.btnTestClick(Sender: TObject);
var
  TestStr: ANSIstring;

begin
  // function TfrmSpecAna.setSML(SML: ANSIstring): Boolean;
  TestStr := 'Now is the time for all good men...';
  setSML(TestStr);

end;

{$ENDREGION}
//
{$REGION ' C L O S E  '}

procedure TfrmSpecAna.FormatChange(Sender: TObject);
begin
  recordOffLine.PCMFormat := TPCMFormat(Format.ItemIndex + 1);
end;

procedure TfrmSpecAna.FormClose(Sender: TObject; var Action: TCloseAction);

begin
  Run := false;
  AudioIn.Active := false;
  SETINetReg := TRegistry.Create;
  // S A V E  R E G   E N T R I E S
  try
    with SETINetReg do
    begin
      RootKey := HKEY_CURRENT_User;
      DeleteKey(SETINetSpectrumAnalyzerKey);
      // Kill the whole key every time.
      OpenKey(SETINetSpectrumAnalyzerKey, True); // Then write it back
      // OpenKey will create the key if the second parameter is true and the key does not already exist

      WriteInteger('Integration', Integration);
      WriteInteger('knobFFTSize', knobFFTSize.SwitchPosition);
      WriteFloat('VerticalRange', VerticalRange);
      WriteInteger('SegmentSize', SegmentSize);
      WriteInteger('LinesPerFrame', LinesPerFrame);

      WriteFloat('LowerGuard', chart.CrossHair2.PosX / (FFT.SpectrumSize DIV 2));
      WriteFloat('UpperGuard', chart.CrossHair3.PosX / (FFT.SpectrumSize DIV 2));
      WriteInteger('Threshold', ThresholdRMT);
      WriteInteger('Left', frmSpecAna.Left);
      WriteInteger('Top', frmSpecAna.Top);
      WriteInteger('LineLimit', AmplitudeLimit);

      //
      WriteInteger('WaterTop', frmWaterFall.Top);
      WriteInteger('WaterLeft', frmWaterFall.Left);
      WriteInteger('WaterHeight', frmWaterFall.Height);
      WriteInteger('WaterWidth', frmWaterFall.Width);

      // cbFormat.ItemIndex := ReadInteger();
      CloseKey;
    end;
    // SETINetReg
  finally
    SETINetReg.Free;
  end; // try..finally

end;

//
procedure TfrmSpecAna.DisplayHint(Sender: TObject);

begin
  if cbShowLongHints.Checked then
    sbSpecAna.Panels[CAlert].Text := GetLongHint(Application.Hint);
end;

procedure TfrmSpecAna.FormActivate(Sender: TObject);
begin
  { Assign the application’s OnHint event handler at runtime
    because the Application is not available in the Object Inspector at design time }
  Application.OnHint := DisplayHint;

end;

procedure TfrmSpecAna.cbShowHintsClick(Sender: TObject);
begin
  Application.ShowHint := cbShowHints.Checked;
  // frmSpecAna.ShowHint := cbShowHints.Checked;
end;

initialization

uRORTTIServerSupport.RODLLibraryName := 'NewLibrary';
{$ENDREGION}

end.
