unit AdaptiveFiltering; interface {$I BdsppDefs.inc} uses Math387, MtxVec ,Types ; type (* Abstract class for the Kalman filtering algorithm(s). *) TBaseKalmanFilter = class strict protected Z_IsScalar: boolean; X_IsScalar: boolean; R_IsScalar: boolean; Q_IsScalar: boolean; H_IsScalar: boolean; TimeInitialized: boolean; MeasurementInitialized: boolean; tmpV: TMtx; tmpS: TMtx; tmpM: TMtx; tmpA: TMtx; procedure OnSize(Sender: TObject); public (* Optional parameter. *) Iter: integer; (* The process estimates. *) x: TMtx; (* The measured values. *) z: TMtx; (* Maps x(k-1) to x(k) without noise or system input. In case of the extended Kalman filter this the matrix of partial derivates of non-linear f with respect to x. The variable is in some sources refered to as the "F". *) A: TMTx; (* Relates process values x(k) to the measurement z(k). In case of the extended Kalman filter this is the matrix of partial derivates of non-linear h with respect to w, where w is the process noise. *) H: TMtx; (* Kalman gain. *) K: TMtx; (* Estimation error covariance. *) P: TMtx; (* Process noise covariance. *) Q: TMtx; (* Measurement noise covariance. *) R: TMtx; (* Applies one "time update". *) procedure TimeUpdate; virtual; (* Applies one "measurement update". *) procedure MeasurementUpdate; virtual; (* Advances the computation by one iteration. All public matrix and vector fields like x, z, A, H, P, Q, R,... can be modified between consecutive calls to Update by the user, if neccessary. *) procedure Update; constructor Create; virtual; destructor Destroy; override; end; (* Kalman filter component implements standard Kalman filtering algorithm. The standard Kalman filtering equations are defined as following: Time update: x(k) = A*x(k-1) + B*u(k-1) P(k) = A*P(k-1)*A^T + Q Measurement update: K(k) = P(k)*H^T*(H*P(k)*H^T +R)^-1 x(k) = x(k) + K(k)*(z(k) - H*x(k)) P(k) = (I - K(k)*H)*P(k) The process x(k) has a known mathemathical model. When we have an application where we need to measure x(k) as accurately as possible the Kalman filter can help us reduce the noise. We use the added information from the mathemathical model to more effectively filter the signal. Description of symbols: s - number of parallel inputs (columns in x and z). x(k) - size: n x s. vector state (value) of the process A - size: n x n. Maps x(k-1) to x(k) without noise or system input u(k) - control vector input (optional). size: l x s B - size: n x l maps control input u(k-1) to x(k) Q - process noise covariance R - measurement noise covariance z - size: m x s. Measurement vector H - size: m x n. Relates x(k) to the measurement z(k) w - process noise v - measurement noise All parameters can be modified by the user before each iteration of the filter. One iteration of the filter is achieved by calling the Update method. The process noise and measurement noise are assumed to be independent and gaussian. [1] An Introduction to the Kalman Filter, Greg Welch and Gary Bishop *) TKalmanFilter = class(TBaseKalmanFilter) strict protected FOnDrivingFunction: TMtxNotifyEvent; u: TMtx; B_IsScalar: boolean; procedure SetOnDrivingFunction(const Value: TMtxNotifyEvent); public (* Matrix B is used in the x(k) = A*x(k-1) + B*u(k-1). *) B: TMtx; procedure TimeUpdate; override; procedure MeasurementUpdate; override; constructor Create; override; destructor Destroy; override; public (* Event type used to provide the driving function for the Kalman filter. The driving function is the u(k) in the formula x(k) = A*x(k-1) + B*u(k-1). The parameter of the event is of type of TMtx and should hold the new u(k) data when the function returns. *) property OnDrivingFunction: TMtxNotifyEvent read FOnDrivingFunction write SetOnDrivingFunction; end; (* Event type used by Extended Kalman filter. The z is to be filled with "measurement" data, the function h(x,0). *) TKalmanMeasurementEvent = procedure (z: TMtx) of object; (* Event type used by Extended Kalman filter. The x is to be filled with "process" data, the function x(k) = f(x(k-1), u(k-1), 0 ). *) TKalmanProcessEvent = procedure (x: TMtx) of object; (* Extended Kalman filter component allows filtering of a non-linear process. Kalman filter that will linearize non-linear process around the covariance and current average value is called the extended Kalman filter. The equations are defined as following: Time update: x(k) = f(x(k-1), u(k-1), 0 ) P(k) = A(k)*P(k-1)*A(k)^T + W(k)*Q*W(k)^T Measurement update: K(k) = P(k)*H(k)^T *( H(k)*P(k)*H(k)^T + V(k)* R*V(k)^T )^-1 x(k) = x(k) + K(k) * (z(k) - h(x(k), 0)) P(k) = (I - K(k)*H(k))*P(k) The process x(k) has a known mathemathical model. When we have an application where we need to measure x(k) as accurately as possible the Kalman filter can help us reduce the noise. We use the added information from the mathemathical model to more effectively filter the signal. Description of symbols: s - number of parallel inputs (columns in x and z). f - non-linear function relating x(k-1) to x(k) x(k) - size: n x s. vector state (value) of the process in each column u(k) - control vector input. size: l x s B - size: n x l maps control input u(k-1) to x(k) Q - process noise covariance R - measurement noise covariance z - size: m x s. Measurement vector h - Non-linear function relates x(k) to the measurement z(k) w(k) - process noise v(k) - measurement noise A(k) - size: n x n. matrix of partial derivates of f with respect to x H(k) - matrix of partial derivates of h with respect to x W(k) - matrix of partial derivates of f with respect to w V(k) - matrix of partial derivates of h with respect to v All parameters can be modified by the user before each iteration of the filter. One iteration of the filter is achieved by calling the Update method. The process noise and measurement noise are assumed to be independent and gaussian. [1] An Introduction to the Kalman Filter, Greg Welch and Gary Bishop *) TExtendedKalmanFilter = class(TBaseKalmanFilter) strict protected V_IsOne: boolean; tmpR: TMtx; tmpB: TMtx; FOnGenerateMeasurement: TKalmanMeasurementEvent; FOnGenerateProcess: TKalmanProcessEvent; procedure SetOnGenerateMeasurement(const Value: TKalmanMeasurementEvent); procedure SetOnGenerateProcess(const Value: TKalmanProcessEvent); public (* Matrix of partial derivates of f with respect to w. w is the process noise. *) W: TMtx; (* Matrix of partial derivates of h with respect to v. v is the measurement noise. *) V: TMtx; procedure TimeUpdate; override; procedure MeasurementUpdate; override; constructor Create; override; destructor Destroy; override; public (* Parameter is of type TVec and has to be filled with result of non-linear process function f. *) property OnGenerateProcess: TKalmanProcessEvent read FOnGenerateProcess write SetOnGenerateProcess; (* Parameter is of type TVec and has to be filled with result of non-linear measurement function h. *) property OnGenerateMeasurement: TKalmanMeasurementEvent read FOnGenerateMeasurement write SetOnGenerateMeasurement; end; (*Read or write files.*) unit FileSignal; interface {$I BdsppDefs.inc} {$WARN SYMBOL_DEPRECATED OFF} {$DEFINE WINDOWS_WAV} {$DEFINE USING_FORMS} uses MtxVec, Math387, SignalTools, MtxBaseComp ,Types ,MtxDialogs ,MMSystem ,Classes ,SysUtils ,Contnrs ,ippspl ,ippsplSingle ; const WAVE_FORMAT_IEEE_FLOAT : cardinal = $0003; const WAVE_FORMAT_PCM : cardinal = $0001; const WAVE_FORMAT_EXTENSIBLE : cardinal = $FFFE; const KSDATAFORMAT_SUBTYPE_PCM : TGUID = '{00000001-0000-0010-8000-00aa00389b71}'; const KSDATAFORMAT_SUBTYPE_IEEE_FLOAT: TGUID = '{00000003-0000-0010-8000-00aa00389b71}'; type {$EXTERNALSYM TWaveFormatEx} TWaveFormatEx = packed record wFormatTag: Word; nChannels: Word; nSamplesPerSec: Cardinal; nAvgBytesPerSec: Cardinal; nBlockAlign: Word; wBitsPerSample: Word; cbSize: Word; end; TWaveFormatExtensible = packed record Format: TWaveFormatEx; wValidBitsPerSample : Word; dwChannelMask : cardinal; SubFormat : TGUID; end; TWavNonPCMHeader = packed record ckId0: Byte; ckId1: Byte; ckId2: Byte; ckId3: Byte; ckSize: Cardinal; wave_ckID0: Byte; wave_ckID1: Byte; wave_ckID2: Byte; wave_ckID3: Byte; fmt_ckID0: Byte; fmt_ckID1: Byte; fmt_ckID2: Byte; fmt_ckID3: Byte; fmt_ckSize: Cardinal; pcm: TWaveFormatEx; data_ckID0: Byte; data_ckID1: Byte; data_ckID2: Byte; data_ckID3: Byte; data_ckSize: Cardinal; end; TWavExtendedHeader = packed record ckId0: Byte; ckId1: Byte; ckId2: Byte; ckId3: Byte; ckSize: Cardinal; wave_ckID0: Byte; wave_ckID1: Byte; wave_ckID2: Byte; wave_ckID3: Byte; fmt_ckID0: Byte; fmt_ckID1: Byte; fmt_ckID2: Byte; fmt_ckID3: Byte; fmt_ckSize: Cardinal; pcm: TWaveFormatExtensible; end; (*Defines how to browse a multi-record file. Defines how the TSignalRead component should browse a multi-record file. *) TFraming = ( (*Calling NextFrame method will return the next frame from the current record.*) frSingleRecord, (*Calling NextFrame method will return the frame from the next record and at the same record position.*) frAcrossAllRecords ); (*Specifies file formats supported. File formats supported by TSignalWrite and TSignalWrite components. *) TFileFormat=( (*Multi record binary file. The header contains 32bit unsigned - sample count in the data section 64bit float - Sampling frequency 32bit signed int - Precision (TPrecision) 32bit signed int - Complex (bool) 32bit signed int - ChannelCount Header is followed by data. Each data section can again be followed by a header.*) ffDat, (*Multi record text file. First line denotes number of entries. Second line is the total time of sampling and the third line containes the samples separated with ";". The samples can be complex. The fourth line is empty. This four lines can repeate any number of times within the file.*) ffSgl, (*Backward compatibility. Replaced by the ffDat format.*) ffBin, (*Windows OS uncompressed PCM wav file.*) ffWav, (*Single record text file. First line denotes number of entries. Second line is the the total time of sampling and the third line is empty. All the samples follow, each in its own line. Samples can be complex.*) ffSfs, (*A two column text file, first column is the time axis and the second column is the amplitude. The second column can hold complex numbers.*) ffAsc, (*Backward compatibility. Replaced by the ffDat format.*) ffDbn, (*Backward compatibility. Replaced by the ffDat format.*) ffDbl, (*Headerless binary file format.*) ffRaw, (*Headerless binary file format.*) ffPCM, (*Headerless text file format. Each column represents one chanel. The numbers can be complex.*) ffTxt ); (*Abstract class for read and writing signals. Abstract class for signal reading and writing. *) TFileStorage = class(TSignal) strict protected FFileName: TFileName; FileStream: TFileStream; FFileFormat: TFileFormat; FAutoFileFormat: boolean; procedure SetFileName(const Value: TFileName); procedure SetFileFormat(const Value: TFileFormat); procedure SetAutoFileFormat(const Value: boolean); strict protected FPrecision: TPrecision; FileReady: boolean; Strings: TStringList; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure CloseFile; virtual; abstract; (*Saves a header file describing file contents. The file is saved in the same directory as the signal file and has the same file name but with a .header extension. *) procedure SaveHeaderFile; published (*If True, the file format will be automatically determined from the file name extension. File name is stored in the FileName property. Recognized file extensions are: dat, asc, sfs, sgl, bin, dbn, wav, raw, pcm. *) property AutoFileFormat: boolean read FAutoFileFormat write SetAutoFileFormat default true ; (*Defines the format of the file, if AutoFileFormat property is False.*) property FileFormat: TFileFormat read FFileFormat write SetFileFormat default ffDat ; (*Defines the file name to be read from or written to.*) property FileName: TFileName read FFileName write SetFileName; end; (*Defines how to handle data at the end of file. Defines how to handle data at the end of the file, when the data is being read with the Update method, which advances the read cursor automatically. The sample count left to be read may be less then the Length specified and there are several options what to do. *) TLastFrameCheck = ( (*The last block of data will be zero padded. The length property will not be changed.*) lfcZeroPadded, (*The reading of the file will stop when the last full block of data has been read from the file. The length property will not be changed. With lfcZeroPadded the very last block read from the file will contain zeros padded after the position exceeding the size of the file. This padded zeros can affect any averaging process working on the data blocks. By selecting lfcLastFullBlock the IsEndOfFile will return true for the last completely full data block. The Pull method calls IsEndOfFile to check for the end of file.*) lfcLastFullBlock, (*The Length property and the size of the data block to be read will be adjusted to allow reading less than specified Length samples. The user must reset the Length property back to its orignal when read from the file again at some other position.*) lfcAdjustLength ); (*Reads a signal from the file. TSignalRead is probably the most important component to understand in order to make effective use of other signal processing components. Use TSignalRead to read wav, binary and text files and provide streaming capabilities with frame navigation and overlapp support. The read data is placed in the Data property and its sample count is determined with the Length property. Once a file is opened, the key properties immediatelly contain all the necessary information about the file size, number of records, data type etc... You can connect the component to other components which have an Input property. Example: SpectrumAnalyzer.Input = SignalRead; By calling SpectrumAnalyzer.Pull data will be read from the file and a frequency spectrum will be computed. Read multichannel files: SignalRead.FileName := MyFile.wav; SignalRead.OpenFile; SignalDemuxList.Input := SignalRead; SignalDemuxList.Pull; //now we have demulitplexed data stored in: //SignalDemuxList[0].Data //SignalDemuxList[1].Data //.. //SignalDemuxList[SignalRead.ChannelCount-1].Data *) TSignalRead = class(TFileStorage) strict private fFileSize: Int64; DataPos: Int64; DataSize: Cardinal; PrevRecordNumber: integer; FNumberOfRecords: integer; FRecordLength: int64; fRecordTimePosition: Double; FRecordNumber: integer; fRecordTime: Double; FScaleFactor: Double; FFraming: TFraming; FOverlappingPercent: Double; FMaxFrames: int64; FOverlappingSamples: integer; FRecordPosition: Int64; FFrameNumber: int64; FFramesPerSecond: Double; FLoop: boolean; FFilePosition: int64; LoadedData: TMtx; FOnRecordPositionChange: TNotifyEvent; FLastFrameCheck: TLastFrameCheck; FSelectionStop: int64; FOnProcessAll: TNotifyEvent; FForceLength: boolean; FSelectionStart: int64; FLastUpdatedLength: integer; FPostBufferTime: Double; FPostBufferSamples: integer; procedure ImportDatFile(FileName: string); procedure ImportSglFile(FileName: string); procedure ImportBinFile(FileName: string); procedure ImportWavFile(FileName: string); procedure ImportSfsFile(FileName: string); procedure ImportAscFile(FileName: string); procedure ImportDbnFile(FileName: string); procedure ImportDblFile(FileName: string); procedure ImportRawFile(FileName: string); procedure ImportPcmFile(FileName: string); procedure ImportTxtFile(FileName: string); procedure ProcessDatRecord(NewRecordPosition: Int64); procedure ProcessSglRecord(NewRecordPosition: Int64); procedure ProcessBinRecord(NewRecordPosition: Int64); procedure ProcessWavRecord(NewRecordPosition: Int64); procedure ProcessSfsRecord(NewRecordPosition: Int64); procedure ProcessAscRecord(NewRecordPosition: Int64); procedure ProcessDbnRecord(NewRecordPosition: Int64); procedure ProcessDblRecord(NewRecordPosition: Int64); procedure ProcessRawRecord(NewRecordPosition: Int64); procedure ProcessPcmRecord(NewRecordPosition: Int64); procedure ProcessTxtRecord(NewRecordPosition: Int64); procedure SetRecordNumber(const Value: integer); procedure SetScaleFactor(const Value: Double); procedure SetFraming(const Value: TFraming); procedure SetOverlappingPercent(const Value: Double); procedure SetOverlappingSamples(const Value: integer); procedure SetRecordPosition(const Value: int64); procedure SetFrameNumber(const Value: int64); procedure SetFramesPerSecond(const Value: Double); procedure SetLoop(const Value: boolean); function SeekRecordNumber: boolean; function SeekDatRecordNumber: boolean; function SeekDatRecordPosition(NewRecordPosition: int64): boolean; procedure SetPrecision(const Value: TPrecision); procedure SetMaxFrames(const Value: int64); procedure SetNumberOfRecords(const Value: integer); procedure SetRecordLength(const Value: int64); procedure SetRecordTime(const Value: Double); procedure SetRecordTimePosition(const Value: Double); procedure ReadDatHeader; function SeekRecordNumber2: boolean; procedure SetOnRecordPositionChange(const Value: TNotifyEvent); procedure SetSelectionStop(const Value: int64); procedure SetLastFrameCheck(const Value: TLastFrameCheck); procedure SetOnProcessAll(const Value: TNotifyEvent); function GetFileSize: Int64; procedure SetForceLength(const Value: boolean); procedure SetSelectionStart(const Value: int64); procedure ImportRawPcm(FileName: string); procedure ProcessBinaryRecord(NewRecordPosition: int64); function ExtractHeaderParam(const Str: string): string; procedure SetPostBufferSamples(const Value: integer); procedure SetPostBufferTime(const Value: Double); procedure ReadExtHeader(FileStream: TFileStream; var extHeader: TWavExtendedHeader); protected class function EditorClass: string; override; strict protected function StandardWavFormat: boolean; procedure UpdateSetLength(Sender: TObject); override; function InternalUpdate: TPipeState; override; procedure ProcessRecord(NewRecordPosition: Int64); procedure AdvanceFrame; virtual; procedure SetSamplingFrequency(const Value: Double); override; public (*Reads the header file for raw file formats. This is neccessary only, if the stored parameters were changed after the file was already opened. *) procedure LoadHeaderFile; (*Checks if the file format is raw. Returns true, if the file format specified by the file name is raw and does not contain header that would specify its contents like sampling frequency, channel count or precision stored. *) function RawFileFormat: boolean; function InternalPull: TPipeState; override; (*Returnes the number of samples left to the end of the file per channel. It takes in to accout the SelectionStop property. *) function SamplesLeft: Int64; (*Returns the size of the file in bytes.*) function FileSize: Int64; (*Returns true, if the file is open.*) function FileIsOpen: boolean; (*Advances the record position forward in the current record and reads the data. The sample count skiped is determined with the Length property and OverlappingSamples property. *) procedure NextFrame; (*Moves the record position backward in the current record and reads the data. The sample count skiped is determined with the Length property and OverlappingSamples property. *) procedure PrevFrame; (*Sets the record position to the start of the current record and reads the data. The sample count is determined with the Length property and OverlappingSamples property. *) procedure FirstFrame; (*Sets the record position to the start of the last frame of the current record and reads the data. The sample count is determined with the Length property and OverlappingSamples property. *) procedure LastFrame; (*Open file.*) procedure OpenFile; virtual; (*Close file.*) procedure CloseFile; override; (*Returns true, if the current frame is the last frame.*) function IsEndOfFile: boolean; constructor Create(AOwner: TComponent); override; destructor Destroy; override; (*Returns the name of the file format.*) function FormatTagDescription: string; (*Returns the description of the file format.*) function FormatDescription: string; procedure DetectChannelCount; override; published (*Specifies how to handle the end of file. The file being read by the component can be of any size, but data is being read in blocks of Length samples. This property determines how will the last data block be handled when there may not be enough data left for one full block. *) property LastFrameCheck: TLastFrameCheck read FLastFrameCheck write SetLastFrameCheck default lfcZeroPadded; (* Specifies the upper file range limit. To limit the range of samples in the file being processed the SelectionStop can be set to something different then -1. If the file ends before the specified SelectionStop the processing will not be affected by the value of this property. The value of this property affects only the IsEndOfFile method. SelectionStop is defined in number of samples. *) property SelectionStop: int64 read FSelectionStop write SetSelectionStop; (* Specifies the lower file range limit. To limited the range of samples being processed the SelectionStart can be set to something different then 0. SelectionStart is defined in number of samples. *) property SelectionStart: int64 read FSelectionStart write SetSelectionStart; (*If true, looping will be enabled. If True, consecutive calls to NextFrame will reset to the record position of the first frame, once the end of the current record or selection is reached. *) property Loop: boolean read FLoop write SetLoop default False ; (*Defines a scale factor to be applied to just read data.*) property ScaleFactor: Double read FScaleFactor write SetScaleFactor; (*Read only. Returns the number of records contained in the current file.*) property NumberOfRecords: integer read FNumberOfRecords write SetNumberOfRecords stored False; (*Read only. Returns the length of the current record in samples for one channel.*) property RecordLength: int64 read FRecordLength write SetRecordLength stored False; (*Read only. Returns the length of the current record in seconds for one channel.*) property RecordTime: Double read FRecordTime write SetRecordTime stored False; (*Set RecordTimePosition in seconds to position the read cursor within the current record. Read RecordTimePosition to determine the current position of the read cursor within the current record. If the record is multiplexed, the position is defined for one channel. *) property RecordTimePosition: Double read FRecordTimePosition write SetRecordTimePosition stored False; (*Set and get position in samples. Set RecordPosition in samples to position the read cursor within the current record and load Length samples from the Channel. Read Recordosition to determine the current position of the read cursor within the current record. If the record is multiplexed, the position is defined for sample count per single channel. Setting RecordPosition will also open file, if not already open. *) property RecordPosition: int64 read FRecordPosition write SetRecordPosition stored false; (*Set RecordNumber to move the read cursor within the file to the record with RecordNumber number. Read RecordNumber property to determine the current record number. RecordPosition will not change. First record has a number 0. *) property RecordNumber: integer read FRecordNumber write SetRecordNumber default 0; (*Define the overlapping in percent of consequtive frames when reading the file.*) property OverlappingPercent: Double read FOverlappingPercent write SetOverlappingPercent; (*Define the overlapping in sampels of consequtive frames when reading the file.*) property OverlappingSamples: integer read FOverlappingSamples write SetOverlappingSamples default 0; (*Read Only. Determine the maximum number of frames. Determine the maximum number of frames within the current record with the current setting of OverlappingSamples property. *) property MaxFrames: int64 read FMaxFrames write SetMaxFrames stored False; (*Defines the method to traverse a multi-record the file. With each consecutive call to NextFrame, you can either move along the same record (increasing RecordPosition) or across all the records (increasing RecordNumber). *) property Framing: TFraming read FFraming write SetFraming default frSingleRecord ; (*Returns the number of frames per second according to Framing and overlapp properties.*) property FramesPerSecond: Double read FFramesPerSecond write SetFramesPerSecond stored False; (*Set FrameNumber to position the read cursor within the current record. Read the property to determine the current frame number (position) within the current record. *) property FrameNumber: int64 read FFrameNumber write SetFrameNumber stored false; (*Read the precision of the data being read from the file.*) property Precision: TPrecision read FPrecision write SetPrecision stored False; (*Triggered after RecordPosition changes.*) property OnRecordPositionChange: TNotifyEvent read FOnRecordPositionChange write SetOnRecordPositionChange; (*Event triggered when Process command is selected from the drop-down menu in the component editor.*) property OnProcessAll: TNotifyEvent read FOnProcessAll write SetOnProcessAll; (*Warn about compressed files varying Length read. In case of variable bit rate codecs or codecs whose bit rate is not a multiple of 8 bits, the number of samples being read from the file can not be always matched with the specified TSignalRead.Length property. If ForceLength is True and the value of the Length property can not be matched an exception will be raised. The position within the file with variable bit rate can be defined only approximately (in steps of 0.1 seconds for example.) When the data blocks are read without explicit changes to the RecordPosition, all data blocks are read consecutively without overlapping or loosing any samples. (But the Length property will vary between consecutive calls. *) property ForceLength: boolean read FForceLength write SetForceLength ; (* Append zeros to end of file. Set this to greater than zero in seconds, to compensate for FIR filter delays and clicks at the end of the playback. *) property PostBufferTime: Double read FPostBufferTime write SetPostBufferTime; (* Append zeros to then end of file. Set this to greater than zero in samples per channel, to compensate for FIR filter delays and clicks at the end of the playback. The file will be virtually extended by adding zeros at the end. *) property PostBufferSamples: integer read FPostBufferSamples write SetPostBufferSamples; end; (*Writes a signal to a file. Use TSignalWrite to write wav, binary and text files and provide streaming capabilities. The data to be written is connected to the Input property. If you would like to save data in the Data property, connect the component to itself. *) TSignalWrite = class(TFileStorage) strict private RecordInitialized: boolean; DoInitRecord: boolean; DataSize: integer; RecordStartOffset: Int64; FTotalRecordLength: Int64; FAppendFile: boolean; FRounding: TRounding; FRecordNumber: integer; FAuthorInfo: string; FReFormat: string; FImFormat: string; npcmH: TWavNonPCMHeader; procedure SetAppendFile(const Value: boolean); procedure SetRounding(const Value: TRounding); procedure SetRecordNumber(const Value: integer); function SeekRecordNumber: boolean; procedure SetPrecision(const Value: TPrecision); procedure SetRecordLength(const Value: Int64); procedure SetRecordTime(const Value: Double); function GetRecordLength: int64; function GetRecordTime: Double; procedure SetTotalRecordLength(const Value: Int64); procedure SetAuthorInfo(const Value: string); procedure SaveTxtBlock; procedure SetReFormat(const Value: string); procedure SetImFormat(const Value: string); procedure WriteExtHeader(FileStream: TFileStream; var extHeader: TWavNonPCMHeader); strict protected function InternalUpdate: TPipeState; override; procedure SaveAscBlock; public (*Returns the amount of data written so far in bytes. Does not inlude file header size. *) function BytesWritten: int64; (*Returns true, if the file is open.*) function FileIsOpen: boolean; (*Appends a new record to the file with Filename. The file is opened, the Data is appended as a single record and the file is closed. *) procedure AppendRecord; (*Initialize a new record. When writing multi-record files, call InitRecord each time you want to start a new a record. For each InitRecord a call to CloseRecord is made first. InitRecord and CloseFile will also call CloseRecord. InitRecord does not write data to the file. Instead it allocates space for the record header. When a call to SaveBlock is made, InitRecord will be called automatically, if the record has not yet been initialized. *) procedure InitRecord; (*Call the CloseRecord once the record has been written. Usually this method is used in the sequence: InitRecord, SaveBlock,..., SaveBlock, CloseRecord. InitRecord, SaveBlock,...., SaveBlock,.... You do not have to call it after a call to InitRecord: InitRecord, SaveBlock,..., SaveBlock, InitRecord, SaveBlock,.... If InitRecord is called and the record is open, CloseRecord is called automatically. *) procedure CloseRecord; (*Save Data as a single record to the file. Close record does not write any data to the file. *) procedure SaveRecord; (*Save Data to the current record and advance the file position. Call CloseRecord at the end of calls to update the total length of the record. *) procedure SaveBlock; (*Prepare the file with FileName for writing.*) procedure SaveFile; (*Close the file with FileName. Usually the method is used in the call sequence: SaveFile, InitRecord,SaveBlock,..., SaveBlock, CloseRecord, IniRecord,..... ,CloseRecord, CloseFile. *) procedure CloseFile; override; (*Saves the data connected to the input property to the file with FileName. This method cals: SaveFile, InitRecord, SaveBlock, CloseRecord, CloseFile. Use this method when the data is not streamed. *) procedure SaveDataToFile(const aFileName: string); function InternalPull: TPipeState; override; constructor Create(AOwner: TComponent); override; published (*Number format definition for text file formats. Specifies the number format of the real part of the complex number for the text file formats. To use default (general floating point) formatting, leave this property empty. *) property ReFormat: string read FReFormat write SetReFormat; (*Number format definition for text file formats. Specifies the number format of the imaginary part of the complex number for the text file formats. To use default (general floating point) formatting, leave this property empty. *) property ImFormat: string read FImFormat write SetImFormat; (*Holds the string added to the header of the saved wav file.*) property AuthorInfo: string read FAuthorInfo write SetAuthorInfo; (*Source data to be written to the file. *) property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; (*If True, the SaveFile method will set the file cursor at the end of the file. Any records written to the file will be appended to the existing ones. If the value of the property is false, the existing file will be overwritten. *) property AppendFile: boolean read FAppendFile write SetAppendFile default false ; (*Define the rounding mode when saving to integer types.*) property Rounding: TRounding read FRounding write SetRounding default rnTrunc ; (*Move the file position cursor to the beginging of the record number RecordNumber.*) property RecordNumber: integer read FRecordNumber write SetRecordNumber default 0; (*Defines the precision with which the Data will be saved. For text file types the formating is specified with ReFormat and ImFormat. *) property Precision: TPrecision read FPrecision write SetPrecision default prDouble ; (*Read only. Returns the length in samples of the Data written so far in the current record per channel. *) property RecordLength: int64 read GetRecordLength write SetRecordLength stored False; (*Read only. Returns the length in seconds of the Data written so far in the current record per channel. *) property RecordTime: Double read GetRecordTime write SetRecordTime stored False; (*Returns the sample count written for all channels. Returns the length in samples of the Data written so far in the current record of all channels. The value of this property is the same as RecordLength, if ChannelCount is 1. *) property TotalRecordLength: int64 read FTotalRecordLength write SetTotalRecordLength stored False; end; (*Manage a list of TSignalRead components.*) TSignalReadList = class(TSignalList) strict private function GetItems(index: integer): TSignalRead; reintroduce; procedure SetItems(index: integer; const Value: TSignalRead); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Add another TSignalRead item to the List.*) procedure Add; reintroduce; (*Close all files specified in Items[i].FileName.*) procedure CloseFiles; (*Default array property access to the items of the list.*) property Items[index: integer]: TSignalRead read GetItems write SetItems; default; end; (*Manage a list of TSignalWrite components.*) TSignalWriteList = class(TSignalList) strict private function GetItems(index: integer): TSignalWrite; reintroduce; procedure SetItems(index: integer; const Value: TSignalWrite); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Add another TSignalWrite item to the List.*) procedure Add; reintroduce; (*Close the files with Items[i].FileName.*) procedure CloseFiles; (*Prepare the files with Items[i].FileName for writing.*) procedure SaveFiles; (*Default array property access to the items of the list.*) property Items[index: integer]: TSignalWrite read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (*Interface the audio compression manager. Audio compression manager is a Windows service for compressing and decompressing audio data. *) (* Stores a list of TVec objects using TSignalWrite to one of the supported formats The file format is determined from the extension of the file. Precision and FS (Sampling frequency) also need to be specified. *) procedure SaveSignalFile(const a: array of TVec; FileName: string; Precision: TPrecision; FS: Double); (* Converts file extension from string to *) function ExtensionToFileSignalFormat(Extension: string): TFileFormat; overload; (* Converts file extension to string. *) function FileSignalFormatToExtension(aFormat: TFileFormat): string; (* Sets multiple or trailing tab charachters before or after number to space char. *) function NumberizeString(const src: string): string; (*Drop down file types string. A string to be assigned to the Filter property of the TFileOpen or TFileSave dialog. *) var SignalDialogFilter: string = 'Binary multi precision records (*.dat)|*.dat|' + 'Single column ASCII (*.sfs)|*.sfs|'+ 'Two column ASCII (*.asc)|*.asc|'+ 'Windows PCM (*.wav)|*.wav|'+ 'Raw PCM (*.raw)|*.raw|'+ 'Raw PCM (*.pcm)|*.pcm|'+ 'Text (*.txt)|*.txt|'+ 'All known file extensions|*.dat;*.sfs;*.asc;*.wav;*.raw;*.pcm;*.txt|' + 'All files (*.*)|*.*'; var (*Default file extension for level 1 peak files produced by .*) PeakFileExtension: string = '.peak'; var (*Default file extension for level 2 peak files produced by .*) PeakFileExtension2: string = '.peakk'; {$I BdsppDefs.inc} (*Design IIR filters.*) unit IIRFilters; interface uses Math387,MtxVec,SignalUtils; (*Maximum IIR filter order. The constant is used to limit the maximum IIR filter order estimated. *) const MaxIirOrder = 50; (*Defines supported IIR filter design methods. *) type TIirFilterMethod = ( (*Butterworth filter.*)fimButter, (*Chebyshev type I filter.*)fimChebyshevI, (*Chebyshev type II filter.*)fimChebyshevII, (*Elliptic filter.*)fimElliptic, (*DC filter.*)fimDC, (*Notch filter.*)fimNotch, (*Butterworth filter.*)fimBessel ); (*Methods for frequency band transformation of IIR filters. Defines supported approaches for frequency band transformations of IIR filters. Frequency band transformations are used to convert a normalized prototype analog IIR lowpass filter with a fixed cutoff frequency to arbitrary lowpass, highpass, bandpass or bandstop filter. The frequency band transformation can be applied before or after the filter has been transformed from s-domain to z-domain. *) TIirFrequencyTransform = ( (*Analog filter prototype in the zero-pole form is converted to the state-space form. Frequency band transformations are applied first (in the s-domain) followed by a bilinear transform and a finally the filter is converted from the state-space form back to the zero-pole form.*) ftStateSpaceAnalog, (*Analog filter prototype is obtained in the zero-pole form. Frequency band transformations are applied first (in the s-domain) followed by a bilinear transform.*) ftZeroPoleAnalog, (*Analog filter prototype is obtained in the zero-pole form. A bilinear transform is applied first to map the filter to z-domain. Frequency band transformations are applied in the z-domain. This method is usefull in cases where the mapping from s-domain to z-domain is not bilinear transformation, but impulse invariance or matched z-transform.*) ftZeroPoleDiscrete ); (*Design analog Butterworth type IIR prototype filter. Design analog butterworth lowpass prototype filter of order Order. Place the resulting transfer function in zero-pole form in Z (zeros), P (poles) and K (gain). The cutoff frequency of the prototype filter is preset to 1 rad/sec. The filter has all zeros in infinity. The transfer function is defined as ([1], p. 277):

                         k0
      H(s) = -------------------------
             (s - s[1])*...*(s - s[n])


      The poles of the filter are located at

      s[k] := Expj(Pi*(0.5+(2*k-1)/(2*n)));

      n = order of filter
      k = 1,...,n
      k0 = gain
       
The magnitude response is down 3dB at the cutoff frequency. References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975.
Design an analog lowpass filter with cutoff frequency at 3 rad/sec. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k, Wc; int Order = 5; //design a fifth order filter. IIRFilters.ButterAnalog(Order,z, p, out k); //design analog protype Wc = 3; //cutoff frequency LinearSystems.LowpassToLowpass(z, p, ref k, Wc); LinearSystems.ZeroPoleToTransferFun(num, den, z, p, k); FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr, -1, 1); SignalUtils.FrequencyResponseS(num, den, FreqFr, Response, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); } *) procedure ButterAnalog(Order: integer; const z,p: TVec; out k: double); (*Design analog Chebyshev type I IIR prototype filter. Design analog Chebyshev type I lowpass prototype filter of order Order. Place the resulting transfer function in zero-pole form in Z (zeros), P (poles) and K (gain). PassRipple defines the ripple of the passband (dB). The cutoff frequency of the prototype filter is preset to 1 rad/sec, the unit circle. Chebyshevs type I filters are all-pole designs and are equiripple in the passband. The filter has all zeros in infinity. The design formulas are found in [1] p. 232:

      Poles: p[k] = s[k] + j*W[k]

      s[k] = -sinh(Phi)*sin((2*k-1)*Pi/(2*n))
      W[k] =  cosh(Phi)*cos((2*k-1)*Pi/(2*n))

      sinh(phi) =  0.5*(v - 1/v)
      cosh(phi) =  0.5*(v + 1/v)

             1 + (1 + eps^2)^0.5
      v = ( --------------------- )^(1/n)
                    eps

      n - order of the filter
      k = 1,...,n
       
References: [1] "Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975".
Design an analog highpass filter with cutoff frequency at 2 rad/sec with a 0.2dB ripple in the passband. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k, Wc; int Order = 5; //design a fifth order filter. IIRFilters.ChebyshevIAnalog(Order,0.2,z, p, out k); //design analog protype Wc = 2; //cutoff frequency LinearSystems.LowpassToLowpass(z, p, ref k, Wc); LinearSystems.ZeroPoleToTransferFun(num, den, z, p, k); FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr, -1, 1); SignalUtils.FrequencyResponseS(num, den, FreqFr, Response, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); } *) procedure ChebyshevIAnalog(Order: integer; PassRipple: double; const z,p: TVec; out k: double); (*Design analog Chebyshev type I IIR prototype filter. Design analog Chebyshev type II lowpass prototype filter of order Order. Place the resulting transfer function in zero-pole form in Z (zeros), P (poles) and K (gain). Ripple defines the StopRipple (dB) of the stopband.The cutoff frequency of the prototype filter is preset to 1 rad/sec, the unit circle. Chebyshevs type II filters have poles and zeros and are equiripple in the stopband. The design formulas are found in [1] p. 232:
 
                                Wr
      Zeros: z[k] = j* ---------------------
                       cos((2*k-1)/(2*n)*Pi)

      Poles: p[k] = s[k] + j*W[k]

                    Wr*a[k]
      s[k]  = -----------------
               a[k]^2  + b[k]^2

                   -Wr*b[k]
      W[k]  = -----------------
               a[k]^2  + b[k]^2

      a[k] = -sinh(Phi)*sin((2*k-1)*Pi/(2*n))
      b[k] =  cosh(Phi)*cos((2*k-1)*Pi/(2*n))

      sinh(phi) =  0.5*(v - 1/v)
      cosh(phi) =  0.5*(v + 1/v)

      v = (A  + (A^2 - 1)^0.5)^(1/n),    A = 1/sr^2

      n - order of the filter
      k = 1,...,n
      Wr - stopband edge
      sr - stopband ripple
       
References: [1] "Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975".
Design an analog bandpass filter with passband between 2 and 3 rad/sec and with 20dB ripple in the stopband. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k, Wc; int Order = 5; //design a fifth order filter. IIRFilters.ChebyshevIIAnalog(Order,20,z, p, out k); //design analog protype Wc = Math.Sqrt(3*2); //cutoff frequency double BW = 3 - 2; LinearSystems.LowpassToBandpass(z, p, ref k, Wc,BW); LinearSystems.ZeroPoleToTransferFun(num, den, z, p, k); FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr, -1, 1); SignalUtils.FrequencyResponseS(num, den, FreqFr, Response, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); } *) procedure ChebyshevIIAnalog(Order: integer; StopRipple: double; const z,p: TVec; out k: double); (*Design analog Elliptic type IIR prototype filter. Design analog elliptic prototype filter of order Order. Place the resulting transfer function in zero-pole form in Z (zeros), P (poles) and K (gain). PassRipple defines the ripple (dB) of the passband and StopRipple defines the ripple of the stopband (dB). The cutoff frequency of the prototype filter is preset to 1 rad/sec. For pole and zero specifications see [1] p. 187. References: [1] Digital Filter Design, T.W.Parks and C.S.Burrs, John Wiley and Sons, 1987. Design an analog bandstop filter with stopband between 1 and 3 rad/sec and with 20dB ripple in the stopband and 0.1dB in the passband. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k, Wc; int Order = 5; //design a fifth order filter. IIRFilters.EllipticAnalog(Order,0.1,20,z, p, out k); //design analog protype Wc = Math.Sqrt(3*1); //cutoff frequency double BW = 3 - 1; LinearSystems.LowpassToBandstop(z, p, ref k, Wc,BW); LinearSystems.ZeroPoleToTransferFun(num, den, z, p, k); FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr, -1, 1); SignalUtils.FrequencyResponseS(num, den, FreqFr, Response, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); } *) procedure EllipticAnalog(Order: integer; PassRipple, StopRipple: double; const z,p: TVec; out k: double); overload; (*Design analog Bessel type IIR prototype filter. Design analog Bessel prototype filter of order Order. Place the resulting transfer function in zero-pole form in Z (zeros), P (poles) and K (gain). The cutoff frequency of the prototype filter is preset to 1 rad/sec. The filter has all zeros in infinity. The transfer function is defined as([1], p. 230):

                d0
      H(s) = --------
               Bn(s)

            (2*n)!               n
      d0 = ------- ,    Bn(s) = Sum(d[k]*s^k),  k = 0,...,n
            2^n*n!              k=0

                (2*n-k)!
      d[k] = --------------    , n = order of the filter
              2^(n-k)*(n-k)!


      Filter poles must be scaled with d0^(1/n)
       
Roots of the Bessel polynomial Bn(s) are found with the PolyRoots routine. Bessel lowpass filters are charachterized by the property that the group delay is maximally flat at the origing of the s-plane. ([1], p. 228). References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975.
Design an analog lowpass filter with cutoff at 1.1 rad/sec. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k, Wc; int Order = 5; //design a fifth order filter. IIRFilters.BesselAnalog(Order,z, p, out k); //design analog protype Wc = 1.1; //cutoff frequency LinearSystems.LowpassToLowpass(z, p, ref k, Wc); LinearSystems.ZeroPoleToTransferFun(num, den, z, p, k); FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr, -1, 1); SignalUtils.FrequencyResponseS(num, den, FreqFr, Response, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); } *) procedure BesselAnalog(Order: integer; const z,p: TVec; out k: double); overload; (*Estimate the order a butterworth IIR filter. Returns the order of a butterworth type IIR filter. Bedg array must contain the band edges of the transition region(s) sorted in ascending order. PassRipple defines the ripple of the passband and StopRipple defines the ripple of the stopband. The length of the CutoffFreq array must be equal to one half of the length of the BEdg array and must match the specified FilterType. The routine returns the estimated order as a result and fill's the CutoffFreq array. This array can then be passed to the ButterFilter routine. Design an analog lowpass filter with transition band between 2 and 6 rad/sec and with at least 40dB attenuation at the end of the transition band and. The passband should not have more then 0.2dB ripple. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k; double[] Wc = new double[1]; int Order; //design a fifth order filter. Order = IIRFilters.ButterOrder(new double[2] { 2, 6 }, 0.2,40,TFilterType.ftLowpass,ref Wc,true); //design analog protype IIRFilters.ButterAnalog(Order,z, p, out k); //design analog protype LinearSystems.LowpassToLowpass(z, p, ref k, Wc[0]); LinearSystems.ZeroPoleToTransferFun(num, den, z, p, k); FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr, -1, 1); SignalUtils.FrequencyResponseS(num, den, FreqFr, Response, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); } *) function ButterOrder(const BEdges: array of double; PassRipple,StopRipple: double; FilterType: TFilterType; var CutoffFreq: array of double; Analog: boolean = False): integer; (*Estimate the order a Chebyshev type I IIR filter. Returns the order of the Chebyshev type I filter. Bedg array must contain the band edges of the transition region(s) sorted in ascending order. PassRipple defines the ripple of the passband and StopRipple defines the ripple of the stopband. The length of the CutoffFreq array must be equal to one half of the length of the BEdg array and must match the specified FilterType. The routine returns the estimated order as a result and fill's the CutoffFreq array. This array can then be passed to the ChebyshevIFilter routine. Design an analog highpass filter with transition band between 1 and 4 rad/sec and with at least 50dB attenuation at the end of the transition band and. The passband should not have more then 0.1dB ripple. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k; double[] Wc = new double[1]; int Order; //design a fifth order filter. Order = IIRFilters.ChebyshevIOrder(new double[2] { 1, 4 }, 0.2,40,TFilterType.ftHighpass,ref Wc,true); //design analog protype IIRFilters.ChebyshevIAnalog(Order, 0.1, z, p, out k); //design analog protype LinearSystems.LowpassToHighpass(z, p, ref k, Wc[0]); LinearSystems.ZeroPoleToTransferFun(num, den, z, p, k); FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr, -1, 1); SignalUtils.FrequencyResponses(num, den, FreqFr, Response, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); } *) function ChebyshevIOrder(const BEdges: array of double; PassRipple,StopRipple: double; FilterType: TFilterType; var CutoffFreq: array of double; Analog: boolean = False): integer; (*Estimate the order of a Chebyshev type II IIR filter. Returns the order of the Chebyshev type II filter. Bedg array must contain the band edges of the transition region(s) sorted in ascending order. PassRipple defines the ripple of the passband and StopRipple defines the ripple of the stopband. The length of the CutoffFreq array must be equal to one half of the length of the BEdg array and must match the specified FilterType. The routine returns the estimated order as a result and fill's the CutoffFreq array. This array can then be passed to the ChebyshevIIFilter routine. Design an analog bandpass filter with transition band between 1..3 and 6..9 rad/sec and with at least 50dB attenuation in the stopband and. The passband should not have more then 0.2dB ripple. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double Wc,k; double[] WcArray = new double[2]; int Order; //design a fifth order filter. Order = IIRFilters.ChebyshevIIOrder(new double[4] { 1, 3, 6, 9 }, 0.2, 50, TFilterType.ftBandpass, ref WcArray, true); //design analog protype IIRFilters.ChebyshevIIAnalog(Order, 50, z, p, out k); //design analog protype Wc = Math.Sqrt(WcArray[0] * WcArray[1]); //modified 3dB frequency double Bw = WcArray[1] - WcArray[0]; LinearSystems.LowpassToBandpass(z, p, ref k, Wc, Bw); //frequency transformation in s-domain LinearSystems.ZeroPoleToTransferFun(num, den, z, p, k); FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr, -1, 1); SignalUtils.FrequencyResponseS(num, den, FreqFr, Response, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); } *) function ChebyshevIIOrder(const BEdges: array of double; PassRipple,StopRipple: double; FilterType: TFilterType; var CutoffFreq: array of double; Analog: boolean = False): integer; (*Estimate the order of the Elliptic filter. Bedg array must contain the band edges of the transition region(s) sorted in ascending order. PassRipple defines the ripple of the passband and StopRipple defines the ripple of the stopband. The length of the CutoffFreq array must be equal to one half of the length of the BEdg array and must match the specified FilterType. The routine returns the estimated order as a result and fill's the CutoffFreq array. This array can then be passed to the EllipticFilter routine. Design a digital bandstop filter with transition band between 0.2..0.3 and 0.6..0.7 Hz and with at least 50dB attenuation in the stopband and. The passband should not have more then 0.2dB ripple. The sampling frequency is 2Hz. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k,Wc; double[] WcArray = new double[2]; int Order; //design a fifth order filter. double FS = 2; Order = IIRFilters.EllipticOrder(new double[4] { 0.2, 0.3, 0.6, 0.7 }, 0.2, 50, TFilterType.ftBandstop, ref WcArray, true); //design analog protype IIRFilters.EllipticAnalog(Order, 0.2, 50, z, p, out k); //design analog protype LinearSystems.Bilinear(z, p, ref k, FS, true); Wc = Math.Sqrt(WcArray[0] * WcArray[1]); //modified 3dB frequency double Bw = WcArray[1] - WcArray[0]; LinearSystems.LowpassToBandStopZ(z, p, ref k, Wc, Bw, LinearSystems.BilinearUnwarp(1,FS)); LinearSystems.ZeroPoleToTransferFun(num, den, z, p, k); SignalUtils.FrequencyResponse(num, den, Response, 32, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); } *) function EllipticOrder(const BEdges: array of double; PassRipple,StopRipple: double; FilterType: TFilterType; var CutoffFreq: array of double; Analog: boolean = False): integer; (*Design Butterworth IIR filter. Design Butterworth filter of Order with CutoffFreq frequencies and of FilterType type. Set Analog to True, to request and analog filter design in s-plane or set it to false to obtain a digital filter design in z-plane. The CutoffFreq must be in range between 0 and 1 (Sampling frequency = 2) in case of a digital filter design. IIrFrequencyTransform specifies when and how will the frequency band transformation be applied. The resulting transfer function is returned in the zero-pole form, with z,p,k variables. *) function ButterFilter(Order: integer;const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const z,p: TVec; out k: double; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the numerator/denumerator form with num and den.*) function ButterFilter(Order: integer;const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const num,den: TVec; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the second order section form stored in the sos variable. The sos variable can be passed directly to the IirInitBQ filter initialization routine. Second order section form delivers substantially higher numerical stability and range than filtering with num/den form which is used by the IirInit routine. *) function ButterFilter(Order: integer;const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const sos: TVec; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the state-space form with A,B,C,D variables. Design a digital bandstop filter with transition band between 0.2..0.3 and 0.6..0.7 Hz and with at least 50dB attenuation in the stopband. The passband should not have more then 0.2dB ripple. The sampling frequency is 2Hz. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double[] WcArray = new double[2]; int Order; //design a fifth order filter. Order = IIRFilters.ButterOrder(new double[4] { 0.2, 0.3, 0.6, 0.7 }, 0.2, 50, TFilterType.ftBandstop, ref WcArray, true); //design analog protype IIRFilters.ButterFilter(Order, WcArray, TFilterType.ftBandstop, false, num, den, TIirFrequencyTransform.ftStateSpaceAnalog); // Alternative 1. Specify the order and the 3 dB frequencies explicitely: // // IIRFilters.ButterFilter(5, new double[2] {0.2,0.7}, TFilterType.ftBandstop,false,num,den,TIirFrequencyTransform.ftStateSpaceAnalog); // Alternative 2. Specifying the 3 dB frequencies explicitely // will result in 3 dB ripple (and not 0.2 as requested) in the passband, // but one coulde always move the 3 dB frequencies a little: // // IIRFilters.ButterFilter(5, new double[2] { 0.22, 0.68 }, TFilterType.ftBandstop, false, num, den, TIirFrequencyTransform.ftStateSpaceAnalog); // Alternative 3. Specifying the order explicitely // will not ensure 50 dB attenuation in the edges of the stopband, // but one can increase filter order: // // IIRFilters.ButterFilter(10, new double[2] { 0.22, 0.68 }, TFilterType.ftBandstop, false, num, den, TIirFrequencyTransform.ftStateSpaceAnalog); SignalUtils.FrequencyResponse(num, den, Response, 32, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); *) function ButterFilter(Order: integer;const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const A: TMtx; const B,C: TVec; out d: double): double; overload; (*Design Chebyshev type I IIR filter. Design Chebyshev type I filter of Order with CutoffFreq frequencies and of FilterType type. Set Analog to True, to request and analog filter design in s-plane or set it to false to obtain a digital filter design in z-plane. PassRipple defines the passband ripple in dB. The CutoffFreq must be in range between 0 and 1 (Sampling frequency = 2) in case of a digital filter design. IIrFrequencyTransform specifies when and how will the frequency band transformation be applied. The resulting transfer function is returned in the zero-pole form, with z,p,k variables. *) function ChebyshevIFilter(Order: integer; PassRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const z,p: TVec; out k: double; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the numerator/denumerator form with num and den.*) function ChebyshevIFilter(Order: integer; PassRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; num,den: TVec; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the second order section form stored in the sos variable. The sos variable can be passed directly to the IirInitBQ filter initialization routine. Second order section form delivers substantially higher numerical stability and range than filtering with num/den form which is used by the IirInit routine. *) function ChebyshevIFilter(Order: integer; PassRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const sos: TVec; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the state-space form with A,B,C,D variables. Design an analog bandpass filter with transition band between 1..2 and 5..7 rad/sec and with at least 50dB attenuation in the stopband and. The passband should not have more then 0.2dB ripple. Note: This example does not actually filter data. It only designs the filter. To see how to actually apply the filter check the IirInit and IirInitBQ routines. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double[] WcArray= new double[2]; //design analog filter int Order = IIRFilters.ChebyshevIOrder(new double[4] { 1, 2, 5, 7 }, 0.2, 50, TFilterType.ftBandpass, ref WcArray, true); IIRFilters.ChebyshevIFilter(Order, 50, WcArray, TFilterType.ftBandpass, true, num,den, TIirFrequencyTransform.ftStateSpaceAnalog); //design analog protype FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr, -1, 1); SignalUtils.FrequencyResponseS(num, den, FreqFr, Response, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); //Alternative: Design a digital filter with the passband between 0.2 and 0.5 Hz (FS =2) //Order = IIRFilters.ChebyshevIOrder(new double[4] { 0.1, 0.2, 0.5, 0.6 }, 0.2, 50, TFilterType.ftBandpass, ref WcArray, false); //IIRFilters.ChebyshevIFilter(Order, 50, WcArray, TFilterType.ftBandpass, false, num, den, TIirFrequencyTransform.ftStateSpaceAnalog); //design digital protype //FrequencyResponse(num,den,Response,64); //FreqFr = MtxExpr.Ramp(Response.Length, TMtxFloatPrecision.mvDouble, 0, 1.0 / Response.Length); //MtxVecTee.DrawIt(FreqFr, Response, "Frequency response", false); } *) function ChebyshevIFilter(Order: integer; PassRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const A: TMtx; const B,C: TVec; out d: double): double; overload; (*Design Chebyshev type II IIR filter. Design Chebyshev type II filter of Order with CutoffFreq frequencies and of FilterType type. Set Analog to True, to request an analog filter design in s-plane or set it to false to obtain a digital filter design in z-plane. StopRipple defines the stopband ripple in dB. CutoffFreq must be in range between 0 and 1 (Sampling frequency = 2) in case of a digital filter design. IirFrequencyTransform specifies when and how will the frequency band transformation be applied. The resulting transfer function is returned in the zero-pole form, with z,p,k variables. *) function ChebyshevIIFilter(Order: integer; StopRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const z, p: TVec; out k: double; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the second order section form stored in the sos variable. The sos variable can be passed directly to the IirInitBQ digital filter initialization routine. Second order section form delivers substantially higher numerical stability and range than filtering with num/den form which is used by the IirInit routine. *) function ChebyshevIIFilter(Order: integer; StopRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const sos: TVec; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the numerator/denumerator form with num and den.*) function ChebyshevIIFilter(Order: integer; StopRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const num, den: TVec; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the state-space form with A,B,C,D variables. Design a discrete highpass filter with transition band between 10..12 Hz, if the sampling frequency is 30 Hz. The stopband should have more then 50 dB attenuation and the passband should not have more then 0.2dB ripple. This example does not actually filter data. It only designs the filter. To see how to actually apply the filter check the IirInit and IirInitBQ routines. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); double[] WcArray = new double[1]; int Order; Order = IIRFilters.ChebyshevIIOrder(new double[2] { 10.0*2/30, 12.0*2/30 }, 0.2, 50, TFilterType.ftHighpass, ref WcArray, false); //design analog protype IIRFilters.ChebyshevIIFilter(Order, 50, WcArray, TFilterType.ftHighpass, false, num, den, TIirFrequencyTransform.ftStateSpaceAnalog); SignalUtils.FrequencyResponse(num, den, Response, 32, false, TSignalWindowType.wtRectangular, 0); Vector FreqFr = MtxExpr.Ramp(Response.Length, mvDouble,0, 1.0 / Response.Length); //X axis DrawIt(FreqFr,20*MtxExpr.Log10(MtxExpr.Abs(Response)),"Magnitude",false); } *) function ChebyshevIIFilter(Order: integer; StopRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const A: TMtx; const B,C: TVec; out d: double): double; overload; (*Design Elliptic IIR filter. Design Elliptic filter of Order with CutoffFreq frequencies and of FilterType type. Set Analog to True, to request an analog filter design in s-plane or set it to false to obtain a digital filter design in z-plane. CutoffFreq must be in range between 0 and 1 (Sampling frequency = 2) in case of a digital filter design. IIrFrequencyTransform specifies when and how will the frequency band transformation be applied. PassRipple defines the passband ripple in dB and StopRipple defines the stopband ripple in dB. The resulting transfer function is returned in the zero-pole form, with z,p,k variables. *) function EllipticFilter(Order: integer; PassRipple,StopRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const z, p: TVec; var k: double; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the numerator/denumerator form with num and den.*) function EllipticFilter(Order: integer; PassRipple,StopRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const num, den: TVec; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the second order section form stored in the sos variable. The sos variable can be passed directly to the IirInitBQ digital filter initialization routine. Second order section form delivers substantially higher numerical stability and range than filtering with num/den form used by the IirInit function. *) function EllipticFilter(Order: integer; PassRipple,StopRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const sos: TVec; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the state-space form with A,B,C,D variables. Design a discrete lowpass filter with transition band between 5..6 Hz, if the sampling frequency is 30 Hz. The stopband should have more then 40 dB attenuation and the passband should not have more then 0.1dB ripple. Note: This example does not actually filter data. It only designs the filter. To see how to actually apply the filter check the IirInit and IirInitBQ routines. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); double[] WcArray = new double[1]; int Order; //design a fifth order filter. Order = IIRFilters.EllipticOrder(new double[2] { 5.0/15, 6.0/15 }, 0.1, 40, TFilterType.ftLowpass, ref WcArray, false); //design analog protype IIRFilters.EllipticFilter(Order, 0.1,40, WcArray, TFilterType.ftLowpass, false, num, den, TIirFrequencyTransform.ftStateSpaceAnalog); SignalUtils.FrequencyResponse(num, den, Response, 32, false, TSignalWindowType.wtRectangular, 0); Vector FreqFr = MtxExpr.Ramp(Response.Length, TMtxFloatPrecision.mvDouble,0, 30*0.5 / Response.Length); //X axis MtxVecTee.DrawIt(FreqFr, Response, "Frequency response", false); } *) function EllipticFilter(Order: integer; PassRipple,StopRipple: double; const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const A: TMtx; const B, C: TVec; out d: double): double; overload; (*Design Bessel IIR filter. Design Bessel filter of Order with CutoffFreq frequencies and of FilterType type. Set Analog to True, to request an analog filter design in s-plane or set it to false to obtain a digital filter design in z-plane. CutoffFreq must be in range between 0 and 1 (Sampling frequency = 2). IIrFrequencyTransform specifies when and how will the frequency band transformation be applied. Bessel filters typically do not preserve a flat group delay once transformed to z-domain. The routine uses bilinear transformation to map from s to z-domain. Use matched-Z transform to preserve more phase properties of the Bessel filters in the z-domain. The resulting transfer function is returned in the zero-pole form, with z,p,k variables. *) function BesselFilter(Order: integer;const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const z, p: TVec; var k: double; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the numerator/denumerator form with num and den.*) function BesselFilter(Order: integer;const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const num,den: TVec; IirFrequencyTransform: TIirFrequencyTransform = ftStateSpaceAnalog): double; overload; (*The resulting transfer function is returned in the state-space form with A,B,C,D variables. Design a fifth order analog lowpass filter with the cutoff frequency at 3 rad/sec. Note: This example does not actually filter data. It only designs the filter. To see how to actually apply the filter check the IirInit and IirInitBQ routines. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); IIRFilters.BesselFilter(5, new double[1] {3}, TFilterType.ftLowpass, true, num,den,TIirFrequencyTransform.ftStateSpaceAnalog); //design analog protype FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr, -1, 1); SignalUtils.FrequencyResponseS(num, den, FreqFr, Response, 0); MtxVecTee.DrawIt(Response, "Frequency response", false); } *) function BesselFilter(Order: integer;const CutoffFreq: array of double; FilterType: TFilterType; Analog: boolean; const A: TMtx; const B, C: TVec; out d: double): double; overload; (*Convert a TIirFilterMethod type to a string. The function returns a description of TIirFilterMethod specified by the IirFilterMethod variable. *) function IirFilterMethodToString(IirFilterMethod: TIIRFilterMethod): string; (*Compute exact zeroes of Butterworth and Cheybshev type I filter. The order defies the Order of the filter, WC is the bilinear warped center frequency of the stopband of a bandstop filter. FilterType defines, if the filter is lowpass, highpass, bandpass or bandstop, Analog specifies, if the filter is designed for the s-domain or for the z-domain. The result is placed in z. *) procedure ExactIirZeros(Order: integer; Wc: double; FilterType: TFilterType; Analog: boolean; const z: TVec); overload; {$I BdsppDefs.inc} (* Utility routines for managing linear systems. Conversion routines between different representations of the transfer function, frequency transformations and bilinear transform. *) unit LinearSystems; interface uses MtxVec, Polynoms, Math387; (* Convert transfer function from zero-pole to state-space form. Convert a transfer function defined with zeros Z and poles P and gain K in to its state space presentation of the real valued A matrix, B,C vectors and D value. *) procedure ZeroPoleToStateSpace(const A: TMtx; const B, C: TVec; out D: Double; const z, p: TVec; const k: Double); (*Convert transfer function from state-space to zero-pole form. Convert a transfer function defined in state space presentation of the real valued A matrix, B,C vectors and D value. in to its zero-pole form with zeros Z, poles P and gain K. State space system is related to its numerator/denominator representation via the following relation: num(s) (s-sz1)*...*(s-szn) H(s) = -------- = K*------------------- =C*(s*I-A)^(-1)*B + D den(s) (s-sp1)*...*(s-spn) dx/dt = Ax + Bu y = Cx + Du x - system input y - system output szn - n'th zero spn - n'th pole K - system gain *) procedure StateSpaceToZeroPole(const z, p: TVec; out k: Double; const A: TMtx; const B,C: TVec; const D: Double); (* Convert transfer function from state-space to numerator-denominator form. Convert a transfer function defined in state space presentation of the real valued A matrix, B,C vectors and D value in to its numerator Num and denominator Den form. State space system is related to its numerator/denominator representation via the following relation: num(s) H(s) = -------- = C*(s*I-A)^(-1)*B + D den(s) dx/dt = Ax + Bu y = Cx + Du x - system input y - system output *) procedure StateSpaceToTransferFun(const num, den: TVec; const A: TMtx; const B, C: TVec; const D: Double); (* Convert transfer function from numerator-denominator to state-space form. Convert transfer function in form of a numerator Num and denominator Num in to its state space presentation of the real valued A matrix, B,C vectors and D value. State space system is related to its numerator/denominator representation via the following relation: num(s) H(s) = -------- = C*(s*I-A)^(-1)*B + D den(s) dx/dt = Ax + Bu y = Cx + Du x - system input y - system output *) procedure TransferFunToStateSpace(const A: TMtx; const B, C: TVec; out D: Double; const num,den: TVec); (* Convert transfer function from zero-pole to numerator-denominator form. Convert a rational polynomial defined with zeros Z and poles P and gain K in to its numerator Num and denominator Den form. The numerator will be scaled by K and both polynomials are assumed to have only real coefficents. (Poles and zeros can still be complex, if they have complex conjugated pairs.) num(s) (s-sz1)*...*(s-szn) H(s) = -------- = K*------------------- den(s) (s-sp1)*...*(s-spn) szn - n'th zero spn - n'th pole K - system gain The following example computes the coefficients of the rational polynomial. The numerator has zeros at 3 and 4 and the denominator has zeros at 1 and 2. The polynomial in zero pole form can be written as: (x - 3)*(x - 4) --------------- (y - 2)*(y - 1) And in transfer function form: x^2 - 7*x + 12 -------------- x^2 - 3*x + 2 Notice that powers are falling from left to right. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); double k; z.SetIt(false,new double[2] {3,4}); p.SetIt(false,new double[2] {1,2}); k = 1; LinearSystems.ZeroPoleToTransferFun(num,den,z,p,k); MtxVecEdit.ViewValues(num,"Numerator",true); MtxVecEdit.ViewValues(den,"Denominator",true); // num = [1, -7, 12] // den = [1, -3, 2] } *) procedure ZeroPoleToTransferFun(const num,den: TVec; const z,p: TVec; k: Double); (* Convert transfer function from zero-pole to second order sections form. Convert a rational polynomial defined with zeros Z and poles P and gain K in to a product of second order sections. The numerator in the last section will be scaled by K and all polynomials are assumed to have only real coefficents. (Poles and zeros can still be complex, if they have complex conjugated pairs.) Second order sectios are stored in destination vector in the order: Num1, Den1, Num2, Den2... DenN. Each section consists of 3 items (a,b,c) from the formula : a*x^2 + b*x + c num_sos1(s) num_sosN(s) (s-sz1)*...*(s-szn) H(s) = ------------- * ... * ------------- = K*------------------- den_sos1(s) den_sosN(s) (s-sp1)*...*(s-spn) szn - n'th zero spn - n'th pole K - system gain *) procedure ZeroPoleToSOS(const sos: TVec; const z,p: TVec; k: Double); (*Convert transfer function from numerator-denominator to zero-pole form. Convert a transfer function defined with numerator Num and denominator Den in to its zero-pole form with zeros Z, poles P and gain K. The routine calls PolyRoots routine from the Polynoms unit. Numerator and denominator can be real or complex. A rational polynomial can be converted to zero pole form by finding the roots of the numerator and denominator: x^2 - 7*x + 12 -------------- x^2 - 3*x + 2 Zero pole form: (x - 3)*(x - 4) --------------- (y - 2)*(y - 1) using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(3); Vector den = new Vector(3); double k; num.Values[0] = 1; num.Values[1] = -7; num.Values[2] = 12; den.Values[0] = 1; den.Values[1] = -3; den.Values[2] = 2; k = 1; LinearSystems.TransferFunToZeroPole(z, p, out k, num, den); MtxVecEdit.ViewValues(z,"Zeros",true); MtxVecEdit.ViewValues(p,"Poles",true); // z = [4 , 3] // p = [2 , 1] // k = num[0]/den[0] = 1; } *) procedure TransferFunToZeroPole(const z, p: TVec; out k: Double; const num,den: TVec); (* Find zeros of a linear time invariant system in state-space form. Compute zeros of a linear time invariant system represented in state space form. The resulting zeros are placed in Z and computed system gain in Gain. If a linear time invariant system is represented by A,B,C,D, a zero pole representation can be obtained like this: using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector b = new Vector(3); Vector c = new Vector(3); Matrix a = new Matrix(3, 3); double k,d; a.SetIt(3, 3, false, new double[9] {0,1,2, 2,3,4, 5,6,3}); b.Values[0] = 1; b.Values[1] = 0.5; b.Values[2] = 2; c.Values[0] = 1; c.Values[1] = 2.5; c.Values[2] = 2; d = 1; // .... get a state space version and store in A,B,C,D a.Eig(p,TMtxType.mtGeneral); //compute poles LinearSystems.LTIZeros(z, out k, a, b, c, d); //get zeros and gain MtxVecEdit.ViewValues(z,"Zeros",true); MtxVecEdit.ViewValues(p,"Poles",true); } *) procedure LTIZeros(const z: TVec; out gain: Double; const A: TMtx; const B, C:TVec; const D: Double); (* Compute bilinear transformation. Apply bilinear transform to transfer function represented in zero-pole form. FS defines the sampling frequency. If z.Length < p.Length the routine will not add zeroes at -1 to match the number of poles unless AddZeros is True. Bilinear transformation is defined with the mapping: 2 (1 - z^(-1)) s -> - * ------------ T (1 - z^(-1)) Bilinear transform requires that the amplitude response of a continuous system is piecewise constant and can not be used to transform an analog differentiator to a digital filter (for example). Bilinear transform also does not preserve the impulse or the phase response [1]. References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 *) procedure Bilinear(const z, p: TVec; var k: Double; FS: Double = 2; AddZeros: boolean = True); overload; (*Apply bilinear transform to a linear system represented in state-space form. An analog lowpass filter is converted to z-domain by using the bilinear transform. The analog filter has a normalized cutoff frequency at 1 rad/sec. This frequency is mapped by the bilinear transformation to the value as returned by the function: BilinearUnwarp(1). To obtain the required cutoff frequency of an analog lowpass filter, which will map to a selected frequency in z-domain use the BilinearPrewarp routine. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector b = new Vector(0); Vector c = new Vector(0); double k,Wc,d; Matrix A = new Matrix(0,0); double FS = 2; int Order = 5; //design a fifth order filter. IIRFilters.EllipticAnalog(Order,0.2,40,z,p,out k); //design analog protype //passband ripple 0.2dB, stopband attenuation 40dB LinearSystems.Bilinear(z,p,ref k,FS,true); //Sampling frequency = 2 Wc = 0.6; //request a cutoff at 0.6 Hz LinearSystems.LowpassToLowpassZ(z,p,ref k,Wc, LinearSystems.BilinearUnwarp(1,FS)); //frequency transformation in z-domain LinearSystems.ZeroPoleToTransferFun(num,den,z,p,k); SignalUtils.FrequencyResponse(num, den, Response, 64, false, TSignalWindowType.wtRectangular, 0); //zero padding set to 64 MtxVecTee.DrawIt(Response, "Design method 0", false); //Alternative 1: IIRFilters.EllipticAnalog(Order,0.2,40,z,p,out k); //design analog protype LinearSystems.Bilinear(z,p,ref k,FS,true); //Sampling frequency = 2 LinearSystems.ZeroPoleToTransferFun(num,den,z,p,k); Wc = 0.6; //request a cutoff at 0.6 Hz LinearSystems.LowpassToLowpassZ(num,den,Wc,LinearSystems.BilinearUnwarp(1,FS)); //frequency transformation in z-domain SignalUtils.FrequencyResponse(num, den, Response, 64, false, TSignalWindowType.wtRectangular, 0); //zero padding set to 64 MtxVecTee.DrawIt(Response, "Design method 1", false); //Alternative 2: IIRFilters.EllipticAnalog(Order,0.2,40,z,p,out k); //design analog protype Wc = LinearSystems.BilinearPrewarp(0.6,FS); //request a cutoff at 0.6 Hz LinearSystems.LowpassToLowpass(z,p,ref k,Wc); //frequency transformation in s-domain LinearSystems.Bilinear(z,p,ref k, FS,true); //Sampling frequency = 2 LinearSystems.ZeroPoleToTransferFun(num,den,z,p,k); SignalUtils.FrequencyResponse(num, den, Response, 64, false, TSignalWindowType.wtRectangular, 0); //zero padding set to 64 MtxVecTee.DrawIt(Response, "Design method 2", false); //Alternative 3: IIRFilters.EllipticAnalog(Order,0.2,40,z,p,out k); //design analog protype LinearSystems.ZeroPoleToStateSpace(A,b,c,out d,z,p,k); Wc = LinearSystems.BilinearPrewarp(0.6,FS); //request a cutoff at 0.6 Hz LinearSystems.LowpassToLowpass(A,b,c,ref d,Wc); //frequency transformation in s-domain LinearSystems.Bilinear(A,b,c,ref d,2); LinearSystems.StateSpaceToZeroPole(z,p,out k,A,b,c,d); LinearSystems.ZeroPoleToTransferFun(num,den,z,p,k); SignalUtils.FrequencyResponse(num,den,Response,64,false,TSignalWindowType.wtRectangular,0); //zero padding set to 64 MtxVecTee.DrawIt(Response,"Design method 3",false); } *) procedure Bilinear(const A: TMtx;const B,C: TVec; var D: Double; FS: Double = 2); overload; (* Frequency transformation from a lowpass to a bandstop filter in s-domain. Transform a lowpass filter prototype in zero-pole form to a bandstop filter, where the stopband has width BW centered around the frequency CenterFreq. Assumed sampling frequency is 2. The transformation is defined as ([1], p. 258): s(Wu - Wl) s --> ------------- s^2 + Wu*Wl Wl - lower cutoff frequency Wu - upper cutoff frequency The routine also adds pairs of zeros at +/-j*CenterFreq, if the number of zeros is less then number of poles, to match the number of poles. References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 *) procedure LowpassToBandstop(const z, p: TVec; var k: Double; CenterFreq,Bw: Double); overload; (* Convert a lowpass filter prototype in state space form to a bandstop filter. Design an analog bandstop filter, where the stopband is defined with Wl = 0.2 rad/sec, Wu = 0.6 rad/sec using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k,Wc,BW; double FS = 2; int Order = 5; //design a fifth order filter. IIRFilters.EllipticAnalog(Order,0.2,40,z,p,out k); //design analog protype Wc = Math.Sqrt(0.2*0.6); BW = 0.6 - 0.2; LinearSystems.LowpassToBandstop(z,p,ref k,Wc,BW); //frequency transformation in s-domain LinearSystems.ZeroPoleToTransferFun(num,den,z,p,k); //Define the frequency grid (logarithmic) FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr,-1,1); //between 0.1 (=10^(-1)) and 10 (=10^1) rad/sec SignalUtils.FrequencyResponseS(num,den,FreqFr,Response,0); //Laplace MtxVecTee.DrawIt(Response,"Frequency response",false); //Y axis linear, X axis logarithmic } *) procedure LowpassToBandstop(const a: TMtx; const b,c: TVec; var D: Double; CenterFreq,Bw: Double); overload; (* Frequency transformation from a lowpass to a bandpass filter in s-domain. Transform a lowpass filter prototype in zero-pole form to bandpass filter, where the passband of width BW is centered around the frequency CenterFreq. Assumed sampling frequency is 2. The transformation is defined as ([1], p. 258): s^2 + Wu*Wl s --> ------------- s(Wu - Wl) Wl - lower cutoff frequency Wu - upper cutoff frequency The routine also adds zeros at 0. It adds one zero, if the lowpass filter order is odd and already has zeros. If the filter does not have zeros, it adds sufficient zeros at 0 to match the order of the lowpass filter. References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 *) procedure LowpassToBandpass(const z, p: TVec; var k: Double; CenterFreq, Bw: Double); overload; (* Convert a lowpass filter prototype in state space form to a bandstop filter. Design an analog bandpass filter, where the passband is defined with Wl = 0.2 rad/sec, Wu = 0.6 rad/sec using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k,Wc,BW; double FS = 2; int Order = 5; //design a fifth order filter. IIRFilters.EllipticAnalog(Order,0.2,40,z,p,out k); //design analog protype Wc = Math.Sqrt(0.2*0.6); BW = 0.6 - 0.2; LinearSystems.LowpassToBandpass(z,p,ref k,Wc,BW); //frequency transformation in s-domain LinearSystems.ZeroPoleToTransferFun(num,den,z,p,k); //Define the frequency grid (logarithmic) FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr,-1,1); //between 0.1 (=10^(-1)) and 10 (=10^1) rad/sec SignalUtils.FrequencyResponseS(num,den,FreqFr,Response,0); //Laplace MtxVecTee.DrawIt(Response,"Frequency response",false); //Y axis linear, X axis logarithmic } *) procedure LowpassToBandpass(const a: TMtx; const b,c: TVec; var D: Double; CenterFreq, BW: Double); overload; (* Frequency transformation from a lowpass to a highpass filter in s-domain. Transform a lowpass filter prototype in zero-pole form to a highpass filter, where the new cutoff frequency is Freq. Assumed sampling frequency is 2. The transformation is defined as ([1], p. 258): Wu s --> ----- s Wu - new cutoff frequency The routine also adds zeros at 0. It adds one zero, if the lowpass filter order is odd and already has zeros. If the filter does not have zeros, it adds sufficient zeros at 0 to match the order of the filter. References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 *) procedure LowpassToHighpass(const z, p: TVec; var k: Double; Freq: Double); overload; (* Transform a lowpass filter prototype in state space form to highpass filter. Design an analog highass filter, where the passband is defined with Wu = 0.2 rad/sec. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k,Wc; double FS = 2; int Order = 5; //design a fifth order filter. IIRFilters.EllipticAnalog(Order,0.2,40,z,p,out k); //design analog protype Wc = 0.2; LinearSystems.LowpassToHighpass(z,p,ref k,Wc); //frequency transformation in s-domain LinearSystems.ZeroPoleToTransferFun(num,den,z,p,k); //Define the frequency grid (logarithmic) FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr,-1,1); //between 0.1 (=10^(-1)) and 10 (=10^1) rad/sec SignalUtils.FrequencyResponseS(num,den,FreqFr,Response,0); //Laplace MtxVecTee.DrawIt(Response,"Frequency response",false); //Y axis linear, X axis logarithmic } *) procedure LowpassToHighpass(const A: TMtx; const B, C: TVec; var D: Double; Freq: Double); overload; (* Frequency transformation from a lowpass to a lowpass filter in s-domain. Transform a lowpass filter prototype in zero-pole form to a lowpass filter, where the new cutoff frequency is Freq. Assumed sampling frequency is 2. The transformation is defined as ([1], p. 258): s s --> ----- Wu Wu - new cutoff frequency References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 *) procedure LowpassToLowpass(const z, p: TVec; var k: Double; Freq: Double); overload; (* Transform a lowpass filter prototype in state space form to a lowpass filter. Design an analog lowpass filter, where the passband is defined with Wu = 0.7 rad/sec. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k,Wc,BW; double FS = 2; int Order = 5; //design a fifth order filter. IIRFilters.EllipticAnalog(Order,0.2,40,z,p,out k); //design analog protype Wc = 0.7; LinearSystems.LowpassToLowpass(z,p,ref k,Wc); //frequency transformation in s-domain LinearSystems.ZeroPoleToTransferFun(num,den,z,p,k); //Define the frequency grid (logarithmic) FreqFr.Length = 1000; SignalUtils.LogRamp(FreqFr,-1,1); //between 0.1 (=10^(-1)) and 10 (=10^1) rad/sec SignalUtils.FrequencyResponseS(num,den,FreqFr,Response,0); //Laplace MtxVecTee.DrawIt(Response,"Frequency response",false); //Y axis linear, X axis logarithmic } *) procedure LowpassToLowpass(const a: TMtx; const b,c: TVec; var D: Double; Freq: Double); overload; (* Transform the zeros and poles of a filter in s-domain to z-domain. Transform the zeros Z and poles P of a filter from s-domain to z-domain, where FS is the sampling frequency. The transformation is defined as ([1], p. 224): s + a --> 1 - z^(-1)*e^(-a/FS) FS - sampling frequency a - pole or zero If the analog system has zeros with center frequencies greater then half the sampling frequency, their z-plane positions will be greatly aliased [1]. The transformation has the advantage of not affecting the phase response of the original transfer function. References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 A bessel analog lowpass filter is converted to z-domain by using the matched Z transform. The analog filter has a normalized cutoff frequency at 1 rad/sec. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); double k,Wc,BW; double FS = 2; int Order = 5; //design a fifth order filter. IIRFilters.BesselAnalog(Order,z,p,out k); //design analog protype Wc = 0.5; LinearSystems.LowpassToLowpass(z,p,ref k,Wc); //frequency transformation in s-domain LinearSystems.MatchedZTransform(z, p, FS); k = k/LinearSystems.ComputeGain(z,p,1); z.Size(p.Length,false); z.SetVal(-1); //add missing zeros at -1 LinearSystems.ZeroPoleToTransferFun(num,den,z,p,k); SignalUtils.FrequencyResponse(num,den,Response,64,false,TSignalWindowType.wtRectangular,0); //zero padding set to 64 MtxVecTee.DrawIt(20*MtxExpr.Log10(MtxExpr.Abs(Response)),"Magnitude",false); MtxVecTee.DrawIt(MtxExpr.PhaseSpectrum(Response)*(180/Math.PI),"Phase",false); } *) procedure MatchedZTransform(const z,p: TVec; FS: Double = 1); overload; (*Returns gain of a filter with zeroes in z and poles in p. Returns gain of a filter with zeroes in z and poles in p. Val parameter defines the value at which to evaluate the rational polynomial: (x-z1)*....*(x-zn) (Val-z1)*....*(Val-zn) ------------------ ==> ---------------------- = number (x-p1)*....*(x-pn) (Val-p1)*....*(Val-pn) x - variable z1..zn - zeros p1..pn - poles The routine assumes that the coefficients of the polynomial are all real and therefore returns only the real part of the result. *) function ComputeGain(const z, p: TVec; Val: Double = 1): Double; overload; (* Returns gain of a filter with zeroes in z and poles in p. Val parameter defines the value at which to evaluate the rational polynomial. *) function ComputeGain(const z, p: TVec; Val: TCplx): Double; overload; (* Replace the variable of a rational polynomial with another rational polyniomial. Num is the nominator and den is the denominator of the original polynomial. Nz is the nominator and Dz is the denominator of the polynom to substitude the variable with in the original polynomial. The function returns modified num and den. Num and den must have the same length. Nz and Dz must have the same length. The advantage of this method is that rooting of the polynomials is not required. The resulting polynomial is normalized to have the coefficient for the highest power in the polynomial equal to 1. *) procedure RationalSubstitution(const num,den: TVec; const Nz,Dz: TVec); overload; (* Zeroes of the original polynomial are stored in z and poles in p vector. Nz is the nominator and Dz is the denominator of the polynom to substitude the variable with in the original polynomial. The function returns modified z and p. Nz and Dz must have the same length. k is the gain factor (not modified). Substitute the variable: z^2 - 2*z + 1 z - 1 ------------------ , z -- > ----- z^2- 4*z + 1 z - 2 The resulting polynomial: -0.5 ------------------ z^2 - 3*z + 1.5 using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector num = new Vector(0); Vector den = new Vector(0); Vector den1 = new Vector(0); Vector num1 = new Vector(0); num.SetIt(false,new double[3] {1,-2, 1}); den.SetIt(false,new double[3] {1,-4, 1}); num1.SetIt(false,new double[2] {1,-1}); den1.SetIt(false,new double[2] {1,-2}); LinearSystems.RationalSubstitution(num,den,num1,den1); MtxVecEdit.ViewValues(num,"Num",true); MtxVecEdit.ViewValues(den,"Den",true); // num = [ 0, 0, -0.5] // den = [ 1,-3, 1.5] } *) procedure RationalSubstitution(const z,p: TVec; var k: Double; const Nz,Dz: TVec); overload; (* Apply frequency band transformation from lowpass to lowpass in the z-domain. Freq is the cutoff frequency of the new filter. The function returns modified num and den. PrototypeFreq is the cutoff frequency of the prototype lowpass filter after it has been mapped to z-domain. Freq and PrototypeFreq must be between 0 and 1 (Sampling frequency = 2). The transformation is defined with the following mapping ([1] p. 260 and [2] p. 434, [3] p. 352): z^(-1) - a z^(-1) ---> -------------- 1 - a*z^(-1) sin((wc - wn)/2) a = ------------------- sin((wc + wn)/2) wc - old cutoff frequency wn - new (desired) cutoff frequency References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 [2] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989 [3] Digital signal processing, Vinay K. Ingle and John G. Proakis, Brooks-Cole, 2000 *) procedure LowpassToLowpassZ(const num,den: TVec; Freq: Double; PrototypeFreq: Double); overload; (* The function returns modified z (zeros), p (poles) and k (gain). Elliptic lowpass filter design. The cutoff frequency of an analog filter prototype transformed in to z domain is obtained with BilinearUnwarp method. The analog prototype filter has a normalized cutoff frequency at 1 rad/sec. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); double k, Wc; double FS = 2; int Order = 4; //design a fifth order filter. IIRFilters.EllipticAnalog(Order,0.1,30, z, p, out k); //design analog protype LinearSystems.Bilinear(z, p, ref k, FS,true); Wc = 0.5; LinearSystems.LowpassToLowpassZ(z, p, ref k, Wc, LinearSystems.BilinearUnwarp(1,FS)); LinearSystems.ZeroPoleToTransferFun(num,den, z, p, k); SignalUtils.FrequencyResponse(num, den, Response, 64, false, TSignalWindowType.wtRectangular, 0); //zero padding set to 64 //Alternative: // ... // LinearSystems.ZeroPoleToTransferFun(num,den, z, p, k); // LinearSystems.LowpassToLowpassZ(num,den, Wc, LinearSystems.BilinearUnwarp(1,FS)); MtxVecTee.DrawIt(Response, "Frequency response", false); //MtxVecTee.DrawIt(20 * MtxExpr.Log10(MtxExpr.Abs(Response)), "Magnitude", false); //MtxVecTee.DrawIt(MtxExpr.PhaseSpectrum(Response) * (180 / Math.PI), "Phase", false); } *) procedure LowpassToLowpassZ(const z,p: TVec;var k: Double; Freq,PrototypeFreq: Double); overload; (* Apply frequency band transformation from lowpass to highpass in the z-domain. Freq is the cutoff frequency of the new filter. The function returns modified num and den. PrototypeFreq is the cutoff frequency of the prototype lowpass filter after it has been mapped to z-domain. Freq and PrototypeFreq must be between 0 and 1 (Sampling frequency = 2). The transformation is defined with the following mapping ([1] p. 260 and [2] p. 434, [3] p. 352): z^(-1) + a z^(-1) ---> - ------------- 1 + a*z^(-1) cos((wc - wn)/2) a = ------------------- cos((wc + wn)/2) wc - old cutoff frequency wn - new (desired) cutoff frequency References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 [2] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989 [3] Digital signal processing, Vinay K. Ingle and John G. Proakis, Brooks-Cole, 2000 *) procedure LowpassToHighpassZ(const num,den: TVec; Freq: Double; PrototypeFreq: Double); overload; (* The function returns modified z (zeros), p (poles) and k (gain). Elliptic highpass filter design. The cutoff frequency of a lowpass analog filter prototype transformed in to z domain is obtained with BilinearUnwarp method. The analog prototype filter has a normalized cutoff frequency at 1 rad/sec. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); double k, Wc; double FS = 2; int Order = 4; //design a fifth order filter. IIRFilters.EllipticAnalog(Order,0.1,30, z, p, out k); //design analog protype LinearSystems.Bilinear(z, p, ref k, FS,true); Wc = 0.5; LinearSystems.LowpassToHighpassZ(z, p, ref k, Wc, LinearSystems.BilinearUnwarp(1,FS)); LinearSystems.ZeroPoleToTransferFun(num,den, z, p, k); SignalUtils.FrequencyResponse(num, den, Response, 64, false, TSignalWindowType.wtRectangular, 0); //zero padding set to 64 //Alternative: // ... // LinearSystems.ZeroPoleToTransferFun(num,den, z, p, k); // LinearSystems.LowpassToHighpassZ(num,den, Wc, LinearSystems.BilinearUnwarp(1,FS)); MtxVecTee.DrawIt(Response, "Frequency response", false); //MtxVecTee.DrawIt(20 * MtxExpr.Log10(MtxExpr.Abs(Response)), "Magnitude", false); //MtxVecTee.DrawIt(MtxExpr.PhaseSpectrum(Response) * (180 / Math.PI), "Phase", false); } *) procedure LowpassToHighpassZ(const z, p: TVec;var k: Double; Freq: Double; PrototypeFreq: Double); overload; (* Apply frequency band transformation from lowpass to bandpass in the z-domain. Freq is the center frequency of the passband with width BW of the new filter. The function returns modified num and den. PrototypeFreq is the cutoff frequency of the prototype lowpass filter after it has been mapped to z-domain. Freq, BW and PrototypeFreq must be between 0 and 1 (Sampling frequency = 2). The transformation is defined with the following mapping ([1] p. 260 and [2] p. 434, [3] p. 352): -a2 + a1*z^(-1) - z^(-2) z^(-1) ---> --------------------------- 1 - a1*z^(-1) + a2*z^(-2) cos((w2 + w1)/2) Beta = ------------------- cos((w2 - w1)/2) k = cot((w2-w1)/2)*tan(wc/2) a1 = 2*beta*k1/(k1+1) a2 = (k1-1)/(k1+1) wc - old cutoff frequency w2 - desired upper cutoff frequency w1 - desired lower cutoff frequency References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 [2] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989 [3] Digital signal processing, Vinay K. Ingle and John G. Proakis, Brooks-Cole, 2000 *) procedure LowpassToBandpassZ(const num,den: TVec; Freq, BW: Double; PrototypeFreq: Double); overload; (* The function returns modified z (zeros), p (poles) and k (gain). Elliptic bandpass filter design. The cutoff frequency of a lowpass analog filter prototype transformed in to z domain is obtained with BilinearUnwarp method. The analog prototype filter has a normalized cutoff frequency at 1 rad/sec. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); double k, Wc; double FS = 2; int Order = 4; //design a fourth order filter. IIRFilters.EllipticAnalog(Order,0.1,30, z, p, out k); //design analog protype LinearSystems.Bilinear(z, p, ref k, FS,true); double w1 = 0.2; //start of the passband at 0.2Hz. double w2 = 0.5; //stop of the passband at 0.5Hz. Wc = Math.Sqrt(w1*w2); //center frequency of the passband double BW = w2-w1; //passband width LinearSystems.LowpassToBandpassZ(z, p, ref k, Wc, BW, LinearSystems.BilinearUnwarp(1,FS)); LinearSystems.ZeroPoleToTransferFun(num,den, z, p, k); SignalUtils.FrequencyResponse(num, den, Response, 64, false, TSignalWindowType.wtRectangular, 0); //zero padding set to 64 //Alternative: // ... // LinearSystems.ZeroPoleToTransferFun(num,den, z, p, k); // LinearSystems.LowpassToBandpassZ(num,den, Wc, BW, LinearSystems.BilinearUnwarp(1,FS)); MtxVecTee.DrawIt(Response, "Frequency response", false); //MtxVecTee.DrawIt(20 * MtxExpr.Log10(MtxExpr.Abs(Response)), "Magnitude", false); //MtxVecTee.DrawIt(MtxExpr.PhaseSpectrum(Response) * (180 / Math.PI), "Phase", false); } *) procedure LowpassToBandpassZ(const z, p: TVec;var k: Double;Freq, BW: Double; PrototypeFreq: Double); overload; (* Apply frequency band transformation from lowpass to bandstop in the z-domain. Freq is the center frequency of the stopband with width BW of the new filter. The function returns modified num and den. PrototypeFreq is the cutoff frequency of the prototype lowpass filter after it has been mapped to z-domain. Freq, BW and PrototypeFreq must be between 0 and 1 (Sampling frequency = 2). The transformation is defined with the following mapping ([1] p. 260 and [2] p. 434, [3] p. 352): a2 - a1*z^(-1) + z^(-2) z^(-1) ---> --------------------------- 1 - a1*z^(-1) + a2*z^(-2) cos((w2 + w1)/2) Beta = ------------------- cos((w2 - w1)/2) k = tan((w2-w1)/2)*tan(wc/2) a1 = 2*beta/(k1+1) a2 = (1-k1)/(k1+1) wc - old cutoff frequency w2 - desired upper cutoff frequency w1 - desired lower cutoff frequency References: [1] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 [2] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989 [3] Digital signal processing, Vinay K. Ingle and John G. Proakis, Brooks-Cole, 2000 *) procedure LowpassToBandstopZ(const num, den: TVec; Freq, BW: Double; PrototypeFreq: Double); overload; (* The function returns modified z (zeros), p (poles) and k (gain). Elliptic bandstop filter design. The cutoff frequency of a lowpass analog filter prototype transformed in to z domain is obtained with BilinearUnwarp method. The analog prototype filter has a normalized cutoff frequency at 1 rad/sec. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector p = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); double k, Wc; double FS = 2; int Order = 4; //design a fourth order filter. IIRFilters.EllipticAnalog(Order,0.1,30, z, p, out k); //design analog protype LinearSystems.Bilinear(z, p, ref k, FS,true); double w1 = 0.2; //start of the stopband at 0.2Hz. double w2 = 0.5; //stop of the stopband at 0.5Hz. Wc = Math.Sqrt(w1*w2); //center frequency of the stopband double BW = w2-w1; //passband width LinearSystems.LowpassToBandPStopZ(z, p, ref k, Wc, BW, LinearSystems.BilinearUnwarp(1,FS)); LinearSystems.ZeroPoleToTransferFun(num,den, z, p, k); SignalUtils.FrequencyResponse(num, den, Response, 64, false, TSignalWindowType.wtRectangular, 0); //zero padding set to 64 //Alternative: // ... // LinearSystems.ZeroPoleToTransferFun(num,den, z, p, k); // LinearSystems.LowpassToBandstopZ(num,den, Wc, BW, LinearSystems.BilinearUnwarp(1,FS)); MtxVecTee.DrawIt(Response, "Frequency response", false); //MtxVecTee.DrawIt(20 * MtxExpr.Log10(MtxExpr.Abs(Response)), "Magnitude", false); //MtxVecTee.DrawIt(MtxExpr.PhaseSpectrum(Response) * (180 / Math.PI), "Phase", false); } *) procedure LowpassToBandstopZ(const z, p: TVec;var k: Double; Freq, BW: Double; PrototypeFreq: Double); overload; (* Returns prewarped frequency according to the bilinear transform. The function returns the frequency, which will map to Freq after the bilinear transformation has been aplied. The mapping is defined as ([1], p. 338, eq 8.28): Result = (2/Pi)*ArcTan(Freq/(2*FS)); FS defines the sampling frequency. T = 1/FS. References: [1] Digital signal processing, Vinay K. Ingle and John G. Proakis, Brooks-Cole, 2000 *) function BilinearPrewarp(Freq: Double; FS: Double = 2): Double; overload; (* Return unwarped frequency according to the bilinear transform. The function returns the frequency, to which the Freq will map after the bilinear transformation has been aplied. The mapping is defined as ([1], p. 338, eq 8.28): Result = 2*FS*tan(Pi*Freq/2); FS defines the sampling frequency. T = 1/FS. References: [1] Digital signal processing, Vinay K. Ingle and John G. Proakis, Brooks-Cole, 2000 *) function BilinearUnwarp(Freq: Double; FS: Double = 2): Double; overload; unit MtxParseSignal; interface {$I BdsppDefs.inc} (*Parks-McClellan optimal filter design routines.*) unit OptimalFir; interface uses Math387, MtxVec, SignalUtils, MtxVecBase; type (*Defines the type of the filter for Parks-McClellan algorithm. Defines the type of the filter the Parks-McClellan algorithm can create. Althought it is possible to design Double integrators and Double differentiators, these filter types do not have phase shifted as required (Double differentiator requires a 180 degree phase shift), but their amplitude response is correct. A -90 degree phase shift is a delay and a +90 degree shift is possible, because the total filter delay is (n-1)/2, where n is the length of the impulse response. The phase shift is evaluated relatively to the total filter delay. A filter with -90 degrees phase shift can be converted to a filter with a +90 degree phase shift, by scaling all the taps with -1. Inverse Hilbert transformer can be obtained by scaling the Hilbert transformer with -1. *) TRemezType = ( (*Supports all lowpass, highpass, bandpass, bandstop and multiband filters.*) rmtBandPass, (*Designs a filter with a +20dB/decade slope and +90 degree phase shift.*) rmtDifferentiator, (*Designs an allpass filter with a -90 degree phase shift.*) rmtHilbert, (*Designs a filter with a -20dB/decade slope and a -90 degree phase shift.*) rmtIntegrator, (*Designs a filter with a +40dB/decade slope and a +90 degree phase shift.*) rmtDoubleDifferentiator, (*Designs a filter with a -40dB/decade slope and a -90 degree phase shift.*) rmtDoubleIntegrator ); (*Defines filter symmetry. Define the filter symmetry, positive or negative. Positive symmetry is defined as h[-i] = h[i] and negative symmetry as h[-i] = -h[i], where h is centered around zero. Filters with negative symmetry shift the phase by 90 degrees across all frequencies. *) TFilterSymmetry = ( (**) fsmPositive, (**) fsmNegative); (*Design an optimal equiripple FIR filter with Parks-McClellan algorithm. An array of length h.Length on entry and contains the filter impulse response on exit. Defines the frequency bands. Defines, if the band is a stopband or a passband. Array contains the ratios between required ripples for different bands. Specifies the sampling frequency. Contains the maximum ripple error upon return. Choose between bandpass, hilbert, differentator and integrator. If True, the ripple will be constant and not weighted to give constant percentage error in case of the following filter types: rmtDifferentiator, rmtIntegrator, rmtDoubleDifferentiator, rmtDoubleIntegrator Designs an equiripple (optimal) FIR filter. The routine will not always converge. Parameters have to be specified, for which the filter exists. Most common causes for trouble are: too wide transition bands transition bands not of equal width. too strong attenuation (of the stopband) or to small ripple (of the passband) specified. wrong filter length (odd/even). The length of the filter can be estimated with the RemezLength routine. RemezLength routine will also properly adjust the error weights. An extensive explanation of the algorithm can be found in [1] Ch. 7.6, p. 462. A few need to know things about FIR filters: The length of the filter is defined as: n = Order-1. (Order is the order of the polynomial and n is the number of coefficients.) highpass or a bandstop filter or any filter with the passband at FS/2 has to have odd length (even order). RemezLength routine automatically adjusts filter length. a filter with negative symmetry also shifts the phase by 90 degrees. The routine automatically assumes negative symmetry, if the FilterType is different from rmtBandpass. Required stopband attenuation is usually specified in dB. To obtain the required ripple, the following formula can be used: Ripple = Exp10(Att/-20); Passband ripple can also be specified in dB. To obtain the required linear ripple, the following formula can be used: Ripple = (1-Exp10(PassRippldB/-20))/2 The passband ripple "rp" specified for the passband is a +/-rp specification. Total ripple is 2*rp. the amount of ripple is a function of filter length. (longer filters give less ripple). if different bands have different required ripple, this can be addressed by adjusting the error weights array. The Fortran source code can be found in [2] p. 198. References: A sample lowpass filter design with sampling frequency 8000Hz, transition band between at 1500Hz and 2000Hz with 60 dB attenuation (20 * Log10(0.001)) and 0.001 ripple in the passband. To try out other setups comment out the lowpass and comment in the desired filter. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector z = new Vector(0); Vector Response = new Vector(0); Vector Weights = new Vector(0); int n; double err; double FS = 2; //Sampling frequency //Design a lowpass n = OptimalFir.RemezLength(new double[4] {0, 1500, 2000, 4000}, new double[2] {1, 0}, new double[2] {0.001, 0.001} ,Weights, 8000); z.Size(n); OptimalFir.remez( z, new double[4] { 0, 1500, 2000, 4000 }, new double[2] { 1, 0 }, (double []) Weights, TRemezType.rmtBandPass, out err, 8000, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Lowpass", false); //Design a highpass n = OptimalFir.RemezLength(new double[4] { 0, 1500, 2000, 4000 }, new double[2] { 0, 1 }, new double[2] { 0.001, 0.001 }, Weights, 8000); z.Size(n); OptimalFir.remez(z, new double[4] { 0, 1500, 2000, 4000 }, new double[2] { 0, 1 }, (double[])Weights, TRemezType.rmtBandPass, out err, 8000, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Highpass", false); //Design a bandpass (Sampling frequency = 2) n = OptimalFir.RemezLength(new double[6] {0, 0.15, 0.25, 0.45, 0.55, 1}, new double[3] {0, 1, 0},new double[3] {0.001, 0.001, 0.001},Weights,FS); z.Size(n); OptimalFir.remez(z,new double[6] {0, 0.15, 0.25, 0.45, 0.55, 1}, new double[3] {0, 1, 0}, (double []) Weights, TRemezType.rmtBandPass, out err, FS, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Bandpass", false); //Design a bandstop (Sampling frequency = 2) n = OptimalFir.RemezLength(new double[6] { 0, 0.15, 0.25, 0.45, 0.55, 1 }, new double[3] { 1, 0, 1 }, new double[3] { 0.001, 0.001, 0.001 }, Weights,FS) ; z.Size(n); OptimalFir.remez(z, new double[6] { 0, 0.15, 0.25, 0.45, 0.55, 1 }, new double[3] { 1, 0, 1 }, (double[]) Weights, TRemezType.rmtBandPass, out err, FS, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Bandstop", false); //Design a multiband (Sampling frequency = 2) n = OptimalFir.RemezLength(new double[10] {0.00, 0.10, 0.20, 0.30, 0.40, 0.45, 0.55, 0.60, 0.70, 1.00}, new double[5] { 1, 0, 1, 0, 1 }, new double[5] { 0.001, 0.001, 0.001, 0.001, 0.001 }, Weights, FS); z.Size(n); OptimalFir.remez(z, new double[10] {0.00, 0.10, 0.20, 0.30, 0.40,0.45, 0.55, 0.60, 0.70, 1.00}, new double[5] { 1, 0, 1, 0, 1 }, (double[]) Weights, TRemezType.rmtBandPass, out err, FS, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Multibandpass", false); // Design a hilbert (type III) transformer (Sampling frequency = 2) n = OptimalFir.RemezLength(new double[6] {0, 0, 0.1, 0.9, 1, 1}, new double[3] {0,1,0},new double[3] {0.01,0.01,0.01},Weights,FS); if (n % 2 == 0) n++; //odd length type III filter z.Size(n); OptimalFir.remez(z,new double[2] {0.1, 0.9}, new double[1] {1}, new double[1] {1}, TRemezType.rmtHilbert,out err, FS, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Hilbert III", false); //Design a hilbert (type IV) transformer (Sampling frequency = 2) n = OptimalFir.RemezLength(new double[6] {0, 0, 0.1, 0.9, 1, 1}, new double[3] {0,1,0},new double[3] {0.01,0.01,0.01},Weights,FS); if (n % 2 != 0) n++; //even length type IV filter z.Size(n); OptimalFir.remez(z, new double[2] {0.1, 1}, new double[1] {1},new double[1] {1}, TRemezType.rmtHilbert,out err, 2, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Hilbert IV", false); //Design a differentiator (type III) (Sampling frequency = 2) n = OptimalFir.RemezLength(new double[6] {0, 0, 0.1, 0.9, 1, 1}, new double[3] {0,1,0}, new double[3] {0.01,0.01,0.01},Weights,FS); if (n % 2 == 0) n++; //odd length type III filter z.Size(n); OptimalFir.remez(z,new double[2] {0.1, 0.9}, new double[1] {1},new double[1] {1}, TRemezType.rmtDifferentiator,out err, 2, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Differentiator III", false); //Design a differentiator (type IV) (Sampling frequency = 2) n = OptimalFir.RemezLength(new double[6] {0, 0, 0.1, 0.9, 1, 1}, new double[3] {0,1,0},new double[3] {0.01,0.01,0.01},Weights, FS); if (n % 2 != 0) n++; //even length type IV filter z.Size(n); OptimalFir.remez(z,new double[2] {0.1, 1}, new double[1] {1},new double[1] {1}, TRemezType.rmtDifferentiator,out err, FS, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Differentiator IV", false); //Design a double differentiator (Sampling frequency = 2) n = OptimalFir.RemezLength(new double[6] {0, 0, 0.05, 0.95, 1, 1}, new double[3] {0,1,0},new double[3] {0.01,0.01,0.01},Weights, FS); if (n %2 != 0) n++; //even length type IV filter z.Size(n); OptimalFir.remez(z,new double[2] {0.05, 1}, new double[1] {1},new double[1] {1}, TRemezType.rmtDoubleDifferentiator,out err, FS, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Double differentiator IV", false); //Design an integrator (Sampling frequency = 2) n = OptimalFir.RemezLength(new double[6] {0, 0, 0.05, 0.95, 1, 1}, new double[3] {0,1,0},new double[3] {0.01,0.01,0.01}, Weights, FS); if (n %2 != 0) n++; //even length type IV filter z.Size(n); OptimalFir.remez(z,new double[2] {0.05, 1}, new double[1] {1},new double[1] {1}, TRemezType.rmtIntegrator,out err, FS, false); SignalUtils.FrequencyResponse(z, null, Response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(Response, "Integrator IV", false); //Design a double integrator (Sampling frequency = 2) n = OptimalFir.RemezLength(new double[6] {0, 0, 0.05, 0.95, 1, 1}, new double[3] {0,1,0}, new double[3] {0.01,0.01,0.01},Weights,FS); if (n %2 != 0) n++; //even length type IV filter z.Size(n); OptimalFir.remez(z,new double[2] {0.05, 1}, new double[1] {1}, new double[1] {1}, TRemezType.rmtDoubleIntegrator,out err, FS, false); SignalUtils.FrequencyResponse(z,null,Response,8,false,TSignalWindowType.wtRectangular,0); MtxVecTee.DrawIt(Response,"Double integrator IV",false); } *) function remez(const h: TVec; const bands, gains, weights: array of Double; FilterType: TRemezType; out err: Double; FS: Double = 2; ConstantRipple: boolean = false): integer; (*Design an optimal equiripple FIR filter with Parks-McClellan algorithm. H vector holds the impulse response on exit. The required linear ripple of the passband and 20*Log10(Ripple) is the required attenuation of the stop band. Parameter defines the filter type. Specifies the gain of the passband. Resulting filter length will be odd (not divisable by 2), if set to true. Default is true. The sampling frequency used to normalize transition band edges defined in the W array. Default value for FS is 2. Array which can hold only 2 (highpass/lowpass definition) or 4(bandpass/bandstop definition) parameters. The resulting impulse response is placed in H. Length of the filter is automatically estimated from the required Ripple and transition bandwidth. Function returns True, if the filter was succesfully designed. This does not guarantee that filter specifications have been meet. This routine is a simplified version of Remez and can be used to design: Lowpass, bandpass, bandstop, highpass, differentiators and hilbert transformers. Note: RemezImpulse routine designes FIR filters about 10-20% shorter than the KaiserImpulse routine. *) function RemezImpulse(const H: TVec;const W: array of Double; Ripple: Double; FilterType: TFilterType; Gain: Double = 1; FS: Double = 2; EnsureOdd: boolean = True): boolean; overload; (*Design an optimal equiripple FIR filter with Parks-McClellan algorithm. Required length of the filter must be preset by setting H.Length. H vector holds the impulse response on exit. RemezImpulse examples. Comment out the filter setup that you need. using Dew.Math; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector H = new Vector(0); Vector Response = new Vector(0); //Assumed sampling frequency = 2 double FS = 2; double TransBW = 0.02; //transition bandwidth in Hz. double Ripple = 0.001; //Lowpass filter OptimalFir.RemezImpulse(H,new double[2] {0.3,0.3+TransBW},Ripple, TFilterType.ftLowpass,1,FS,false); //Highpass filter OptimalFir.RemezImpulse(H,new double[2] {0.3,0.3+TransBW},Ripple, TFilterType.ftHighpass,1,FS,false); //Bandpass filter OptimalFir.RemezImpulse(H,new double[4] {0.3,0.3+TransBW, 0.5-TransBW,0.5},Ripple, TFilterType.ftBandpass,1,FS,false); //Bandstop filter OptimalFir.RemezImpulse(H,new double[4] {0.3,0.3+TransBW, 0.5-TransBW,0.5},Ripple, TFilterType.ftBandstop, 1,FS,false); // Type III Hilbert transformer OptimalFir.RemezImpulse(H,new double[2] {TransBW,1-TransBW},Ripple, TFilterType.ftHilbertIII,1,FS,false); // Type IV Hilbert transformer OptimalFir.RemezImpulse(H,new double[2] {TransBW,1},Ripple, TFilterType.ftHilbertIV,1,FS,false); // Type III linear phase differentiator filter SignalUtils.KaiserImpulse(H,new double[2] {1-TransBW,1},Ripple, TFilterType.ftDifferentiatorIII,1,FS,false); H.Scale(FS); //Scale by sampling frequency // Type IV linear phase differentiator filter SignalUtils.KaiserImpulse( H,new double[2] {1-TransBW,1},Ripple, TFilterType.ftDifferentiatorIV,1,FS,false); H.Scale(FS); //Scale by sampling frequency // Type III differentiator filter OptimalFir.RemezImpulse(H,new double[2] {0,1-TransBW},Ripple, TFilterType.ftDifferentiatorIII,1,FS,false); H.Scale(FS); //Scale by sampling frequency // Type IV differentiator filter OptimalFir.RemezImpulse( H,new double[2] {0,1-TransBW},Ripple, TFilterType.ftDifferentiatorIV,1,FS,false); H.Scale(FS); //Scale by sampling frequency // Type III 2x differentiator filter (remez) OptimalFir.RemezImpulse(H,new double[2] {0,1-TransBW},Ripple, TFilterType.ftDoubleDifferentiatorIII,1,FS,false); H.Scale(FS*FS); //Scale by sampling frequency // Type IV 2x differentiator filter (remez) OptimalFir.RemezImpulse(H,new double[2] {0,1-TransBW},Ripple, TFilterType.ftDoubleDifferentiatorIV,1,FS,false); H.Scale(FS*FS); //Scale by sampling frequency // Type III integrator filter (remez).'; OptimalFir.RemezImpulse(H,new double[2] {TransBW,1-TransBW},Ripple, TFilterType.ftIntegratorIII,1,FS,false); H.Scale(1/FS); //Scale by sampling frequency // Type IV integrator filter (remez).'; OptimalFir.RemezImpulse(H,new double[2] {TransBW,1},Ripple, TFilterType.ftIntegratorIV,1,FS,false); H.Scale(1/FS); //Scale by sampling frequency // Type III 2x integrator filter (remez).'; OptimalFir.RemezImpulse(H,new double[2] {TransBW,1-TransBW},Ripple, TFilterType.ftDoubleIntegratorIII,1,FS,false); H.Scale(Math.Sqrt(1/FS)); //Scale by sampling frequency // Type IV 2x integrator filter (remez).'; OptimalFir.RemezImpulse(H,new double[2] {TransBW,1},Ripple, TFilterType.ftDoubleIntegratorIV,1,FS,false); H.Scale(Math.Sqrt(1/FS)); //Scale by sampling frequency SignalUtils.FrequencyResponse(H,null,Response,16,false,TSignalWindowType.wtRectangular,0); MtxVecTee.DrawIt(Response,"",false); *) function RemezImpulse(const H: TVec; const W: array of Double; FilterType: TFilterType; Gain: Double = 1; FS: Double = 2): boolean; overload; (*Estimate the length of an optimal FIR filter. Returns the length of the equiripple FIR filter, design with Parks-McClellan algorithm where the maximum allowed ripple of the pass band is Ripple and sampling frequency is FS. The requested stopband attenuation is estimated as 20*Log10(Ripple). The W array holds two or four parameters: the start and the stop of the transition bands, relative to the specified sampling frequency. The maximum filter order is limited with MaxFirLength global variable. Note This routine is a simplified version of RemezLength routine. *) function RemezFirLength(const W: array of Double; Ripple: Double; FilterType: TFilterType; FS: Double = 2): integer; overload; (*Estimate the length of an optimal FIR filter. Estimate the length of an optimal FIR filter designed with Parks-McClellan algorithm. See the remez routine for description of parameters and examples. *) function RemezLength(const bands, gains, ripple: array of Double; Weights: TVec; FS: Double = 2): integer; (*Grid densitiy for the Parks-McClellan algorithm. Defines the density of the frequency grid on which to perform the optimization of the equiripple FIR filter with the remez routine. Increasing the density of the grid might improve filter design. *) var GridDensity: integer = 16; {$I BdsppDefs.inc} (* Signal analysis components. *) unit SignalAnalysis; interface {$WARN SYMBOL_DEPRECATED OFF} uses Math387, SignalTools, MtxVec, SignalUtils, MtxBaseComp, SignalProcessing, MtxParseExpr, MtxParseClass, MtxVecBase, MtxVecInt ,Classes ,SysUtils ,Contnrs ; type (*Defines the transformation to be applied to the time signal. Defines the transformation for the TSignalAnalyzer component. *) TTimeTransform = ( (*Applies a user defined time window.*)ttWindow, (*Computes autocorrelation.*) ttAutoCorr, (*Computes cross correlation.*) ttCrossCorr, (*Discrete cosine transform.*) ttDct, (*Inverse discrete cosine transform.*) ttInverseDct, (*Real cepstrum.*) ttRealCepstrum, (*Complex cepstrum.*) ttCplxCepstrum ); (* Defines the autocorrelation type. Autocorrelation type can be: Normal, Biased or unbiased. See TVec.AutoCorr routines... for more info. *) TAutoCorrType = ( (*Normal autocorrelation scaling.*) ctNormal, (*Biased autocorrelation scaling*) ctBiased, (*Unbiased autocorrelation scaling*) ctUnbiased ); (* Defines integration/differentiation options. Defines the integration/differentiation for TSignalAnalyzer component. *) TIntegration = ( (*No integration or differentiation.*) inNone, (*Integrates once.*) inOnce, (*Integrates twice*) inTwice, (*Differentiates once.*) diffOnce, (*Differentiates twice*) diffTwice ); (*Defines methods for frequency spectrum estimation. Defines the methods available to compute the frequency spectrum. *) TSpectrumMethod = ( (*Fast Fourier Transform.*) smFFT, (*Autogressive YuleWalker method.*) smYuleWalker, (*Burg autogressive method.*) smBurg, (*Covariance based autoregressive method.*) smCov, (*Modified covariance based autogressive method.*) smMCov, (*Chirp Z-transform.*) smCZT ); (*Defines how to compute the logarithm of the frequency spectrum. Defines how the logarithm is to be applied to frequency spectrum. *) TLogType = ( (*Applies the logarithm with base 10 and produces result in dB.*)ltAbsolute, (*Applies the logarithm with base 10, produces result in dB and positions the maximum value at zero.*) ltRelative, (*Applies the logarithm. Uses additional parameters for log base and scaling.*) ltAbsoluteParam, (*Applies the logarithm and positions the maximum value at zero. Uses additional parameters for log base and scaling.*) ltRelativeParam ); (*Defines the span of the frequency spectrum in dB. Defines the span of the frequency spectrum in dB. Considered only when TLogType is ltAbsolute or ltRelative. *) TLogSpan = ( (*30dB*) ls30, (*45dB*) ls45, (*60dB*) ls60, (*90dB*) ls90, (*120dB*) ls120, (*150dB*) ls150, (*200dB*) ls200, (*350dB*) ls350 ); (*Defines averaging type. Defines the type of averaging used by the spectrum analyzer. Finite average differs from infinite by stopping the averaging at a predefined average count. Averaging of complex spectrums is the same as averaging of the time signal and can be used only, if consecutive time signal frames are phase synchronized. *) TAveraging = ( (*No averaging.*) avNone, (*Linear infinite averaging.*) avLinearInf, (*Exponential infinite averaging.*) avExponentInf, (*Linear finite averaging.*) avLinear, (*Finite peak hold "averaging".*) avPeakHold, (*Infinite peak hold "averaging".*) avPeakHoldInf, (*Peak hold and exponential decay.*) avPeakAndDecay, (*Finite linear average of complex spectrum's.*) avCplxLinear, (*Infinite linear average of complex spectrum's.*) avCplxLinearInf, (*Infinite exponential average of complex spectrum's.*) avCplxExponentInf); (*Specifies how to mark peaks automatically. Defines the method used to mark peaks on the frequency spectrum *) TPeakTraceMethod = ( (*Existing marks will not be removed, but their amplitudes will be updated.*) ptNone, (*Existing marks will be shifted in frequency to the closest peaks.*) ptCurrent, (*Existing marks will be removed, and a prespecified number of largest peaks will be marked.*) ptLargest, (*Existing marks will be removed and a band limited search is performed to find the peak with the largest amplitude. The newly found peak is regarded as a fundamental frequency and a prespecified number of harmonics is marked.*) ptAmpltHarmonics, (*All marks except the first are removed. Treats the first mark as the fundamental frequency and then marks a prespecified number of harmonics.*) ptFreqHarmonics, (*Finds all peaks, which are not too close.*) ptAllPeaks, (*Finds all largest peaks, which are not too close. It then searches for the harmonic series with the strongest support.*) ptFirstHarmonic ); (*Defines a set of frequency spectrum peak interpolators. Defines the method used to interpolate the frequency of a marked peak. Frequency spectrum is computed only at discrete frequencies. If you want to know the actual frequency of the peak that falls between two frequency bins, you have to use peak interpolation. You can also simply increase the number of frequencies at which the frequency spectrum is calculated, by increasing the zero padding. Peak interpolation is more accurate and faster. *) TInterpolationMethod = ( (*No frequency interpolation will be performed.*) imNone, (*Numerical peak interpolation is the most accurate and can be used with any window and any zero padding setting, but it is also the slowest. Absolute accuracy of the numerical interpolator can be increased dramatically by using a Kaiser window with high attenuation factors.*) imNumeric, (*Requires rectangular window, no zero padding, but works with amplitude spectrum only and is fast. Accuracy is not very good.*) imQuadratic, (*Requires rectangular window, no zero padding, but works with amplitude spectrum only and is fast. Accuracy is not very good.*) imBarycentric, (*Works with rectangular window and no zero padding. Requires complex frequency spectrum and is very fast.*) imQuinnFirst, (*Works with rectangular window and no zero padding. Requires complex frequency spectrum and is very fast. The second Quinn estimator is very accurate and fast, if your frequency spectrum is not based on zero padded data and you can afford to use the rectangular window.*) imQuinnSecond, (*Requires rectangular window, no zero padding, but works with amplitude spectrum only and is very fast. Accuracy is not very good.*) imJain ); (*Precision specification for numerical peak interpolation. Defines the precision to be achieved by the numerical peak interpolator. *) TInterpolationPrecision = ( (*Interpolate to accuracy 1E+2 higher then frequency spectrum resolution.*) ip2, (*Interpolate to accuracy 1E+4 higher then frequency spectrum resolution.*) ip4, (*Interpolate to accuracy 1E+6 higher then frequency spectrum resolution.*) ip6, (*Interpolate to accuracy 1E+8 higher then frequency spectrum resolution.*) ip8 ); (*Band reference specification. Defines the band reference used by the routines that compute statistics from the bands of the frequency spectrum. *) TBandReference = ( (*The frequency band is defined with absolute frequencies.*) fnSamplingFrequency = 0, (*The frequency band is defined with frequencies normalized with the sampling frequency frequency.*) fnNyquist = 1, (*The frequency band is defined relative to a special frequency defined with value of the OrderFrequency property.*) fnOrder = 2, (*The frequency band is defined relative to the frequency of the N'th peak.*) fnNthPeak = 3 ); (*Event type passing a TVec object as a parameter. Event type passing a TVec object as a parameter. *) TNotifyVecEvent = procedure (Sender: TObject; Vec: TVec) of object; (*Event type passing a TMtx object as a parameter. Event type passing a TMtx object as a parameter. *) TNotifyMtxEvent = procedure (Sender: TObject; Mtx: TMtx) of object; TBandRecordDouble = packed record Checked: byte; StartTransHz: double; StartHz: double; EndHz: double; EndTransHz: double; RMSValue: double; BandReference: byte; SpcTypeAdjust: byte; PeakNumber: Int32; end; TBandRecordArray = array of TBandRecordDouble; TStaticSpectrumAnalyzer = class; (*Stores the definition of a frequency band. Frequency band is specified to compute RMS from a specified frequency range. It is also possible to specify transition regions to approximate digital filter response. *) TBandRecord = class public (*If Checked is True, the statistics based on this band will be updated. *) Checked: boolean; (* StartTransHz is the starting transition of the frequency band. *) StartTransHz: Double; (* Start of the pass band. Amplitude is zero and raises lineary until it reaches StartHz, where the amplitude is no longer attenuated. This transition band can be used, to simulate the effect of a transition region of a digital filter. *) StartHz: Double; (* End of the pass band. *) EndHz: Double; (* EndTransHz is the ending transition of the pass band. This transition band can be used, to simulate the effect of a transition region of a digital filter. *) EndTransHz: Double; (*RMSValue holds the RMS estimated on the frequency band. *) RMSValue: Double; (* Band reference defines the frequency reference for the band definition. *) BandReference: TBandReference; (* If true, the RMS value will be adjusted to compensate for the spectrum type. *) SpcTypeAdjust: boolean; (* PeakNumber is an additional parameter used by the NthPeak band reference method. *) PeakNumber: integer; (* Assign all field values from Src. *) procedure Assign(Src: TBandRecord); overload; (* Assign all field values from Src. *) procedure Assign(const Src: TBandRecordDouble); overload; (* Assign all field values to Dst. *) procedure AssignTo(var Dst: TBandRecordDouble); overload; (* Maps frequency information to array indexes . *) procedure MapToIndexes(var Idx1, Idx2, Idx3, Idx4: integer; const Spc: TStaticSpectrumAnalyzer); overload; end; TSpectrumBandsAnalyzer = class; (*A list to frequency band definitions. Manages a list to frequency band definitions. The list ownes the items. *) TSpectrumBands = class(TStreamedList) strict private function GetItems(Index: Integer): TBandRecord; procedure SetItems(Index: Integer; const Value: TBandRecord); procedure WriteArrayOfRecords(var Src: TBandRecordArray; Dst: TStream; Len: integer); overload; procedure ReadArrayOfRecords(Src: TStream; var Dst: TBandRecordArray; Len: integer); overload; strict protected FAOwner: TSpectrumBandsAnalyzer; public (*Recalculate the statistics for bands. *) procedure Update; override; (*Add a new band definition and allocate the record internally. *) function Add(const Item: TBandRecord): integer; (*Save the list to stream. *) procedure SaveToStream(Dst: TStream); override; (*Load the list from the stream. *) procedure LoadFromStream(Src: TStream); override; (*Create the list and pass TSpectrumBandsAnalyzer component as the owner. *) constructor Create(AOwner: TSpectrumBandsAnalyzer); (*Default array property allows access to individual list items. *) property Item[Index: Integer]: TBandRecord read GetItems write SetItems; default; end; TSpectrumAnalyzer = class; TCustomSpectrumAnalyzer = class; (*A list of list of lists of frequency band definitions. Manages a list of lists of frequency band definitions. *) TSpectrumBandsAnalyzer = class(TStreamTemplates) strict private function GetItems(Index: Integer): TBandRecord; procedure SetItems(Index: Integer; const Value: TBandRecord); procedure SetTemplate(const Value: TSpectrumBands); function GetTemplate: TSpectrumBands; strict protected function GetStreamedTemplate: TStreamedList; override; public (*Default array property allows access to individual list items.*) property Items[Index: Integer]: TBandRecord read GetItems write SetItems; default; (*Convert values to strings. The strings will be stored in TStrings object with two columns, alligned for display with a fixed charachter width Font. The Header of each column is defined with the XTitle and ATitle and PTitle properties. The header line will not be displayed, if you set XTitle, ATitle and PTitle to empty strings. The width of the columns is defined with global variable: SignalTextColumnWidth. If FrequencyFormat or RMSFormat are empty strings, full numeric precision will be used for the coresponding column. *) procedure ValuesToStrings(Strings: TStrings; const FrequencyFormat: string = ''; const RMSFormat: string = ''; const FreqTitle: string = 'Frequency range [Hz]'; const RMSTitle: string = 'RMS'; const Delimiter: string = ''); virtual; constructor Create(AOwner: TComponent); overload; override; published (*Holds a list of objects. All changes made to this list of objects are preserved, when you change TemplateName or TemplateIndex. *) property Template: TSpectrumBands read GetTemplate write SetTemplate; end; (* Refinement of the frequency of the first harmonic. The algorithm will find the fundamental frequency and then compute the frequency of the higher harmonic. Once the higher harmonic is found, it will interpolate the frequency of the higher harmonic and then from that value compute the (improved) frequency of the fundamental. This allows for automatic detection of very long harmonic series (hundreds and thousands) and a much improved accuracy of the estimation of the fundamental. *) TRecursiveHarmonics = ( (*Do not improve accuracy of the first harmonic after locating higher harmonics.*) rhNone, (*Improve accuracy of the first harmonic after locating odd higher harmonics.*) rhOdd, (*Improve accuracy of the first harmonic after locating even higher harmonics.*) rhEven, (*Improve accuracy of the first harmonic after locating any higher harmonics.*) rhAll); (*Encapsulates parameters used for peak interpolation. Holds the parameters required used by peak interpolation methods. *) TSpectrumPeakInterpolation = class(TPersistent) strict private FIPrecision: Double; FHarmonics: boolean; FMethod: TInterpolationMethod; FPrecision: TInterpolationPrecision; FRecursiveHarmonics: TRecursiveHarmonics; fMethodBackup: TInterpolationMethod; procedure SetHarmonics(const Value: boolean); procedure SetMethod(const Value: TInterpolationMethod); procedure SetPrecision(const Value: TInterpolationPrecision); procedure SetRecursiveHarmonics(const Value: TRecursiveHarmonics); public (*Disables interpolation. First stores the value of the Method property and then sets its value to imNone. *) procedure Disable; (*Restores interpolation. Resets the value of the Method property to what it was before the call to Disable method. *) procedure Restore; (*Holds the interpolation precision required. Read only. *) Property IPrecision: Double read FIPrecision; published (*Set this property to define the required interpolation precision for the numerical peak interpolator. Values higher than ip4 will be downgraded to ip4 when the frequency spectrum was computed with single precision. *) property Precision: TInterpolationPrecision read FPrecision write SetPrecision default ip6 ; (*Interpolate first harmonic only. Set this property to True, if only the fundamental frequency should be interpolated and the frequency of the harmonics should be computed from the fundamental (1x, 2x, 3x...). *) property Harmonics: boolean read FHarmonics write SetHarmonics default False ; (* Refinement of the frequency of the first harmonic. Higher harmonics are recursively used to improve the frequency estimation of the first harmonic. *) property RecursiveHarmonics: TRecursiveHarmonics read FRecursiveHarmonics write SetRecursiveHarmonics; (*Defines the peak interpolation method used. *) property Method: TInterpolationMethod read FMethod write SetMethod default imNumeric ; end; (*Normalized tracing options. Tracing means, that on each subsequent computed frequency spectrum, the frequency analyzer will recursivelly use the previous value of the specified marked peak (otNthPeak) to search for the new nearest peak. Alternativelly the marked peak can also be specified as the largest found marked peak (otLargest). In this case, the previous value of the for this peak is disregarded. Usually the largest peak is also the fundamental frequency. otFirstPeak can be used when examining the ratios between different peaks. *) TNormTracing = (otNone, (*Perform tracking relative to the first marked peak from the left.*) otFirstPeak, (*Perform tracking relative to the N'th marked peak.*) otNthPeak, (*Perform tracking relative to the largest marked peak.*) otLargest ); (* Groups parameters for normalized frequency tracing options. *) TNormalizedFreq = class(TPersistent) strict private Spectrum: TSpectrum; FAppliedFrequency: Double; FPeakNumber: integer; FNormTracing: TNormTracing; FNormFrequency: Double; FActive: boolean; FDefaultFS: Double; FDefaultHzRes: Double; procedure SetActive(const Value: boolean); procedure SetNormFrequency(const Value: Double); procedure SetNormTracing(const Value: TNormTracing); procedure SetPeakNumber(const Value: integer); function GetList: TMarkList; procedure SetDefaultFS(const Value: Double); procedure SetDefaultHzRes(const Value: Double); public constructor Create(Aowner: TSpectrum); virtual; (*Computes the normalized frequency. Computes the normalized frequency with the method specified by the Tracing property. Active property must be true for this routine to have any effect. If Trace = otNthPeak then the PeakNumber is used to determine the peak to be used as a reference to compute the normalized frequency. *) procedure ApplyNormalization; property AppliedFrequency: double read FAppliedFrequency; property List: TMarkList read GetList; (*Specifies the normalized frequency. Normalized frequency is a predefined frequency, that always has a value of 1 on the frequency axis. Normalized frequency can be specified absolutely in Hz or it can be specified relative to some marked peak. When normalized frequency is specified relative to a marked peak, this marked peak can be traced. *) property NormFrequency: Double read FNormFrequency write SetNormFrequency; (*When Active is True, this property contains the actual frequency spectrum resolution. *) property DefaultHzRes: Double read FDefaultHzRes write SetDefaultHzRes; (*When Active is True, this property contains the actual sampling frequency. *) property DefaultFS: Double read FDefaultFS write SetDefaultFS; published (*Defines the tracing method. Order tracing determines, how will the normFrequency be estimated. *) property Tracing: TNormTracing read FNormTracing write SetNormTracing default otNone ; (*Used when Tracing = otNthPeak. *) property PeakNumber: integer read FPeakNumber write SetPeakNumber default 1; (*Set this property to True, to enable normalized Frequency. When normFrequency will be applied to the frequency spectrum, the frequency equal to normFrequency will have a value of 1 on the frequency axis. You can then see the ratio of all other frequencies towards the normFrequency. This can be usefull when determining harmonics or half-harmonics. *) property Active: boolean read FActive write SetActive default False ; end; (* Groups parameters for normalized amplitude tracing options. *) TNormalizedAmplt = class(TPersistent) strict private Spectrum: TSpectrum; FAppliedAmplitude: Double; FPeakNumber: integer; FNormTracing: TNormTracing; FNormAmplitude: Double; FActive: boolean; procedure SetActive(const Value: boolean); procedure SetNormAmplitude(const Value: Double); procedure SetNormTracing(const Value: TNormTracing); procedure SetPeakNumber(const Value: integer); function GetList: TMarkList; public constructor Create(Aowner: TSpectrum); virtual; (*Computes the amplitude normalization factor. Computes the scaled factor with the method specified by the Trace property. Active property must be true for this routine to have any effect. If Tracing = otNthPeak then the PeakNumber is used to determine the peak to be used as a reference to compute the NormalizedAmplt factor. *) procedure ApplyNormalization; property List: TMarkList read GetList; property AppliedAmplitude: double read FAppliedAmplitude; (*Specifies the normalized amplitude. Normalized amplitude is a predefined amplitude, that always has a value of 1 on the frequency axis. Normalized amplitude can be specified absolutely in Hz or it can be specified relative to some marked peak. When normalized amplitude is specified relative to a marked peak, this marked peak can be traced (followed between spectrum updates). *) property NormAmplitude: Double read FNormAmplitude write SetNormAmplitude; published (*Defines the tracing method. Order tracing determines, how will the normAmplitude be estimated. *) property Tracing: TNormTracing read FNormTracing write SetNormTracing default otNone ; (*Used when Tracing = otNthPeak. *) property PeakNumber: integer read FPeakNumber write SetPeakNumber default 1; (*Set this property to True, to enable normalized amplitude. When normAmplitude will be applied to the frequency spectrum, the amplitude equal to normAmplitude will have a value of 1 on the amplitude axis. You can then see the ratio of all other amplitudes towards the normAmplitude. *) property Active: boolean read FActive write SetActive default False ; end; (*Manages frequency spectrum peaks. Used the mark, trace, interpolate and filter peaks. *) TSpectrumPeaksAnalyzer = class(TPersistent) strict private BandLIndex: integer; BandHIndex: integer; FLargestCount: integer; FTraceMethod: TPeakTraceMethod; FTraceInterval: Double; FInterpolation: TSpectrumPeakInterpolation; FBandwidthL: Double; FBandwidthH: Double; FUseFullBandwidth: boolean; FActive: boolean; FSpectrum: TSpectrum; FNormalizedFreq: TNormalizedFreq; FNormalizedAmplt: TNormalizedAmplt; FLargestRatio: double; Freq, cEst, aEst, pEst: TVec; procedure SetHarmonicsCount(const Value: integer); procedure SetInterpolation(const Value: TSpectrumPeakInterpolation); procedure SetLargestCount(const Value: integer); procedure SetTraceInterval(const Value: Double); procedure SetTraceMethod(const Value: TPeakTraceMethod); function GetList: TMarkList; function GetHarmonicsCount: integer; procedure SetBandwidthH(const Value: Double); procedure SetBandwidthL(const Value: Double); procedure SetUseFullBandwidth(const Value: boolean); procedure SetActive(const Value: boolean); function GetMarks(i: integer): TMarkRecord; procedure SetMarks(i: integer; const Value: TMarkRecord); procedure SetNormalizedAmplt(const Value: TNormalizedAmplt); procedure SetNormalizedFreq(const Value: TNormalizedFreq); procedure SetLargestRatio(const Value: double); strict protected function Tau(x: Double): Double; protected aIdx: TVecInt; public property Spectrum: TSpectrum read FSpectrum; (*Default array property for the marks (=peaks) array. *) property Marks[i: integer]: TMarkRecord read GetMarks write SetMarks; default; (*A pointer to a list of all marked peaks (= TSpectrum.Marks). Read only. *) property List: TMarkList read GetList; (*Reestimates the order frequency and traces the peaks. Applies the OrderFrequency, traces the peaks according to the value of the TraceMethod property and reestimates the OrderFrequency. *) procedure Update; (*Called by Update method, if TraceMethod is ptNone.*) procedure Adjust; (*Converts all peaks in dB. The associated spectrum must already be in logarithmic scale. *) procedure LogAmplt; (*Called by Update method, if TraceMethod is ptAll. It is recommended that the ZeroPadding is 2 or more. Higher values of zero padding will allow more accurate frequency and amplitude comparisons. Too high values of zero padding will exponentionally increase the computational requirements. *) procedure FindAllPeaks; (*Called by Update method, if TraceMethod is ptLargest. The function will find the largest peaks count specified with the LargestCount property. The search is analytical and guaranteed to return the largest peaks, when peak interpolation.method property is set to TSignalInterpolation.imNumeric. The result is also influenced by the LargestRatio property. The largest peaks are returned in the List property, which is sorted by descending amplitude. It is recommended that the ZeroPadding is 2 or 4 for best performance. ZeroPadding bigger than 1 speeds up the processing mostly when the signal being analyzed is close to pure noise. Using TSpectrumAnalyzer.Window set to wtKaiser with TSpectrumAnalyzer.SidelobeAtt value equal or more than 60 will help with performance. This function is called by * FindFirstHarmonic * FindFundamentals * FindAllPeaks *) procedure FindLargestPeaks; (*Called by Update method, if TraceMethod is ptFirstHarmonic. The routine will call FindFundamentals, but keep only the first most probable peak. *) procedure FindFirstHarmonic; (*Locates the first harmonics of a harmonic series. Uses FindLargestPeaks to start and then determines the amount of support for each frequency, that it is in fact also a first harmonic. This will work also for odd spaced harmonics. The result is a re-sorted TSpectrumPeaksAnalyzer.Marks property (frequency ascending) with lower frequencies at the begining of the list. The algorithm will not exclude candidates, which do not have higher harmonics. It is sometimes recommended to set LargestRatio parameter to 10 to improve accuracy. FindLargestPeak method uses LargestCount property to determine the number of largest peaks to select. The processing will use numerical peak interpolation and is guaranteed to find all peaks regardless of the selected spectral window method and zero-padding factor used to compute the frequency spectrum. *) procedure FindFundamentals; (*Finds the largest peak on the frequency interval. Frequency interval is limited in two ways. First, it is limited with values of the properties: BandwidthL and BandwidthH. This limitation applies only, if UseFullBandwidth is False. And second, it is limited with the value of the TraceInterval property. The Frequency parameter contains the previous known value of the marked peak on entry and the frequency of the newly found peak on exit from the routine. *) function FindFrequency(const Frequency: Double): Double; overload; (*Finds the largest peak on the frequency interval for each value in Frequency parameter. Frequency interval is limited in two ways. First, it is limited with values of the properties: BandwidthL and BandwidthH. This limitation applies only, if UseFullBandwidth is False. And second, it is limited with the value of the TraceInterval property. The Frequency parameter contains the previous known value of the marked peak on entry and the frequency of the newly found peak on exit from the routine. *) procedure FindFrequency(const Frequency: TVec); overload; (*Filters the peak specified with Index. Filters the peak defined with position Index within the List from the time signal associated with the frequency spectrum and recalculates the frequency spectrum. *) procedure Filter(Index: integer); (*Finds the maximum of the peak at Freq and applies interpolation. Applies the peak interpolation methods, specified with the interpolation property, to get a better estimate of the frequency of the peak then Freq. The value of the Freq can be the frequency of the closest frequency bin in the frequency spectrum. *) function Approximate(Freq: Double): Double; overload; (*Finds the maximum of the peak at Freq and applies interpolation. *) procedure Approximate(const Freqs: TVec; const freqIdx: TVecInt); overload; (*Finds the maximum of the peak at Freq and applies interpolation. Finds the nearest peak and applies interpolation for each srcFreq. It stores the amplitude and phase in to dstAmplt and dstPhase. *) procedure Approximate(const srcFreq, dstAmplt, dstPhase: TVec); overload; (*Finds the maximum of the peak at Freq and applies interpolation. Applies the peak interpolation methods, specified with the Interpolation property, to get a better estimate of the frequency of the peak closest to the Freq. The value of the Freq can be the frequency of the closest frequency bin in the frequency spectrum. The function also returns the complex value spectral bin at the specified frequency, if peak interpolation was TInterpolationMethod.imNumeric. Alternatively the result of cplxEstimate is simply zero. *) function ApproximateComplexEst(Freq: Double; out cplxEstimate: TCplx): Double; overload; (*Finds the maximum of the peak at Freq and applies interpolation. Applies peak interpolation methods, specified with the Interpolation property, to get a better estimate of the peaks closest to the frequencies in the Freqs. Freqs parameter will be updated with new values and cplxEst will hold the complex results. The value of the Freq can be the frequency of the closest frequency bin in the frequency spectrum depending on the interpolation method. cplxEst returns the complex value spectral bin at the specified frequency, if peak interpolation was TInterpolationMethod.imNumeric. Alternatively the result of cplxEst is simply zero. freqIdx contains the indexes in the spectrum for Freqs on output. When interpolation is active, the indexes will correspond to the closest spectral line and its content can be discarded. The vectorized variant of this method is roughly 10x faster than the single frequency overload, provided that Freq.Length is large enough. When Freq.Length is 10 then speed up is about 3x and when it is 100, the speed up is about 10x. *) procedure ApproximateComplexEst(const Freqs, cplxEst: TVec; const freqIdx: TVecInt); overload; (*Called by the Update method, if TraceMethod is ptAmpltHarmonics. *) procedure AmpltHarmonics; (*Called by the Update method, if TraceMethod is ptFreqHarmonics. *) procedure FreqHarmonics; (*Filters all peaks. Filters all the peaks within the List from the time signal associated with the frequency spectrum and recalculates the frequency spectrum. *) procedure FilterAll; (*Called by the Update method, if TraceMethod is ptCurrent. *) procedure Trace; (*Returns the index of the newly added mark at Freq. Phase and Amplitude are read from the frequency spectrum by using the Goertzal algorithm. *) function AddMark(Freq: Double): integer; procedure Assign(Source: TPersistent); override; constructor Create(AOwner: TSpectrum); virtual; destructor Destroy; override; published (*Set this property to false, to disable peak tracing, marking, interpolating and filtering when the Update method is called. *) property Active: boolean read FActive write SetActive default True ; (*Defines the peak interpolation parameters. *) property Interpolation: TSpectrumPeakInterpolation read FInterpolation write SetInterpolation; (*Defines the number of largest peaks to mark, when TraceMethod = ptLargest. *) property LargestCount: integer read FLargestCount write SetLargestCount default 1; (*Defines the method used to trace and mark the peaks. *) property TraceMethod: TPeakTraceMethod read FTraceMethod write SetTraceMethod default ptNone ; (*Defines the interval centered around the current position of peak in percentage of the bandwidth, within which the new position of the peak is to be searched for. *) property TraceInterval: Double read FTraceInterval write SetTraceInterval; (*Defines the number of harmonics to be marked, if TraceMethod is ptAmpltHarmonics or ptFreqHarmonics. *) property HarmonicsCount: integer read GetHarmonicsCount write SetHarmonicsCount default 10; (*Defines the lower frequency that limits the search for peak. *) property BandwidthL: Double read FBandwidthL write SetBandwidthL; (*Defines the upper frequency that limits the search for peak. *) property BandwidthH: Double read FBandwidthH write SetBandwidthH; (*Set this property to true, to assume that BandwidthL = 0 and BandwidthH equals the Nyquist frequency. *) property UseFullBandwidth: boolean read FUseFullBandwidth write SetUseFullBandwidth default True ; (* Parameters for normalized frequency. *) property NormalizedFreq: TNormalizedFreq read FNormalizedFreq write SetNormalizedFreq; (* Parameters for normalized amplitude. *) property NormalizedAmplt: TNormalizedAmplt read FNormalizedAmplt write SetNormalizedAmplt; (* Defines the ratio between the largest peak below which, the peak will not be considered. Used when TraceMethod = ptLargest and when calling FindLargestPeaks. Minimum value is 1 and the maximum and default value value is 1E+15. *) property LargestRatio: double read FLargestRatio write SetLargestRatio; end; (*Encapsulates parameters required by chirp-Z transform. Chirp Z-transform, can evaluate the frequency spectrum by starting and ending at any frequency and calculating frequency bins with any frequency step. Chirp Z-transform is more efficient then FFT, if zero padding is required. *) TSpectrumCZT = class(TPersistent) strict private Spectrum: TSpectrumAnalyzer; FStartFrequency: Double; FFrequencyStep: Double; FStopFrequency: Double; FStartRadius: Double; FSTopRadius: Double; procedure SetFrequencyStep(const Value: Double); procedure SetStartFrequency(const Value: Double); procedure SetStartRadius(const Value: Double); procedure SetStopFrequency(const Value: Double); procedure SetStopRadius(const Value: Double); public (*Create the object and pass TSpectrumAnalyzer as the owner. *) constructor Create(AOwner: TSpectrumAnalyzer); virtual; published (*Starting normalized frequency (FS = 1) for the Chirp Z-transform. *) property StartFrequency: Double read FStartFrequency write SetStartFrequency; (*Ending normalized frequency (FS = 1) for the Chirp Z-transform. *) property StopFrequency: Double read FStopFrequency write SetStopFrequency; (*Normalized frequency step (FS = 1) for the Chirp Z-transform. Amplitude and phase will be evaluted from CZTStart to CZTStop in frequency steps by CZTStep. *) property FrequencyStep: Double read FFrequencyStep write SetFrequencyStep; (*Defines the starting radius in the Z-plane. *) property StartRadius: Double read FStartRadius write SetStartRadius; (*Defines the ending radius in the Z-plane. *) property StopRadius: Double read FStopRadius write SetStopRadius; end; (*Defines a set of items to be included in spectrum analysis report. Defines what information is to be included in the spectral analysis report. *) TSpectrumReportSet = class(TPersistent) strict private FFrequencyLines: boolean; FGeneralInfo: boolean; FSFDR: boolean; FPhase: boolean; FTHD: boolean; FMarkedPeaks: boolean; FBandsRMS: boolean; FNF: boolean; FTHDN: boolean; FSNR: boolean; FSINAD: boolean; FDateTime: boolean; FRMS: boolean; procedure setBandsRMS(const Value: boolean); procedure setDateTime(const Value: boolean); procedure setFrequencyLines(const Value: boolean); procedure setGeneralInfo(const Value: boolean); procedure setMarkedPeaks(const Value: boolean); procedure setNF(const Value: boolean); procedure setPhase(const Value: boolean); procedure setRMS(const Value: boolean); procedure setSFDR(const Value: boolean); procedure setSINAD(const Value: boolean); procedure setSNR(const Value: boolean); procedure setTHD(const Value: boolean); procedure setTHDN(const Value: boolean); public (*Returns True, if any of the THD, THDN, NF, SFDR, SINAD, RMS. *) function IncludesStats: boolean; (*Sets all values to false. *) procedure Reset; published (*Include general info in the report. If true, parameters like SamplingFrequency, Bandwidth, number of sampels analyzed etc... will be included in the report. *) property GeneralInfo: boolean read FGeneralInfo write setGeneralInfo; (*If true, MarkedPeaks will be included in the report. *) property MarkedPeaks: boolean read FMarkedPeaks write setMarkedPeaks; (*If true, frequency lines with amplitude will be included in the report. *) property FrequencyLines: boolean read FFrequencyLines write setFrequencyLines; (*If true, phase will be included in the report. *) property Phase: boolean read FPhase write setPhase; (*If true, Total Harmonic Distortion will be included in the report. *) property THD: boolean read FTHD write setTHD; (*If true, Total Harmonic Distortion with noise will be included in the report. *) property THDN: boolean read FTHDN write setTHDN; (*If true, Noise Floor will be included in the report. *) property NF: boolean read FNF write setNF; (*If true, Spurious Free Dynamic Range will be included in the report. *) property SFDR: boolean read FSFDR write setSFDR; (*If true, the RMS of defined frequency ranges (bands) will be included in the report. *) property BandsRMS: boolean read FBandsRMS write setBandsRMS; (*If true, Signal to Noise and Distortion will be inlcuded in the report. *) property SINAD: boolean read FSINAD write setSINAD; (*If true, RMS of the signal will be inlcuded in the report. *) property RMS: boolean read FRMS write setRMS; (*If true, Signal to Noise Ratio will be inlcuded in the report. *) property SNR: boolean read FSNR write setSNR; (*If true, Date and time will be inlcuded in the report. *) property DateTime: boolean read FDateTime write setDateTime; end; (*Defines parameters for generating frequency analysis report. Defines parameters for generating frequency analysis report. *) TSpectrumReport = class(TPersistent) strict private Spectrum: TStaticSpectrumAnalyzer; FFrequencyFormat: string; FAmplitudeFormat: string; FPhaseFormat: string; FReportItems: TSpectrumReportSet; FDelimiter: string; FUseTab: boolean; procedure SetReportItems(const Value: TSpectrumReportSet); procedure SetAmplitudeFormat(const Value: string); procedure SetFrequencyFormat(const Value: string); procedure SetPhaseFormat(const Value: string); procedure SetDelimiter(const Value: string); procedure SetUseTab(const Value: boolean); public (*Create spectrum analysis report and store in strings. *) procedure CreateReport(Strings: TStrings); virtual; constructor Create(AOwner: TStaticSpectrumAnalyzer); virtual; destructor Destroy; override; published (*Defines a set of items to be included in spectrum analysis report. *) property ReportItems: TSpectrumReportSet read FReportItems write SetReportItems; (*Defines the floating point number formating applied to frequency values in the frequency analysis report. *) property FrequencyFormat: string read FFrequencyFormat write SetFrequencyFormat; (*Defines the floating point number formating applied to amplitude values in the frequency analysis report. *) property AmplitudeFormat: string read FAmplitudeFormat write SetAmplitudeFormat; (*Defines the floating point number formating applied to phase values in the frequency analysis report. *) property PhaseFormat: string read FPhaseFormat write SetPhaseFormat; (*A good example for the delimiter would be Tab character. If the delimiter is not used, the columns are padded with space char and aligned to the left. *) property Delimiter: string read FDelimiter write SetDelimiter; (*If true, tab is used to separate description of parameter from its value. The tab will also be used, between the units and the values. *) property UseTab: boolean read FUseTab write SetUseTab default false; end; (*Encapsulates properties for averaging and confidence interval calculation of the spectrum. Encapsulates properties for averaging and confidence interval calculation of the spectrum. *) TSpectrumStats = class(TPersistent) strict private Work: TVecList; Spectrum: TSpectrumAnalyzer; FCalcStdDev: boolean; FCalcLimits: boolean; FAverages: integer; FAveraged: integer; FConfidenceInterval: Double; FExpDecay: integer; FAveraging: TAveraging; FStdDev: TVecList; FLowerLimit: TVecList; FUpperLimit: TVecList; FAveragedSpectrum: TSpectrum; XLimitValue: Double; FRecursive: boolean; FCalcMinMax: boolean; procedure SetAveraged(const Value: integer); procedure SetAverages(const Value: integer); procedure SetAveraging(const Value: TAveraging); procedure SetCalcLimits(const Value: boolean); procedure SetCalcStdDev(const Value: boolean); procedure SetConfidenceInterval(const Value: Double); procedure SetExpDecay(const Value: integer); procedure SetLowerLimit(const Value: TVecList); procedure SetStdDev(const Value: TVecList); procedure SetUpperLimit(const Value: TVecList); procedure SetAveragedSpectrum(const Value: TSpectrum); procedure UpdateNonRecursive; procedure UpdateRecursive; procedure SetRecursive(const Value: boolean); procedure SetCalcMinMax(const Value: boolean); public property AveragedSpectrum: TSpectrum read FAveragedSpectrum write SetAveragedSpectrum; constructor Create(AOwner: TSpectrumAnalyzer); virtual; destructor Destroy; override; (*Recalculates limits considering the current value of the ConfidenceInterval. Works only, if Recursive property is false and StdDev is available. Works for amplitude only. *) procedure UpdateStatsAmpltLimits; (*Apply averaging. Called by TSpectrumAnalyzer. *) procedure Update; virtual; (*Computes average and standard deviations of the spectrums optionally with upper and lower confidence levels, if Recursive property was false while spectrums were added. *) procedure UpdateStats; virtual; (*Recalculates limits considering the current value of the ConfidenceInterval. Works only, if Recursive property is false and StdDev is available. Works for both phase and amplitude. *) procedure UpdateStatsLimits; (*Reset the averaging process and deallocate any associated memory. *) procedure Reset; (*Returns true, if confidence intervals have been computed and are available. *) function ConfidenceLimitsPresent: boolean; published (*Holds a list of vectors with upper confidence interval limits. The first index holds the amplitude and the second the phase related data. For description of vectors see TVec.Caption property. *) property UpperLimit: TVecList read FUpperLimit write SetUpperLimit stored false; (*Holds a list of vectors with lower confidence interval limits. For description of vectors see TVec.Caption property. (example: LowerLimit[i].Caption) *) property LowerLimit: TVecList read FLowerLimit write SetLowerLimit stored false; (*The parameter for the exponential averaging. *) property ExpDecay: integer read FExpDecay write SetExpDecay default 5; (*If CalcStdDev is True and frequency spectrum has been averaged, this property contains the standard deviation of Amplt and Phase vectors. Standard deviation is statistically meaningfull only if linear averaging is used. For description of vectors see TVec.Caption property. The vector count should not be altered. The first index holds the stddev of amplitude and the second index the stddev of the phase. *) property StdDev: TVecList read FStdDev write SetStdDev stored false; (*If True, the averaging process will also compute the standard deviation for each spectral line. *) property CalcStdDev: boolean read FCalcStdDev write SetCalcStdDev default false ; (*If True, the averaging process will also compute the standard deviation. *) property CalcMinMax: boolean read FCalcMinMax write SetCalcMinMax; (*If True, the upper and lower limits of the confidence interval will be calculated and placed in UpperLimit and LowerLimit. *) property CalcLimits: boolean read FCalcLimits write SetCalcLimits; (*Defines the confidence interval in percent to determine the upper and lower confidence limit. If ConfidenceInterval is 99 then the certainty that all values for that specific frequency will fall between upper and lower limit is 99%. *) property ConfidenceInterval: Double read FConfidenceInterval write SetConfidenceInterval; (* Contains the number of averages made so far. *) property Averaged: integer read FAveraged write SetAveraged default 0 ; (*Defines the number of averages to make. When Averaged >= Averages and finite averaging mode is defined with Averaging property, the call to the Update will do nothing. *) property Averages: integer read FAverages write SetAverages default 30 ; (*Defines the type of averaging to perform. *) property Averaging: TAveraging read FAveraging write SetAveraging default avNone ; (*If True the result is computed each time a new spectrum is added. If the value is false, UpdateStats has to be called to obtain statistics, once desired number of spectrums have been accumulated. *) property Recursive: boolean read FRecursive write SetRecursive default True ; end; (*Defined to support storing and loading the spectrum descriptor to/from Streams. Defined to support storing and loading the spectrum descriptor to/from Streams. *) TSpectrumDescriptorRecord = packed record SpectrumMethod: integer; Integration: integer; Logarithmic: integer; Averages: integer; AveragingType: integer; SamplingTime: double; SamplingFrequency: double; ArOrder: integer; Window: integer; ScaleFactor: double; SpectrumType: integer; SidelobeAtt: double; ActualZeroPadding: double; end; (*Stores the description of the spectrum. When a frequency spectrum is saved for example to a database it makes sense to also store the data describing how that spectrum was computed. The object is used by spectrum analyzers to store the description of processing applied. *) TSpectrumDescriptor = class(TPersistent) strict protected FSpectrumMethod: TSpectrumMethod; FIntegration: TIntegration; FLogarithmic: boolean; FAverages: integer; FAveragingType: TAveraging; FSamplingTime: Double; FSamplingFrequency: Double; FArOrder: integer; FWindow: TSignalWindowType; FScaleFactor: Double; FSpectrumType: TSpectrumType; FSidelobeAtt: Double; FActualZeroPadding: Double; procedure SetArOrder(const Value: integer); procedure SetAverages(const Value: integer); procedure SetAveragingType(const Value: TAveraging); procedure SetIntegration(const Value: TIntegration); procedure SetLogarithmic(const Value: boolean); procedure SetSamplingFrequency(const Value: Double); procedure SetSamplingTime(const Value: Double); procedure SetScaleFactor(const Value: Double); procedure SetSidelobeAtt(const Value: Double); procedure SetSpectrumMethod(const Value: TSpectrumMethod); procedure SetSpectrumType(const Value: TSpectrumType); procedure SetWindow(const Value: TSignalWindowType); procedure SetActualZeroPadding(const Value: Double); public (*Sampling frequency of the time signal. *) property SamplingFrequency: Double read FSamplingFrequency write SetSamplingFrequency; (*Sampling time of the time signal. *) property SamplingTime: Double read FSamplingTime write SetSamplingTime; (*Number of averages taken if any. *) property Averages: integer read FAverages write SetAverages; (*Averaging method used, if averaging was performed. *) property AveragingType: TAveraging read FAveragingType write SetAveragingType; (*Type of the spectrum computed. *) property SpectrumType: TSpectrumType read FSpectrumType write SetSpectrumType; (*Method used to compute the spectrum. *) property SpectrumMethod: TSpectrumMethod read FSpectrumMethod write SetSpectrumMethod; (*Any window methods used to window the time signal. *) property Window: TSignalWindowType read FWindow write SetWindow; (*Scale factor by which the spectrum has been scaled. *) property ScaleFactor: Double read FScaleFactor write SetScaleFactor; (*Integration/differentiation methods applied. *) property Integration: TIntegration read FIntegration write SetIntegration; (*True if the spectrum in logarithmic scale. *) property Logarithmic: boolean read FLogarithmic write SetLogarithmic; (*AR order used if autoregressive spectral methods were applied. *) property ArOrder: integer read FArOrder write SetArOrder; (*Kaiser sidelobe attenuation if used. *) property SidelobeAtt: Double read FSidelobeAtt write SetSidelobeAtt; (*Holds the number of zeroes added to the signal prior to applying the frequency transformation. *) property ActualZeroPadding: Double read FActualZeroPadding write SetActualZeroPadding; (*Save the data to the Dst Stream. *) procedure SaveToStream(Dst: TStream); overload; virtual; (*Load the data from the Src Stream. *) procedure LoadFromStream(Src: TStream); overload; virtual; procedure Assign(Source: TPersistent); override; procedure AssignToRecord(var Dst: TSpectrumDescriptorRecord); procedure AssignFromRecord(const Src: TSpectrumDescriptorRecord); end; (*Analyzes a frequency spectrum. Use this component to analyze precomputed frequency spectrum. Spectrum that was stored to a database or an averaged frequency spectrum. The component features abilities to mark peaks, interpolate peaks, trace peaks, compute RMS of bands and create reports describing the spectrum. The amplitude spectrum should be placed in the Amplt property and the phase spectrum in the Phase property. The component supports analyzing the frequency spectrum in both polar and cartesian coordinates. *) TStaticSpectrumAnalyzer = class(TSpectrum) strict protected FPeaks: TSpectrumPeaksAnalyzer; FReport: TSpectrumReport; FBands: TSpectrumBandsAnalyzer; FDescriptor: TSpectrumDescriptor; FOnPeaksUpdate: TNotifyEvent; FOnBandsUpdate: TNotifyEvent; procedure SetBands(const Value: TSpectrumBandsAnalyzer); procedure SetPeaks(const Value: TSpectrumPeaksAnalyzer); procedure SetReport(const Value: TSpectrumReport); procedure SetOnBandsUpdate(const Value: TNotifyEvent); procedure SetOnPeaksUpdate(const Value: TNotifyEvent); strict protected function InternalUpdate: TPipeState; override; function CreateSpectrumReport: TSpectrumReport; virtual; public property Descriptor: TSpectrumDescriptor read FDescriptor; constructor Create(AOwner:TComponent); override; destructor Destroy; override; published (*Holds the parameters needed to generate a spectrum analysis report. Requires that the descriptor property is properly filled up. *) property Report: TSpectrumReport read FReport write SetReport; (*Pointer to the component that manages peak analysis. *) property Peaks: TSpectrumPeaksAnalyzer read FPeaks write SetPeaks; (*Holds the parameters for the frequency bands. *) property Bands: TSpectrumBandsAnalyzer read FBands write SetBands; (*If assigned, this event will be called instead of a call to Peaks.Update method. *) property OnPeaksUpdate: TNotifyEvent read FOnPeaksUpdate write SetOnPeaksUpdate; (*If assigned, this event will be called instead of a call to Bands.Update method. *) property OnBandsUpdate: TNotifyEvent read FOnBandsUpdate write SetOnBandsUpdate; end; (*Manages a list of TStaticSpectrumAnalyzer objects. The component overrides the AddItem method and declares a new default array property returning TStaticSpectrumAnalyzer type. *) TStaticSpectrumAnalyzerList = class(TAnalysisList) strict private function GetItems(index: integer): TStaticSpectrumAnalyzer; reintroduce; procedure SetItems(index: integer; const Value: TStaticSpectrumAnalyzer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSignal objects by Index. *) property Items[index: integer]: TStaticSpectrumAnalyzer read GetItems write SetItems; default; end; (*Generic spectrum analyzer class. The class is used as the basis for analyzing frequency spectrums. *) TCustomSpectrumAnalyzer = class(TStaticSpectrumAnalyzer) strict protected FLdBSpan: double; FIntegration: TIntegration; FLogarithmic: boolean; FRotation: integer; FLogType: TLogType; FScaleFactor: Double; FLogSpan: TLogSpan; FSpectrumType: TSpectrumType; FLogBase: double; FLogScale: double; procedure SetLogarithmic(const Value: boolean); procedure SetLogSpan(const Value: TLogSpan); procedure SetLogType(const Value: TLogType); procedure SetRotation(const Value: integer); procedure SetScaleFactor(const Value: Double); procedure SetLogBase(const Value: double); procedure SetLogScale(const Value: double); strict protected procedure SetSpectrumType(const Value: TSpectrumType); virtual; strict protected procedure SetIntegration(const Value: TIntegration); virtual; public property LdBSpan: double read FLDBSpan; constructor Create(AOwner:TComponent); override; procedure IntegrateSpectrum; published (*Define the integration/differenation of the frequency spectrum. *) property Integration: TIntegration read FIntegration write SetIntegration default inNone ; (*Specify how to apply the logarithm to the spectrum. *) property LogType: TLogType read FLogType write SetLogType default ltAbsolute ; (*Set it to True, to obtain the logarithm of the spectrum in dB. *) property Logarithmic: boolean read FLogarithmic write SetLogarithmic default False ; (*Defines the span of the logarithmic spectrum in dB. *) property LogSpan: TLogSpan read FLogSpan write SetLogSpan default ls120 ; (*Defines the base of the logarithmic spectrum. Considered, when LogType is not dB. If this value is 10 and LogScale is 20, the result will be in dB. Default value is 10. *) property LogBase: double read FLogBase write SetLogBase; (*Defines the scaling of the logarithmic spectrum. Considered, when LogType is not dB. If this value is 10 and LogScale is 20, the result will be in dB. Default value is 20. *) property LogScale: double read FLogScale write SetLogScale; (*Frequency spectrum will be scaled by a scale factor. *) property ScaleFactor: Double read FScaleFactor write SetScaleFactor; (*Before the frequency analysis, the signal can be circularly rotated by a specified number of samples. Rotation defines the number of samples. The direction can be positive or negative. *) property Rotation: integer read FRotation write SetRotation default 0; (*Specifies the spectrum type to be computed. *) property SpectrumType: TSpectrumType read FSpectrumType write SetSpectrumType default spAmplt ; end; TStoredSpectrumAnalyzer = class(TCustomSpectrumAnalyzer) strict protected StoredAmplt: TVec; StoredPhase: TVec; FStoredSpectrumType: TSpectrumType; FStoredIntegration: TIntegration; FStoredLogarithmic: boolean; procedure SetStoredIntegration(const Value: TIntegration); procedure SetStoredLogarithmic(const Value: boolean); procedure SetStoredSpectrumType(const Value: TSpectrumType); function GetInput2: TSpectrum; procedure SetInput2(const Value: TSpectrum); strict protected function InternalUpdate: TPipeState; override; function CreateInputs: TAnalysisConnectorsCollection; override; public constructor Create(AOwner:TComponent); override; destructor Destroy; override; published (*Specifies the SpectrumType of the stored Spectrum. This parameter is used to convert the spectrum to other spectrum types. *) property StoredSpectrumType: TSpectrumType read FStoredSpectrumType write SetStoredSpectrumType; (*Specifies the Logarithmic flag of the stored Spectrum. This parameter is used to compute linear and/or logarithmic spectrum. *) property StoredLogarithmic: boolean read FStoredLogarithmic write SetStoredLogarithmic; (*Specifies the Integration method of the stored Spectrum. *) property StoredIntegration: TIntegration read FStoredIntegration write SetStoredIntegration; (*Specifes the Input spectrum for the analysis component. *) property Input: TSpectrum read GetInput2 write SetInput2 stored InputsStored; end; (*Computes and analyzes a frequency spectrum. Use this component to estimate frequency spectrum with a wide range of different methods, average frequency spectrums, mark and handle peaks, compute statistics from selected frequency bands and more... The resulting spectrum is always placed in Self.Amplt (amplitude spectrum) and Self.Phase (phase spectrum). *) TSpectrumAnalyzer = class(TCustomSpectrumAnalyzer) strict protected FWindowInitialized: boolean; FSpectrumScale: Double; PaddedLength: integer; FWindowApplyScale: Double; FDCDump: boolean; FWindow: TSignalWindowType; FSidelobeAtt: Double; WindowNorm: Double; FExpWindow: Double; FMethod: TSpectrumMethod; FARORder: integer; FOnAfterComplexSpectrum: TNotifyEvent; FOnAfterAverage: TNotifyEvent; FOnBeforeAverage: TNotifyEvent; FCrossSpectral: boolean; FOnBeforeSpectrumType: TNotifyEvent; FCZT: TSpectrumCZT; FUnwrapPhase: boolean; FRunningPhase: TRunningPhase; FActualZeroPadding: Double; FStats: TSpectrumStats; FLogMaxAmplt: Double; procedure SetDCDump(const Value: boolean); procedure SetWindowType(const Value: TSignalWindowType); procedure SetSidelobeAtt(const Value: Double); procedure ApplyWindow(Index, Len: integer); function WindowScaleFactor: Double; procedure SetExpWindow(const Value: Double); procedure SetMethod(const Value: TSpectrumMethod); procedure SetARORder(const Value: integer); procedure CheckPeaks; procedure SetOnAfterAverage(const Value: TNotifyEvent); procedure SetOnAfterComplexSpectrum(const Value: TNotifyEvent); procedure SetOnBeforeAverage(const Value: TNotifyEvent); procedure SetCrossSpectral(const Value: boolean); procedure SetOnBeforeSpectrumType(const Value: TNotifyEvent); procedure SeTSpectrumCZT(const Value: TSpectrumCZT); procedure SetUnwrapPhase(const Value: boolean); procedure SetRunningPhase(const Value: TRunningPhase); procedure SetActualZeroPadding(const Value: Double); procedure SetStats(const Value: TSpectrumStats); strict protected function GetOutput: TSignal; procedure SetOutput(const Value: TSignal); strict protected class function EditorClass: string; override; strict protected FInishedAveraging: boolean; FSupportComplexSpectra: boolean; procedure SetSpectrumType(const Value: TSpectrumType); override; protected property Output: TSignal read GetOutput write SetOutput; strict protected InputTemp, CepstralInput: TSignal; OutputTemp: TSignal; OrderApplied: boolean; SumSqrSpectrum: TSpectrum; TempSpectrum: TSpectrum; TempData, TempData2, TempData3, WindowData: TVec; cztState: TCztState; function GetTempData: TVec; function InternalUpdate: TPipeState; override; procedure BeforeInternalUpdate; override; procedure ApplySpectrumMethod(TargetLength: integer; Dst: TVec = nil); virtual; procedure ApplyPhaseRange; virtual; procedure SetZeroPadding(const Value: integer); override; procedure InitPeakAnalysis; procedure EnableStatistics; function StatisticsIsValid: boolean; procedure ComputeGroupDelay; override; procedure ComputePhaseDelay; override; procedure ApplyPhaseSpectrum; virtual; function FetchResult(Index: integer; Data: TVec): boolean; virtual; procedure SetIntegration(const Value: TIntegration); override; procedure UpdateLogMaxAmplt; virtual; public property Inputs: TSignalCollection read GetInputs1 write SetInputs1 stored InputsStored; property SupportComplexSpectra: boolean read FSupportComplexSpectra; property LogMaxAmplt: double read FLogMaxAmplt; (*Ready only. Returns the ratio between the original signal length and the length of the analyzed signal padded with zeros. *) property ActualZeroPadding: Double read FActualZeroPadding write SetActualZeroPadding; (*The scaling factor applied to the frequency spectrum. This scaling factor includes ScaleFactor, the scaling due to zero padding, scaling due to window used and scaling due to frequency spectrum length. *) property SpectrumScale: Double read FSpectrumScale write FSpectrumScale; (*After the first call to Update method, this property returns a pointer to the scaled complex spectrum. *) property ComplexSpectrum: TVec read GetTempData; (*Returns the amplitude of a peak nearest to the Freq frequency according to the value of the Peaks property. It takes in to account the scaling factors, spectrum type, Integration property and Logarithmic flag. *) function AmpltEst(Freq: Double): Double; overload; override; (*Returns the amplitude of a peak nearest to the Freq frequency according to the value of the Peaks property. It takes in to account the scaling factors, spectrum type, Integration property and Logarithmic flag. *) function AmpltEst(Freq: Double; const SpcFreqBin: TCplx): Double; overload; override; (*Returns the phases of the amplitudes at Freq frequencies according to the value of the Peaks property. SrcSpectrum must contain complex spectrum estimated at frequencies defined in Freq. *) procedure AmpltEst(SrcFreq, SrcSpectrum, DstAmplt: TVec); overload; override; (*Returns the phase of the frequency spectrum at Freq frequency according to the parameters of the Peaks property. *) function PhaseEst(Freq: Double): Double; overload; override; (*Returns the phase of the frequency spectrum at Freq frequency according to the parameters of the Peaks property. *) function PhaseEst(Freq: Double; const SpcFreqBin: TCplx): Double; overload; override; (*Converts the amplitude of the frequency spectrum at Freq frequency to dB according to the value of the Peaks property. *) procedure AmpltLogEst(var Amplitude: Double; Freq: Double); overload; (*Converts the amplitude of the frequency spectrum at Freq frequency to dB according to the value of the Peaks property. Vectorized version. *) procedure AmpltLogEst(const Amplitude: TVec; const Freq: TVec); overload; (*Returns the complex value of the Freq frequency according to the value of the Peaks property. This function will work only, if Method = smFFT. *) function ComplexEst(Freq: Double): TCplx; overload; virtual; (*Returns the complex value of the Freq frequency according to the value of the Peaks property. This function will work only, if Method = smFFT. When peak interpolation method is other than imNumeric, then the spcEstimate1 and spcEstimate2 will contain only the value of the nearest spectral peak. This function takes equal amount of time as the variant taking only one Freq parameter and is thus two times faster. *) procedure ComplexEst(Freq1, Freq2: Double; var spcEstimate1, spcEstimate2: TCplx); overload; virtual; (*Returns the complex value of the Freq frequencies according to the value of the Peaks property. This function will work only, if Method = smFFT. *) procedure ComplexEst(Freq, aResult: TVec); overload; virtual; (*Compute the spectrum from Input.Data according to the state of the component and place the result in Dst. *) procedure Spectrum(Dst: TVec); (*Find the peak nearest to the Freq frequency and return the frequenyc of the newly found peak. *) function PeakApproximate(Freq: Double): Double; overload; override; (*Find the peak nearest to the Freq frequency and return the frequenyc of the newly found peak. This overload returns also spcEstimate, which is the spectral line of the complex spectrum at approximated Freq. Only the numeric peak interpolation will return something else that the nearest peak for spcEstimate. This spcEstimate can then be passed on to the AmpltEst, which accepts also the complex parameter. *) function PeakApproximate(Freq: Double; var spcEstimate: TCplx): Double; overload; (*Filter the peak defined with a TMarkRecord record stored at position Index within Marks list from Input.Data. The method will use the frequency, amplitude and phase stored in the TMarkRecord to subtract a sine wave of the same frequency, amplitude and phase from the signal connected to the Input property. *) procedure FilterPeak(Index: integer); override; (*Compute the frequency spectrum where the Num is the numerator and Den is the denominator of the transfer function. FS defines the sampling frequency. The result is placed in Self. *) procedure ComputeSpectrum(Num: TVec; Den: TVec = nil; FS: Double = 2); virtual; (*Compute the frequency spectrum where the SrcNum is the numerator and SrcDen is the denominator of the transfer function. The resulting amplitude is placed in AmpltDst and resulting phase in PhaseDst. You can pass nil for SrcDen, if not present. If you want the result to be placed in self pass nil for AmpltDst and PhaseDst also. *) procedure Process(SrcNum: TVec; SrcDen: TVec = nil; AmpltDst: TVec = nil; PhaseDst: TVec = nil; FS: Double = 2); virtual; (*Reset the frequency spectrum averaging. *) procedure ResetAveraging; virtual; (*If Stats.Recursive was False, calling this method will compute the averaged spectrum and associated statistics. The predicted workflow is like this. First you set Stats.Recursive to True and specify for example Stats.Averaging := avLinear. Then you compute multiple frequency spectrums by calling Update or Pull method. Once sufficient number of averages has been obtained (Stats.Averaged), call UpdateSpectrumStats method to compute averaged frequency spectrum, optional standard deviation and confidence intervals. If Stats.Recursive was True, the averaged spectrum, standard deviation and confidence intervals were evaluated for each newly computed frequency spectrum. Consequently if the data is processed off-line, it makes more sense to set Stats.Recursive to false, and if live data is being observed like signal comming from the microphone, it makes more sense, to leave the Stats.Recursive property set to True. The speed of processing can be as much as 10x lower, if Recursive is True, standard deviation is required and confidence intervals are computed, because all the stats is evaluated for every iteration. *) procedure UpdateSpectrumStats; (*Call the method after changing the Logarithmic property. It will apply logarithmic amplitude axis on averaged or not averaged signal. It allows switching Logarithmic axis (on/off) without averaging the signal again. If the signal is not averaged, the method defaults to a call to the Update method. *) procedure UpdateLogarithmic; virtual; function IsNumericalInterpolationSupported: boolean; override; procedure SaveToStream(Stream: TStream); override; procedure LoadFromStream(Stream: TStream); override; constructor Create(AOwner:TComponent); override; destructor Destroy; override; published property Input: TSignal read GetInput1 write SetInput1 stored InputsStored ; (*If True, the conjugate symmetric part of the frequency spectrum will be placed in 0.5..1.0 Fs (as returned by the FFT), and not between -0.5..0.0 Fs. The conjugate symmetic part of the frequency spectrum from the real time series can be placed before or after its original.) The property is used in derived classes. *) property ConjFlip: boolean read FConjFlip write SetConjFlip default True ; (*If True, the conjugate symmetric part of the frequency spectrum is to be computed. The property is used in derived classes. The conjugate symmetic part of teh frequency spectrum can be used to detect aliasing, if you search for harmonics. *) property ConjExtend: boolean read FConjExtend write SetConjExtend default False ; (*If True, calculate the cross spectrum and not the transfer function. The transfer function is computed on each call to update. To obtain reliable estimate enable averaging. The phase spectrum will also be valid. *) property CrossSpectral: boolean read FCrossSpectral write SetCrossSpectral default False ; (*Defines the order for the autogressive spectral estimators. *) property ArOrder: integer read FARORder write SetAROrder default 100 ; (*Defines the parameters for the averaging and statistical analysis. *) property Stats: TSpectrumStats read FStats write SetStats; (*Holds Chirp Z-transform configuration. *) property CZT: TSpectrumCZT read FCZT write SetSpectrumCZT; (*If True, the DC component will be removed from the signal prior to the transform to the frequency domain. This can vastly improve the resolution of the frequency spectrum. *) property DCDump: boolean read FDCdump write SetDCDump default True ; (*The parameter for the exponential windowing function. *) property ExpWindow: Double read FExpWindow write SetExpWindow; (*Defines the method to be used for frequency spectrum estimation. *) property Method: TSpectrumMethod read FMethod write SetMethod default smFFT ; (*If True, the phase of the phase spectrum will be "unwrapped". The quality of the unwrapp will strongly depend upon the factor of zero padding. Zero padding factor of 32 is recomended. *) property UnwrapPhase: boolean read FUnwrapPhase write SetUnwrapPhase default false ; (*Defines how to remove the constant (running) phase from the phase spectrum. *) property RunningPhase: TRunningPhase read FRunningPhase write SetRunningPhase default rpConstPhase ; (*Specifies the attenuation of the main sidelobe in dB. This parameter applies only, if you use the Kaiser or Chebyshev window. *) property SidelobeAtt: Double read FSidelobeAtt write SetSidelobeAtt; (*Specifies the window type to be used. Windows decrease spectral leakage, but also decrease spectral resolution. *) property Window: TSignalWindowType read FWindow write SetWindowType default wtRectangular ; (*Event triggered just after complex spectrum has been computed, but before averaging in the complex domain. *) property OnAfterComplexSpectrum: TNotifyEvent read FOnAfterComplexSpectrum write SetOnAfterComplexSpectrum; (*Event triggered just before spectrum type is aplied, but after averaging in the complex domain. *) property OnBeforeSpectrumType: TNotifyEvent read FOnBeforeSpectrumType write SetOnBeforeSpectrumType; (*Event triggered just before the average is applied. *) property OnBeforeAverage: TNotifyEvent read FOnBeforeAverage write SetOnBeforeAverage; (*Event triggered just after the average has been applied. *) property OnAfterAverage: TNotifyEvent read FOnAfterAverage write SetOnAfterAverage; end; (*Defines a set of items to be included in signal analysis report. Defines a set of items to be included in signal analysis report. *) TSignalReportSet = class(TPersistent) strict protected FMean: boolean; FCrest: boolean; FSkewness: boolean; FGeneralInfo: boolean; FSignalValues: boolean; FKurtosis: boolean; FMinMax: boolean; FMarkedValues: boolean; FPeak: boolean; FRMS: boolean; FDateTime: boolean; FStdDev: boolean; procedure setCrest(const Value: boolean); procedure setDateTime(const Value: boolean); procedure setGeneralInfo(const Value: boolean); procedure setKurtosis(const Value: boolean); procedure setMarkedValues(const Value: boolean); procedure setMean(const Value: boolean); procedure setMinMax(const Value: boolean); procedure setPeak(const Value: boolean); procedure setRMS(const Value: boolean); procedure setSignalValues(const Value: boolean); procedure setSkewness(const Value: boolean); procedure setStdDev(const Value: boolean); public function IncludesStats: boolean; published (*If True, General information like SamplingFrequency, Bandwidth, number of sampels analyzed etc... will be included in the report. *) property GeneralInfo: boolean read FGeneralInfo write setGeneralInfo default false ; (*If True, values that were marked will be included in the report. *) property MarkedValues: boolean read FMarkedValues write setMarkedValues default false ; (*If True, all values of the signal will be included in the report. *) property SignalValues: boolean read FSignalValues write setSignalValues default false ; (*If True, date and time will be included in the report. *) property DateTime: boolean read FDateTime write setDateTime default false ; (*If True the mean value of the signal will be included in the report. *) property Mean: boolean read FMean write setMean default false ; (*If True, standard deviation of the signal will be included in the report. *) property StdDev: boolean read FStdDev write setStdDev default false ; (*If True, RMS of the signal will be included in the report. *) property RMS: boolean read FRMS write setRMS default false ; (*If True, the value furthest from the mean of the signal will be included in the report. *) property Peak: boolean read FPeak write setPeak default false ; (*If True, CREST parameter will be included in the report. *) property Crest: boolean read FCrest write setCrest default false ; (*If True, Skewness of the signal will be included in the report. *) property Skewness: boolean read FSkewness write setSkewness default false ; (*If True, Kurtosis of the signal will be included in the report. *) property Kurtosis: boolean read FKurtosis write setKurtosis default false ; (*If True, minimum and maximmum of the signal will be included in the report. *) property MinMax: boolean read FMinMax write setMinMax default false ; end; TSignalAnalyzer = class; (*Defines parameters for generating signal analysis report. Defines parameters for generating signal analysis report. *) TSignalReport = class(TPersistent) strict protected Signal: TSignalAnalyzer; FTimeFormat: string; FAmplitudeFormat: string; FReportItems: TSignalReportSet; FDelimiter: string; FUseTab: boolean; procedure SetReportItems(const Value: TSignalReportSet); procedure SetAmplitudeFormat(const Value: string); procedure SetTimeFormat(const Value: string); procedure SetDelimiter(const Value: string); procedure SetUseTab(const Value: boolean); public (*Create spectrum analysis report and store in Strings. *) procedure CreateReport(Strings: TStrings); constructor Create(AOwner: TSignalAnalyzer); virtual; destructor Destroy; override; published (*Defines a set of items to be included in spectrum analysis report. *) property ReportItems: TSignalReportSet read FReportItems write SetReportItems; (*Defines the floating point number formating applied to frequency values in the frequency analysis report. *) property TimeFormat: string read FTimeFormat write SetTimeFormat; (*Defines the floating point number formating applied to amplitude values in the frequency analysis report. *) property AmplitudeFormat: string read FAmplitudeFormat write SetAmplitudeFormat; (*A good example for the delimiter would be Tab character. If the delimiter is not used, the columns are padded with space char and aligned to the left. *) property Delimiter: string read FDelimiter write SetDelimiter; (* If true, tab is used to separate description of parameter from its value. The tab will also be used, between the units and the values. *) property UseTab: boolean read FUseTab write SetUseTab default false; end; (*Analyzes a signal with different methods in time domain. Signal analyzer component. Use this component to quickly estimate auto-correlation, cross-correlation, time signal integration/differentiation etc... This component provides centralized and easy access to many time series analysis routines. The resulting signal is always placed in Self.Data. *) TSignalAnalyzer = class(TSignal) strict protected fWindowInitialized: boolean; FScaleFactor: Double; FWindow: TSignalWindowType; FSidelobeAtt: Double; WindowData: TVec; FTransform: TTimeTransform; FAutoCorrType: TAutoCorrType; FCorrTaps1: integer; FCorrTaps2: integer; FIntegration: TIntegration; FRemoveDC: boolean; FTraceInterval: Double; FExpWindow: Double; FRotation: integer; FReport: TSignalReport; TempData: TVecList; IntgState: array [0..3] of TIntegrateState; DiffState: array [0..3] of TDiffState; procedure SetScaleFactor(const Value: Double); procedure SetWindow(const Value: TSignalWindowType); procedure SetSidelobeAtt(const Value: Double); procedure SetTransform(const Value: TTimeTransform); procedure SetCorrTaps(const Value: integer); procedure SetAutoCorrType(const Value: TAutoCorrType); procedure SetCorrTaps2(const Value: integer); procedure SetIntegration(const Value: TIntegration); procedure DoIntegrate; procedure SetRemoveDC(const Value: boolean); procedure AutoCorr; procedure CrossCorr; procedure SetExpWindow(const Value: Double); procedure SetRotation(const Value: integer); procedure SetReport(const Value: TSignalReport); protected class function EditorClass: string; override; strict protected function InternalUpdate: TPipeState; override; public (*Resets initial conditions for integration and differentiation. *) procedure ResetIntegrator; (*Find the nearest minimum. The initial position in seconds is given with pos and the final position is returned with the Pos parameter. *) procedure NearestMinimum(var Pos: Double); (*Find the nearest maximum. The initial position in seconds is given with pos and the final position is returned with the Pos parameter. *) procedure NearestMaximum(var Pos: Double); (*Compute the position of the zero crossing. The initial position in seconds is given with pos and the final position is returned with the Pos parameter. y contains the final value of the time signal. In ideal case it should be zero. The method will use linear interpolation to locate the position of the zero. *) procedure ZeroCrossing(var Pos: Double; out y: Double); (*Apply the window function. *) procedure ApplyWindow; procedure Assign(Source: TPersistent); override; constructor Create(AOwner:TComponent); override; destructor Destroy; override; published property Inputs: TSignalCollection read GetInputs1 write SetInputs1 stored InputsStored; (*Generates a report/description of the signal. *) property Report: TSignalReport read FReport write SetReport; (* If True, subtract the average value from the signal. This is not usuable for streaming. *) property RemoveDC: boolean read FRemoveDC write SetRemoveDC default False ; (*Select the auto-correlation type. *) property AutoCorrType: TAutoCorrType read FAutoCorrType write SetAutoCorrType; (*Select number of taps for auto-correlation and cross-correlation. *) property CorrTaps1: integer read FCorrTaps1 write SetCorrTaps default 100; (*Select number of taps for cross-correlation. *) property CorrTaps2: integer read FCorrTaps2 write SetCorrTaps2 default 0; (*Defines sidelobe attenuation for the Kaiser and Cheybshev window. *) property SidelobeAtt: Double read FSidelobeAtt write SetSidelobeAtt; (*The signal will be scaled with the ScaleFactor. *) property ScaleFactor: Double read fScaleFactor write SetScaleFactor; (*Defines the window to applied to the signal. *) property Window: TSignalWindowType read fWindow write SetWindow; (*Selects the type of the transform to apply to the signal. *) property Transform: TTimeTransform read FTransform write SetTransform; (*Specifies, if the signal should be integrated or differentiated. *) property Integration: TIntegration read FIntegration write SetIntegration; (*Final value of the exponential window. *) property ExpWindow: Double read FExpWindow write SetExpWindow; (*Defines the rotation in number of samples within the current buffer. Frequency analysis of the power spectrum is invariant to signal buffer rotation. *) property Rotation: integer read FRotation write SetRotation default 0; end; (*Defines which higher order statistics to calculate. Defines which higher order statistics to calculate. *) TBiAnalysis = ( (*Calculates bicoherence.*) hoBicoherence, (*Calculates bispectrum.*) hoBispectrum ); (*Defines the formula used for bicoherence estimation. The parameter affects the denominator in the bicoherence formula. Both versions return numerically the same result, if the amplitudes of the freqency spectrum do not change during averaging. *) TBicoherenceType = ( (*Tripple product is computed after averaging. This version is exactly 2x slower then bctModified, if Recursive is True. If linear averaging is used and Recursive is set to false, then the speed differences are negligable.*) bctDefault, (*Tripple product is computed before averaging.*) bctModified ); (*Encapsulates higher order spectral analysis methods. Used by the TBispectrumAnalyzer to maintain and handle the storage structures for bicoherence and bispectrum. *) TBiAnalyzer = class(TPersistent) strict private Spectrum: TSpectrumAnalyzer; FFrequency: Double; FSingleLinesOnly: boolean; FTransform: TBiAnalysis; FBiSpectrumB: TMtx; FBiSpectrum: TMtx; FBiSpectrumRatio: TMtx; FLines: TVec; FRecursive: boolean; FBicoherenceType: TBicoherenceType; procedure SetFrequency(const Value: Double); procedure SetBispectrum(const Value: TMtx); procedure SetTransform(const Value: TBiAnalysis); procedure SetLines(const Value: TVec); procedure SetSingleLinesOnly(const Value: boolean); procedure SetRecursive(const Value: boolean); procedure SetBicoherenceType(const Value: TBicoherenceType); public (*Returns a full bispectrum/bicoherence matrix in A. If FlipVertical is True, the matrix will be vertically flipped and the row at Index 0 will hold the frequency FS/4 where FS is the sampling frequency. If SingleLinesOnly is True, the routine will insert zeroes in the part of the spectrum that was not computed.. *) procedure GetFullSpectrum(A: TMtx; FlipVertical: boolean = false); (*After the bicoherence or bispectrum has been computed, you can free the associated memory by calling this routine. This will free up to 75% of the memory allocated. This routine is called automatically, if Recursive is False, by the Update method.. *) procedure FreeUpMemory; (*Get a row from the bispectrum matrix, if the entire matrix is computed and maintained. *) procedure DecodeBispectrumMatrix(Row: TVec); (*Get a row from the bispectrum matrix, if only preselected bispectrum matrix rows is computed. *) procedure DecodeSingleLineMatrix(Row: TVec); (*Place the bispectrum (bicoherence) in the Bispectrum property. *) procedure Update; (*Depending on the value of the SingleLinesOnly and Frequency properties, the coresponding row from the matrix will be copied to the Owner.Amplt. The owner is TSpectrumAnalyzer. *) procedure DecodeBiAnalysis; constructor Create(AOwner: TSpectrumAnalyzer); destructor Destroy; override; (*Holds the values defined with the value of the Transform property. *) property BiSpectrum: TMtx read FBispectrum write SetBispectrum; protected (*Internal. *) property BiSpectrumB: TMtx read FBispectrumB; (*Internal. *) property BiSpectrumRatio: TMtx read FBispectrumRatio; published (*Defines how the bicoherence will be computed. *) property BicoherenceType: TBicoherenceType read FBicoherenceType write SetBicoherenceType default bctModified ; (*If True, bicoherence or bispectrum will be calculated on every call to TBispectrumAnalyzer.Update. When using runing averaging this allows you to view bicoherence and bispectrum in real time. When analyzing data off-line, this would uneccessarily slow down the routine. If you are interested in the final result only, set Recursive to false. When averaging has finished and Recursive was false, you have to call the Update method to compute the actuall bicoherence/bispectrum. Recursive is True by default. *) property Recursive: boolean read FRecursive write SetRecursive default True ; (*Define which transform will be copied to the Bispectrum property when you call the Update method. *) property Transform: TBiAnalysis read FTransform write SetTransform; (*Specifies the frequency of bispectral line from the Bispectrum matrix, that should be copied to the Onwer (TSpectrumAnalyzer). *) property Frequency: Double read FFrequency write SetFrequency; (*If True, only preselected frequencies defined with Lines property will be calculated in the bispectral matrix. *) property SingleLinesOnly: boolean read FSingleLinesOnly write SetSingleLinesOnly default false ; (*Holds a real vector of frequencies, that should be calculated in the bispectral matrix. You can define the vector to be of any length and hold any number of different frequencies. The actual lines beeing computed will be based on the sampling frequency and frequency resolution of the Owner. (TSpectrumAnalyzer). By computing only single bispectrum lines for specific frequencies, you can save substantial processing time and are able to increase the bispectral frequency resolution to values unattainable, if you would be computing the full bispectrum matrix. *) property Lines: TVec read FLines write SetLines; end; (*Estimates bicoherence and/or bispectrum. Use the component to estimate bicoherence and bispectrum from the Input signal. You can estimate bicoherence for all the frequency pairs, or just the selected ones. Bicoherence will be one for two frequency pairs, which are related in phase (like harmonics). It will be zero for frequency pairs unrelated in phase. The formula used to calculate the bicoherence: |E( X[f1] * X[f2] * conj(X[f1+f2]) )| b = ------------------------------------------- (E(|X[f1]|) * E(|X[f2]|) * E(|X[f1+f2]|)) X.. complex frequency spectrum. f1.. frequency index, 0 < f1 < (n-1), f2.. frequency index, 0 < f2 < (n/2-1), n.. length of the frequency spectrum E.. averaging function (expected value) Bispectrum is defined as: B = X[f1] * X[f2] * conj(X[f1+f2]) The component features two modes of operation: Recursive and non-recursive. * In Recursive mode (Recursive property = True), all the spectral parameters are update for every call to the Update method. This mode can be used to run bi spectral analysis on-line in real time, if you enable infinite exponential averaging. * In Non-recursive mode (Recursive property = false), only averaging is performed when calls are made to the Update method. Once the averaging has finished, BiAnalyzer.Update method must be called to calculate the actual bispectra and a call to UpdateSpectrum will place the correct line from bispectrum in the Amplt/Phase property. This approach is more CPU efficient then the first. Because bispectral analysis is based on averaging, you can not change the parameters of the frequency spectrum of already averaged data. *) TBiSpectrumAnalyzer = class(TSpectrumAnalyzer) strict private FBiAnalyzer: TBiAnalyzer; FOnGetSpectrum: TNotifyVecEvent; FOnUpdateSpectrum: TNotifyEvent; procedure SetHigherOrder(const Value: TBiAnalyzer); procedure SetOnGetSpectrum(const Value: TNotifyVecEvent); procedure SetOnUpdateSpectrum(const Value: TNotifyEvent); protected class function EditorClass: string; override; strict protected function InternalUpdate: TPipeState; override; (*Decode data from the bispectrum matrix and place it in Self.Amplt. Update peaks and bands properties. *) procedure BrowseBispectrum; public (*Decode data from the bispectrum matrix and place it in Self.Amplt. Also updates peaks and bands properties and then also triggers chart update (if connected). *) procedure UpdateSpectrum; (*Resets averaging and frees internal memory. Memory allocated for averaging are matrices and can together occupy 3/2 x sqr(Length) x sizeof(Double) of space. *) procedure Reset; override; function Update: TPipeState; override; constructor Create(AOwner:TComponent); override; destructor Destroy; override; published (*Parameters required by the higher order spectral analysis. *) property BiAnalyzer: TBiAnalyzer read FBiAnalyzer write SetHigherOrder; (*Assign the event returning the custom computed frequency spectrum to be used for computation of third order spectrum (bispectrum). *) property OnGetSpectrum: TNotifyVecEvent read FOnGetSpectrum write SetOnGetSpectrum; (*Called from the UpdateSpectrum routine. The event is called after the spectrum data is updated and before the chart is updated. *) property OnUpdateSpectrum: TNotifyEvent read FOnUpdateSpectrum write SetOnUpdateSpectrum; end; (*Defines possible results returned by the cross spectrum analyzer. Input signal is the "input" in to the system being analyzed and Output signal is the output from the system. For example: input in the speakers comes from the signal generator and output is captured with a microphone. *) TCrossTransform = ( (*Returns the averaged spectrum of the Input signal.*) ctInputSpectrum, (*Returns the averaged spectrum of the Ouput signal.*) ctOutputSpectrum, (*Returns the averaged cross spectrum.*) ctCrossSpectrum, (*Returns the coherence between the input and output spectrum. Coherence will be 1 for all frequencies that were only amplified/attenuated or delayed by the "system". It can be used to test the linearity of the system being analyzed. If the system is completely linear, the coherence will be one for all the frequencies. If the system is completely non-linear the coherence will be 0 for all the frequencies.*) ctCoherence, (*Returns the amplitude spectrum of the transfer function determined from the Input and Output signals. Use TSpectrumAnalyzer component to obtain also the phase response, if both Input and Output of the system are related in phase. If they are unrelated in phase use TCrossSpectrumAnalyzer to obtain the amplitude response of the system.*) ctTransferFunction ); (*Defines a set of transforms which can be computed by the TCrossSpectrumAnalyzer. Defines a set of transforms which can be computed by the TCrossAnalyzer component. *) TCrossSpectrumAnalysis = ( (*Compute the coherence.*) caCoherence, (*Compute the transfer funciton.*) caTransferFunction ); (*Defines a set of transforms to be evaluated by the TCrossSpectrumAnalyzer component. Defines which transforms will be evaluated on each call to the Update method. *) TCrossAnalysisSet = class(TPersistent) strict private FTransferFunction: boolean; FCoherence: boolean; procedure setCoherence(const Value: boolean); procedure setTransferFunction(const Value: boolean); published (*If True Coherence will be evaluated. *) property Coherence: boolean read FCoherence write setCoherence default true ; (*If True Transfer function will be evaluated. *) property TransferFunction: boolean read FTransferFunction write setTransferFunction default true ; end; (*Encapsulates cross spectral analysis methods. The object is used by the TCrossSpectrumAnalyzer to maintain and handle the storage structures for coherence, transfer function and other cross spectral analysis related data. *) TCrossAnalyzer = class(TPersistent) strict private FTempSpectrum: TVec; Spectrum: TSpectrumAnalyzer; FCoherence: TVec; FCrossSpectrum: TVec; FInputSpectrum: TVec; FOutputSpectrum: TVec; FCrossAnalysis: TCrossAnalysisSet; FCrossTransform: TCrossTransform; FRecursive: boolean; procedure SetCoherence(const Value: TVec); procedure SetCrossSpectrum(const Value: TVec); procedure SetInputSpectrum(const Value: TVec); procedure SetOutputSpectrum(const Value: TVec); procedure SetCrossAnalysis(const Value: TCrossAnalysisSet); procedure SetCrossTransform(const Value: TCrossTransform); procedure SetRecursive(const Value: boolean); strict protected procedure DoCoherence; procedure DoTransferFunction; protected procedure ApplySpectrumType; (*Internal. *) property TempSpectrum: TVec read FTempSpectrum; public (*Stores the complex averaged cross spectrum between Input and Output. *) property CrossSpectrum: TVec read FCrossSpectrum write SetCrossSpectrum; (*Stores the coherence between Input and output. The vector holds valid values only then, if Analysis property contains caCoherence. *) property Coherence: TVec read FCoherence write SetCoherence; (*Stores the averaged power spectrum of the Input. *) property InputSpectrum: TVec read FInputSpectrum write SetInputSpectrum; (*Stores the averaged power spectrum of the Output. *) property OutputSpectrum: TVec read FOutputSpectrum write SetOutputSpectrum; (*Apply averaging, if Recursive was false and compute coherence and/or transfer function. *) procedure Update; constructor Create(AOwner: TSpectrumAnalyzer); destructor Destroy; override; published (*Set Recursive to false, to disable average calculation on each iteration and call the Update method yourself, after the averaging has finished. *) property Recursive: boolean read FRecursive write SetRecursive default true ; (* Defines which transforms will be computed. *) property Analysis: TCrossAnalysisSet read FCrossAnalysis write SetCrossAnalysis; (*Defines which transform will be analyzed by the TSpectrumAnalyzer. *) property Transform: TCrossTransform read FCrossTransform write SetCrossTransform stored False; end; (*Performs cross spectrum analysis. Cross spectrum analysis is used most to determine transfer function of the system, determine the presence of non-linearities in the analyzed system with the coherence and determine frequency spectrums of Input and Output. The coherence is defined as: |E( X * conj(Y) )| coherence = --------------------- |E(X)| * |E(Y)| Transfer function is defined as: E(|Y|) tf = --------- E(|X|) where X is the complex frequency spectrum of the signal entering the system and Y is the complex frequency spectrum of the signal exiting the system. The component features two modes of operation: Recursive and non-recursive. * In Recursive mode (Recursive property = True), all the spectral parameters are update for every call to the Update method. This mode can be used to run cross spectral analysis on-line in real time, if you enable infinite exponential averaging. * In Non-recursive mode (Recursive property = false), only averaging is performed when calls are made to the Update method. Once the averaging has finished, CrossAnalyzer.Update method must be called to calculate the actual spectra and a call to UpdateSpectrum will place the correct spectrum in the Amplt/Phase property. This approach is more CPU efficient then the first. Because cross spectral analysis is based on averaging, you can not change the parameters of the frequency spectrum of already averaged data. *) TCrossSpectrumAnalyzer = class(TSpectrumAnalyzer) strict private FCrossAnalyzer: TCrossAnalyzer; FOnGetInputSpectrum: TNotifyVecEvent; FOnGetOutputSpectrum: TNotifyVecEvent; FOnUpdateSpectrum: TNotifyEvent; procedure SetCrossAnalyzer(const Value: TCrossAnalyzer); procedure SetOnGetInputSpectrum(const Value: TNotifyVecEvent); procedure SetOnGetOutputSpectrum(const Value: TNotifyVecEvent); procedure SetOnUpdateSpectrum(const Value: TNotifyEvent); function GetComplexSpectrum: TVec; protected class function EditorClass: string; override; strict protected function InternalUpdate: TPipeState; override; procedure BrowseCrossSpectrum; virtual; procedure ComputePhaseDelay; override; procedure ApplyPhaseSpectrum; override; public procedure UpdateLogarithmic; override; (*This property returns a pointer to the scaled averaged complex cross spectrum. *) property ComplexSpectrum: TVec read GetComplexSpectrum; (*Copy the spectrum specified by CrossAnalyzer.Transform property to Self.Amplt. and also performs peak and frequency band analysis, if specified. *) procedure UpdateSpectrum; (*The method is overriden to provide support for the CrossAnalyzer.Recursive property. *) function Update: TPipeState; override; constructor Create(AOwner: TComponent); override; destructor Destroy; override; published (*Holder for all the storage and settings required by the cross spectral analysis. *) property CrossAnalyzer: TCrossAnalyzer read FCrossAnalyzer write SetCrossAnalyzer; (*Event returning the custom computed frequency spectrum to be used for computation of the input spectrum. Returned spectrum must be complex and its length must match the length of the Input spectrum. *) property OnGetInputSpectrum: TNotifyVecEvent read FOnGetInputSpectrum write SetOnGetInputSpectrum; (*Assign the event returning the custom computed frequency spectrum to be used for computation of the output spectrum. Returned spectrum must be complex and its length must match the length of the Input spectrum. *) property OnGetOutputSpectrum: TNotifyVecEvent read FOnGetOutputSpectrum write SetOnGetOutputSpectrum; (*This event is called from the UpdateSpectrum routine. The event is called after the spectrum data is updated and before the chart is updated. *) property OnUpdateSpectrum: TNotifyEvent read FOnUpdateSpectrum write SetOnUpdateSpectrum; (*Assign "output" of the system being analyzed. The transfer function is defined as a ratio: input/output. Calculate the transfer function with TSpectrumAnalyzer, if the phase of input and ouput is related and you are interested in the phase spectrum. You can also use the TCrossSpectrumAnalyzer component, if the phases are not related. *) property Output; end; (*Method type used to return result computed by the method. *) TOnComputeStats = procedure (Sender: TObject; var aResult: Double) of object; (*Abstract class for storing statistics computed from Input data. The computation can be customized within an event or also with user specified math expression. Each computation returns one scalar value computed from the input vector. The values computed are stored in the Data property. When the buffering is reset, the next value will again be written at Data[0]. When Data vector is full, the Update function returns pipeOK and no longer pipeStream. pipeOK is returned once, then the counters are reset again to start writing at Data[0]. The component usually connected to the Input of this component is TSignalBuffer. TSignalBuffer.StrideLength is automatically assigned to the StrideLength property in this case. StrideLength property defines the sampling frequency of the output data as: SamplingFrequency := Input.SamplingFrequency/StrideLength; StrideLength defines the step in number of samples between consecutive windows of data from which the statistical parameters are computed. The Length of the windows is specified with the Input.Length property. *) TStatsBuffer = class(TSignal) strict private FExpression: TMtxExpression; FOnComputeStats: TOnComputeStats; FOnBufferFilled: TNotifyEvent; FStrideLength: integer; procedure SetExpression(const Value: TMtxExpression); procedure SetOnComputeStats(const Value: TOnComputeStats); procedure SetOnBufferFilled(const Value: TNotifyEvent); procedure SetStrideLength(const Value: integer); strict protected Counter: integer; public property Expression: TMtxExpression read FExpression write SetExpression; (*Resets buffering. *) procedure Reset; override; (*Returns the current position at which the values are written in the Data property. When the buffer is full it passes pipeOK to the connected component and rolls over. *) function CurrentCount: integer; constructor Create(AOwner: TComponent); override; published (*To compute custom statistics, place the computation inside this event and assign the result to the result variable. *) property OnComputeStats: TOnComputeStats read FOnComputeStats write SetOnComputeStats; (*Called when buffer is full and before the data contained is used by any conected components. *) property OnBufferFilled: TNotifyEvent read FOnBufferFilled write SetOnBufferFilled; (*Specifies the number of samples the input buffer skips forward on each new buffer returned. The new sampling frequency is computed as: SamplingFrequency := SamplingFrequency/StrideLength; *) property StrideLength: integer read FStrideLength write SetStrideLength; end; (*Defines signal statistics that can be streamed. Defines signal statistics that can be streamed in to a discrete signal. *) TSignalStatistics = ( (*Signal RMS.*) ssRMS, (*Maximum deviation from the mean.*) ssPeak, (*Standard deviation.*) ssStdDev, (*CREST factor.*) ssCrest, (*Average value.*) ssMean, (*Kurtosis of the signal .*) ssKurtosis, (*Skewness of the signal.*) ssSkewness, (*Maximum value.*) ssMax, (*Minimum value.*) ssMin, (*Custom statistics.*) ssSignalCustom ); (*Computes and stores statistics from Input data. Computes specified statistical parameter on the Input data and stores the result in Data property. Until the Data vector is full, it is returning pipeStream on Pull requests. Once the Data is full, it returns pipeOK once and resest the buffer counter to 0. The current writing position is returned via CurrentCount index. The component computes one value (1) from entire Input.Length . Usually the component is preceeded with to specify window analysis length and any window overlapping. *) TSignalStatsBuffer = class(TStatsBuffer) strict private FMean: TSignalStatsBuffer; FStatistics: TSignalStatistics; FStdDev: TSignalStatsBuffer; procedure SetMean(const Value: TSignalStatsBuffer); procedure SetStatistics(const Value: TSignalStatistics); procedure SetStdDev(const Value: TSignalStatsBuffer); strict protected function InternalUpdate: TPipeState; override; public procedure ReferenceRemoved(Sender: TObject); override; function Update: TPipeState; override; constructor Create(AOwner: TComponent); override; published (*Contains optional object with Mean values. *) property Mean: TSignalStatsBuffer read FMean write SetMean; (* Assign optional object with StdDev values. *) property StdDev: TSignalStatsBuffer read FStdDev write SetStdDev; (*Specifies statistics to be stored in the component. *) property Statistics: TSignalStatistics read FStatistics write SetStatistics; property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; end; (*Defines spectral statistics that can be streamed. Defines spectral statistics that can be streamed in to a signal. *) TSpectrumStatistics = ( (*Amplitude of the marked peak.*) ssPeakAmplt, (*Phase of the marked peak.*) ssPeakPhase, (*Frequency of the marked peak.*) ssPeakFreq, (*Total harmonic distortion.*) ssTHD, (*Total harmonic distortion and noise.*)ssTHDNoise, (*Signal to noise ratio.*) ssSNR, (*Signal to noise ratio and distortion.*) ssSINAD, (*Spurious free dynamic range.*) ssSFDR, (*Noise floor.*) ssNF, (*RMS of a frequency band.*) ssBandRMS, (*Maximum amplitude.*) ssMaxAmplt, (*Custom spectrum statistics.*) ssSpectrumCustom ); (*Stores and buffers statistics from a SpectrumAnalyzer. Computes specified statistical parameter on the SpectrumAnalyzer data and stores the result in Data property. Until the Data vector is full, it is returning pipeStream on Pull requests. Once the Data is full, it returns pipeOK once and resest the buffer counter to 0. The current writing position is returned via CurrentCount index. *) TSpectrumStatsBuffer = class(TStatsBuffer) strict private FSpectrumAnalyzer: TSpectrumAnalyzer; FBandIndex: integer; FMarkedPeak: integer; FStatistics: TSpectrumStatistics; procedure SetBandIndex(const Value: integer); procedure SetMarkedPeak(const Value: integer); procedure SetSpectrumAnalyzer(const Value: TSpectrumAnalyzer); procedure SetStatistics(const Value: TSpectrumStatistics); strict protected function InternalUpdate: TPipeState; override; public procedure ReferenceRemoved(Sender: TObject); override; function Update: TPipeState; override; constructor Create(AOwner: TComponent); override; published (*Specifies statistics to be stored in the component. *) property Statistics: TSpectrumStatistics read FStatistics write SetStatistics; (*Specifies the marked peak number whose data is to be stored. *) property MarkedPeak: integer read FMarkedPeak write SetMarkedPeak default 0; (*Specifies the frequency band number whose RMS data is to be stored. *) property BandIndex: integer read FBandIndex write SetBandIndex; (*Connect spectrum analyzer component for the source of data and statistics. *) property SpectrumAnalyzer: TSpectrumAnalyzer read FSpectrumAnalyzer write SetSpectrumAnalyzer; end; (*Stores and buffers an overview of the incoming signal. The size of the preview is maintained small enough to allow quick redraw on a chart. At start the component holds the actual signal up to the size of the SpanLimit. Once SpanLimit is exceeded an overview of the signal is computed and the original is discarded. From that point on, only the overview of the signal is kept. Once the overview exceeds the SpanLimit a new signal overview is computed from the existing overview and this procedure repeats in to infinity allowing virtually unlimited length of the overview. The component allow's limited zoom-in in to the overview, because intermediate results are discarded, but the data discarded will not exceed a factor of sqr(Increment). The default value for increment is 20 and 20^2 = 400. For a 2GB long recording this will result in 5MB of the in-memory buffer. On the resulting overview PixelDownSample method should be applied again. For true navigation use TSignalBrowse component. The purpose of this component is to allow an overview of already recorded data to be displayed to the user. The IsOverview function returns true, if the component stores an overview, otherwise it is still buffering the original signal. This is important to know because the drawing method may differ substantially for signal and overview. The overview stores Max and Min values interleaved. The Max values form the upper and the Min values from the lower envelope. Calling Update or Pull method will return pipeOK, if the Data property has been updated. It will return pipeStream, if there is not enough data buffered yet to update. *) TSignalBrowseBuffer = class(TSignal) strict private OverviewLevel: integer; Data2Len, Data1Len: integer; FIsOverview: boolean; FSpanLimit: integer; Buffer1, Buffer2: TSignalBuffer; StoreBuffer1, StoreBuffer2: TSignalStoreBuffer; Data1,Data2: TSignal; FIncrement: integer; procedure IncreaseData2; procedure IncreaseData1; procedure SetIncrement(const Value: integer); procedure SetSpanLimit(const Value: integer); strict protected function InternalUpdate: TPipeState; override; public (*Defines, if the component already stores an overview. Returns true, if the component already stores an overview. If the result is false, it is still buffering original signal. *) function IsOverview: boolean; (*Reset the buffering and overview. *) procedure Reset; override; constructor Create(AOwner: TComponent); override; destructor Destroy; override; published (*Defines the samples count at which an overview is computed. *) property SpanLimit: integer read FSpanLimit write SetSpanLimit default 32786; (*Defines the increment at which the overview is sampled. The increment step is used in two stages and thus the actual increment is sqr(Increment). *) property Increment: integer read FIncrement write SetIncrement default 40; end; (*Signals saturation warning when recording. Use the component to detect substantial clipping-off of the recorded signal. Many failed recordings done by inexpirienced users are compromised due to signal saturation (clipping). *) TSignalSaturationWarning = class(TSignal) strict private Timer: Double; FSigned: boolean; FShortTermWindow: Double; FPrecisionBits: integer; fClipped: boolean; fClippedShort: boolean; fClippedCurrent: boolean; FTopLimit: Double; FBottomLimit: Double; procedure UpdateRange; procedure SetPrecisionBits(const Value: integer); procedure SetShortTermWindow(const Value: Double); procedure SetSigned(const Value: boolean); strict protected function InternalUpdate: TPipeState; override; public (*Resets clipped flags. *) procedure Reset; override; (*Returns the lower dynamic range limit. *) function LowerLimit: Double; (*Returns the upper dynamic range limit. *) function UpperLimit: Double; (*Returns true, if the signal has been clipped. *) function Clipped: boolean; (*Returns true, if the signal has been clipped within last 5 seconds. *) function ClippedShort: boolean; (*Returns true, if the current values of the signal are clipped. *) function ClippedCurrent: boolean; constructor Create(AOwner: TComponent); override; published (* Defines the (integer) dynamic range of the A/D. Any values exceeding the range of the precision bits are considered to be clipped off. If the data format is floating point specify a value of 0. The dynamic range checked will be within -1..+1 *) property PrecisionBits: integer read FPrecisionBits write SetPrecisionBits default 16; (*Signal is signed, if centered around zero. Set this value to true to indicate if the (integer) signal is centered around zero or spans from zero to 2^(PrecisionBits). *) property Signed: boolean read FSigned write SetSigned default true ; (*Defines the "clipp-hold" time in seconds after which the ClippedShort flag is reset. Default value is 5 seconds. *) property ShortTermWindow: Double read FShortTermWindow write SetShortTermWindow; end; (*Stores the history of the associated Mark. Stores the history of individual fields of associated TMarkRecord. The Mark to trace is specified with the MarkIndex property. *) TTraceItem = class(TPersistent) strict private FMarkIndex: integer; FSpectralDt: Double; FSpectralSamplingFrequency: Double; FSpectralSamplingTime: Double; FSpectralTimeOffset: Double; procedure SetMarkIndex(const Value: integer); procedure SetSpectralSamplingFrequency(const Value: Double); procedure SetSpectralTimeOffset(const Value: Double); public (*Stores the history of the TMarkRecord Amplt field. *) Amplt: TVec; (*Stores the history of the TMarkRecord Freq field. *) Freq: TVec; (*Stores the history of the TMarkRecord Phase field. *) Phase: TVec; (*Stores the history of the TMarkRecord Time field. *) Time: TVec; (*Defines the index in the MarkList, to identify the mark whose history is to be stored. *) property MarkIndex: integer read FMarkIndex write SetMarkIndex; (*Time in seconds at which the spectrogram starts. This parameter does not affect the computation, but can be used later on for charting. *) property SpectralTimeOffset: Double read FSpectralTimeOffset write SetSpectralTimeOffset; (*Total length in seconds between the first and the last spectrum. This value is equal to Count*Dt. This is a ready only property, but the contents of the object can be changed. *) property SpectralSamplingTime: Double read FSpectralSamplingTime write FSpectralSamplingTime; constructor Create; virtual; destructor Destroy; override; procedure Assign(Src: TPersistent); override; published (*Time between two consecutive traced values. *) (*Sampling frequency with which the spectrums are being sampled. When setting this property Dt is automatically updated. *) property SpectralSamplingFrequency: Double read FSpectralSamplingFrequency write SetSpectralSamplingFrequency; (*Time in seconds between consecutive spectrums. When setting this property SamplingFrequency is automatically updated. *) property SpectralDt: Double read FSpectralDt; end; (*Form vectors from a list of TMarkList's. Each time a time signal or spectrum are updated their marks are also updated. This object provides methods for storing the history of values that marks have had. *) TTraceList = class(TPersistent) strict private aList: TObjectsList; FTimeTrace: boolean; FFreqTrace: boolean; FPhaseTrace: boolean; FAmpltTrace: boolean; FValueCount: integer; procedure SetCount(const Value: integer); procedure SetAmpltTrace(const Value: boolean); procedure SetFreqTrace(const Value: boolean); procedure SetPhaseTrace(const Value: boolean); procedure SetTimeTrace(const Value: boolean); function GetItems(i: integer): TTraceItem; procedure SetItems(i: integer; const Value: TTraceItem); function GetCount: integer; procedure SetValueCount(const Value: integer); function GetSpectralTimeOffset: Double; procedure SetSpectralTimeOffset(const Value: Double); function GetSpectralSamplingFrequency: Double; procedure SetSpectralSamplingFrequency(const Value: Double); public (* Resets storage size for all traces. ValueCount is set to zero and all memory allocated to store traces is also reset to 0. *) procedure Reset; (* Adjusts the allocated memory to match number of traced items. To speed up tracing, more memory is allocated than what is immediately needed by the number of traced items. At the end of tracing call EndTrace to resize the memory to match the number of traced items. *) procedure EndTrace; (*Number of values stored for each Item. Usually equal to TSpetrogram.SpectrogramList.Count *) property ValueCount: integer read FValueCount write SetValueCount; (*Returns trace item at index i. *) property Items[i: integer]: TTraceItem read GetItems write SetItems; default; (*If true, the Amplt field in the marks will be traced. *) property AmpltTrace: boolean read FAmpltTrace write SetAmpltTrace; (*If true, the Phase field in the marks will be traced. *) property PhaseTrace: boolean read FPhaseTrace write SetPhaseTrace; (*If true, the Freq field in the marks will be traced. *) property FreqTrace: boolean read FFreqTrace write SetFreqTrace; (*If true, the time field in the marks will be traced. *) property TimeTrace: boolean read FTimeTrace write SetTimeTrace; (*Get or set the number of TTraceItem objects. If the value is smaller, than the current count, they will be deleted. If the value is bigger, they will be created. *) property Count: integer read GetCount write SetCount; (*Add a new TTraceItem object to the list. The function returns the the index at which the new object has been added. *) function Add: integer; overload; (*Add a new TTraceItem object to the list. Thefunction returns the the index at which the new object has been added. *) function Add(Item: TTraceItem): integer; overload; (*Add new TTraceItem object to the list, by copying them for the Src TTraceList. *) procedure Add(Src: TTraceList); overload; (*Clear all items and free associated objects. *) procedure Clear; (*Delete item at Index and free associated object. *) procedure Delete(Index: integer); constructor Create; virtual; destructor Destroy; override; published (*Time between two consecutive traced values. *) (*Sampling frequency with which the spectrums are being sampled. When setting this property Dt is automatically updated. *) property SpectralSamplingFrequency: Double read GetSpectralSamplingFrequency write SetSpectralSamplingFrequency; (*Time in seconds at which the spectrogram starts. This parameter does not affect the computation, but can be used later on for charting. Setting this values modifes only all currently present items. *) property SpectralTimeOffset: Double read GetSpectralTimeOffset write SetSpectralTimeOffset; end; (*Form vectors from a list of TMarkList's. Spectrogram expects a TSpectrumAnalyzer or descendant to be connected to its Input property. Each time the Pull method is called on component, the connected components are recalcuated and new spectrum is added to the list of spectrums within the component. There are various support routines avialable to help manage that data. Typical usage scenarios are like this (assuming single channel file): SignalRead.FileName := YourFile; SignalRead.OpenFile; SignalRead.RecordTimePosition := StartingTime; SpectrumAnalyzer.Input := SignalRead; Spectrogram.Input := SpectrumAnalyzer; Spectrogram.PullUntilEnd;//process entire file Spectrogram.SpectralTimeOffset := StartingTime; Spectrogram.SpectralSamplingFrequency := 1/SignalRead.FramesPerSecond; Alternative: SignalRead.FileName := YourFile; SignalRead.OpenFile; SignalRead.RecordTimePosition := StartingTime; SpectrumAnalyzer.Input := SignalRead; SpectrumAnalyzer.Peaks.TraceMethod := ptAmpltHarmonics; Spectrogram.TraceOnly := true; Spectrogram.Input := SpectrumAnalyzer; Spectrogram.PullUntilEnd;//process entire file Spectrogram.Trace.EndTrace; Spectrogram.Trace.SpectralTimeOffset := StartingTime; Spectrogram.Trace.SpectralSamplingFrequency := 1/SignalRead.FramesPerSecond; // Spectrogram.Trace[i].Freq //TVec contains gathered data // Spectrogram.Trace[i].Amplt //TVec contains gathered data *) TSpectrogram = class(TStaticSpectrumAnalyzer) strict private FSpectrumList: TStaticSpectrumAnalyzerList; FSpectralSamplingTime: Double; FSpectralDt: Double; FSpectralSamplingFrequency: Double; FMaxSpectrumCount: integer; FLogSpan: TLogSpan; FTrace: TTraceList; FLogarithmic: boolean; FSpectralTimeOffset: Double; FNarrowBandL: Double; FNarrowBandH: Double; FTraceOnly: boolean; procedure SetLogSpan(const Value: TLogSpan); procedure SetMaxSpectrumCount(const Value: integer); function GetCount: integer; function GetSpectrum(i: integer): TStaticSpectrumAnalyzer; procedure SetCount(const Value: integer); procedure SetSpectralDt(const Value: Double); procedure SetSpectralSamplingFrequency(const Value: Double); procedure SetSpectrum(i: integer; const Value: TStaticSpectrumAnalyzer); function GetInput1: TSpectrum; reintroduce; procedure SetInput1(const Value: TSpectrum); procedure SetTrace(const Value: TTraceList); procedure TraceAmplt(MarkIndex: integer; const Amplt: TVec); procedure TraceFreq(MarkIndex: integer; const Freq: TVec); procedure TracePhase(MarkIndex: integer; const Phase: TVec); procedure TraceTime(MarkIndex: integer; const Time: TVec); procedure SetLogarithmic(const Value: boolean); procedure SetSpectralTimeOffset(const Value: Double); procedure SetNarrowBandH(const Value: Double); procedure SetNarrowBandL(const Value: Double); procedure AppendTrace; procedure SetTraceOnly(const Value: boolean); strict protected function InternalUpdate: TPipeState; override; function CreateInputs: TAnalysisConnectorsCollection; override; (*Updates marks for all spectrums according to the parameters specified by the Peaks property. *) procedure UpdateMarks; (*Updates the contents of the Trace object according the value of its properties. *) procedure UpdateTrace(TraceIndex: integer); public (*Containes parameters and results for individual traces. A trace is a time series of one of the three parameters (frequency, amplitude, phase) for a predefined peak in dependence of time. *) property Trace: TTraceList read FTrace write SetTrace; (*Time in seconds at which the spectrogram starts. This parameter does not affect the computation, but can be used later on for charting. *) property SpectralTimeOffset: Double read FSpectralTimeOffset write SetSpectralTimeOffset; (*Total length in seconds between the first and the last spectrum. This value is equal to Count*Dt. This is a ready only property, but the contents of the object can be changed. *) property SpectralSamplingTime: Double read FSpectralSamplingTime; (*Specifies the number of the spectrums in the list. Setting the property will set the number of spectrums in the list. *) property Count: integer read GetCount write SetCount; (*Returns spectrum at index i. *) property Spectrum[i: integer]: TStaticSpectrumAnalyzer read GetSpectrum write SetSpectrum; default; (*Propagates settings of Peaks and Bands properties to all spectrums. *) procedure UpdateTraceParameters; (*Copies marked values from spectrogram to the Trace property. *) procedure FetchTraces; (*Updates marks for all spectrums then calls FetchTraces. *) procedure UpdateAllTraces; (*Delete the spectrum at index i. Delete spectrum at index. *) procedure Delete(Index: integer); (*Insert spectrum at index. *) procedure Insert(Index: integer); (*Move spectrum from SrcIdx to DstIdx index. *) procedure Move(SrcIdx, DstIdx: integer); (*Rotate spectrums down or up in the list. *) procedure Rotate(Offset: integer); (*Copies all amplitude spectrums as rows to the destination matrix. *) procedure CopyAmplt(Dst: TMtx); (*Copies all amplitude spectrums as columns to the destination matrix. *) procedure CopyAmpltTransposed(Dst: TMtx); overload; (*Copies all amplitude spectrums as columns to the destination matrix. *) procedure CopyAmpltTransposed(Dst: TMtx; TimeStart, TimeStop, FreqStart, FreqStop: Double); overload; (*Copies all phase spectrums as rows to the destination matrix. *) procedure CopyPhase(Dst: TMtx); (*Reset the spectrogram and delete the stored data. *) procedure Reset; override; constructor Create(AOwner: TComponent); override; destructor Destroy; override; published (*Specifies to track only marked peaks without entire spectrums. Only Trace property will be populated when this property is True. Call Trace.EndTrace method when the last spectrum has been processed. When true the contents of the marked peaks will be stored in the Trace property and the contents of the spectrum will be ignored. This can be used for subsequent analysis of the spectrogram where only specific frequencies are be analyzed with higher resolution. *) property TraceOnly: boolean read FTraceOnly write SetTraceOnly; (*Specifies Input for the data to the component. *) property Input: TSpectrum read GetInput1 write SetInput1 stored InputsStored; (*Sampling frequency with which the spectrums are being sampled. When setting this property Dt is automatically updated. *) property SpectralSamplingFrequency: Double read FSpectralSamplingFrequency write SetSpectralSamplingFrequency; (*Time in seconds between consecutive spectrums. When setting this property SamplingFrequency is automatically updated. *) property SpectralDt: Double read FSpectralDt write SetSpectralDt; (*Maximum number of spectrums up to which the spectrogram will grow. *) property MaxSpectrumCount: integer read FMaxSpectrumCount write SetMaxSpectrumCount default 20; (*Defines the span of the logarithmic spectrum in dB. *) property LogSpan: TLogSpan read FLogSpan write SetLogSpan default ls120 ; (*If true, the stored spectrums will be converted to Logarithmic scale. New spectrums should not be added to the spectrogram, while Logarithmic property is checked. Calling Pull method while Logarithmic property is True, will raise an exception. It is possible however to toggle the value of the Logarithmic property. Changing the value of this property immediately triggers conversion. The value of the LogSpan property will be used as a parameter in the conversion. Logarithmic property can not be used, if incoming spectrums already have logarithmic scale: logarithmic scale can not be undone, because exact parameters on how it was applied are not known. All spectrum analysis methods from Peaks, Bands and Reports properties will comply with the value of the Logarithmic property. *) property Logarithmic: boolean read FLogarithmic write SetLogarithmic; (* Specify value other than 0 to indicate the start of the reduced bandwidth. Spectrogram will store only values between NarrowBandL and NarrowBandH. *) property NarrowBandL: Double read FNarrowBandL write SetNarrowBandL; (* Specify value other than 0 to indicate the final frequency of the reduce bandwidth. Spectrogram will store only values between NarrowBandL and NarrowBandH. *) property NarrowBandH: Double read FNarrowBandH write SetNarrowBandH; end; (*Manages a list of TSpectrogram objects. Manages a list of TSpectrogram objects. *) TSpectrogramList = class(TSpectrumList) strict private function GetItems(index: integer): TSpectrogram; reintroduce; procedure SetItems(index: integer; const Value: TSpectrogram); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSpectrogram components by Index. *) property Items[index: integer]: TSpectrogram read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (*Manages a list of TSignalSaturationWarning objects. Manages a list of TSignalSaturationWarning objects. *) TSignalSaturationWarningList = class(TAnalysisList) strict private function GetItems(index: integer): TSignalSaturationWarning; reintroduce; procedure SetItems(index: integer; const Value: TSignalSaturationWarning); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSignalSaturationWarning components by Index. *) property Items[index: integer]: TSignalSaturationWarning read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (*Manages a list of TSignalBrowseBuffer objects. Manages a list of TSignalBrowseBuffer objects. *) TSignalBrowseBufferList = class(TAnalysisList) strict private function GetItems(index: integer): TSignalBrowseBuffer; reintroduce; procedure SetItems(index: integer; const Value: TSignalBrowseBuffer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSignalBrowseBuffer components by Index. *) property Items[index: integer]: TSignalBrowseBuffer read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (*Manages a list of TSpectrumStatsBuffer objects. Manages a list of TSpectrumStatsBuffer objects. *) TSpectrumStatsBufferList = class(TAnalysisList) strict private function GetItems(index: integer): TSpectrumStatsBuffer; reintroduce; procedure SetItems(index: integer; const Value: TSpectrumStatsBuffer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSpectrumStatsBuffer components by Index. *) property Items[index: integer]: TSpectrumStatsBuffer read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (*Manages a list of TSignalStatsBuffer objects. Manages a list of TSignalStatsBuffer objects. *) TSignalStatsBufferList = class(TAnalysisList) strict private function GetItems(index: integer): TSignalStatsBuffer; reintroduce; procedure SetItems(index: integer; const Value: TSignalStatsBuffer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSignalStatsBuffer components by Index. *) property Items[index: integer]: TSignalStatsBuffer read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (*Manages a list of TSpectrumAnalyzer objects. Manages a list of TSpectrumAnalyzer objects. *) TSpectrumAnalyzerList = class(TSpectrumList) strict private function GetItems(index: integer): TSpectrumAnalyzer; reintroduce; procedure SetItems(index: integer; const Value: TSpectrumAnalyzer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSpectrumAnalyzer components by Index. *) property Items[index: integer]: TSpectrumAnalyzer read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (*Manages a list of TCrossSpectrumAnalyzer objects. Manages a list of TCrossSpectrumAnalyzer objects. *) TCrossSpectrumAnalyzerList = class(TSpectrumList) strict private FOutputs: TSignalList; FOutput: TSignal; function GetItems(index: integer): TCrossSpectrumAnalyzer; reintroduce; procedure SetItems(index: integer; const Value: TCrossSpectrumAnalyzer); procedure SetOutputs(const Value: TSignalList); procedure SetOutput(const Value: TSignal); strict protected function AddItem: TMtxComponent; override; procedure InternalMatchInputs; override; public constructor Create(AOwner: TComponent); override; (*Access TSpectrumAnalyzer components by Index. *) property Items[index: integer]: TCrossSpectrumAnalyzer read GetItems write SetItems; default; published (*Connect system output signals to this connector. If each system has its own output assign the list of outputs to this property. *) property Outputs: TSignalList read FOutputs write SetOutputs stored InputsStored; (*Connect system output signal to this connector. If all systems have common output assign the output to this property. *) property Output: TSignal read FOutput write SetOutput stored InputsStored; (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (*Manages a list of TBiSpectrumAnalyzer objects. Manages a list of TBiSpectrumAnalyzer objects. *) TBiSpectrumAnalyzerList = class(TSpectrumList) strict private function GetItems(index: integer): TBiSpectrumAnalyzer; reintroduce; procedure SetItems(index: integer; const Value: TBiSpectrumAnalyzer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSpectrumAnalyzer components by Index. *) property Items[index: integer]: TBiSpectrumAnalyzer read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (*Manages a list of TSignalAnalyzer objects. Manages a list of TSignalAnalyzer objects. *) TSignalAnalyzerList = class(TSignalList) strict private function GetItems(index: integer): TSignalAnalyzer; reintroduce; procedure SetItems(index: integer; const Value: TSignalAnalyzer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSignalAnalyzer components by Index. *) property Items[index: integer]: TSignalAnalyzer read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (*Convert a TCrossTransform type to a string. The function returns a description of a TCrossTransform type specified by the CrossTransform parameter. *) function CrossTransformToString(CrossTransform: TCrossTransform): string; (*Convert a TSpectrumMethod type to a string. The function returns a description of a TSpectrumMethod type specified by the SpectrumMethod parameter. *) function SpectrumMethodToString(SpectrumMethod: TSpectrumMethod): string; (*Convert a TAveraging type to a string. The function returns a description of a TAveraging type specified by the Averaging parameter. *) function AveragingToString(Averaging: TAveraging): string; (*Convert a TIntegration type to a string. The function returns a description of a TIntegration type specified by the Integration parameter. *) function IntegrationToString(Integration: TIntegration): string; (*Convert a TInterpolationMethod type to a string. The function returns a description of a TInterpolationMethod type specified by the Method parameter. *) function InterpolationMethodToString(Method: TInterpolationMethod): string; (*Convert a TInterpolationPrecision type to a string. The function returns a description of a TInterpolationPrecision type specified by the Precision parameter. *) function InterpolationPrecisionToString(Precision: TInterpolationPrecision): string; (*Convert a TTimeTransform type to a string. The function returns a description of a TTimeTransform type specified by the Transform parameter. *) function TimeTransformToStr(Transform: TTimeTransform): string; (*Convert a TAutoCorrType type to a string. The function returns a description of a TAutoCorrType type specified by the AutoCorrType parameter. *) function AutoCorrTypeToStr(AutoCorrType: TAutoCorrType): string; (* Signal processing components. *) unit SignalProcessing; interface {$I BdsppDefs.inc} {$WARN SYMBOL_DEPRECATED OFF} uses MtxVec, Math387, SignalTools, SignalUtils, IIRFilters, Polynoms, MtxBaseComp, MtxVecBase ,Types ,Classes ,Sysutils ,Contnrs ; (* Maximum power of two that rate conversion factor can have. *) const MaxRateConversionPowerFactor = 20; type TSignalChannel = class(TSignal) strict private FChannels: TIntegersList; procedure SetChannel(const Value: integer); function GetChannel: integer; procedure SetChannels(const Value: TIntegersList); public property Channels: TIntegersList read FChannels write SetChannels; constructor Create(AOwner: TComponent); override; destructor Destroy; override; published (* Specifies the hardware channel number where the data for this object is to come from or go to. *) property Channel: integer read GetChannel write SetChannel; end; (* Demultiplex input signal. Use this component to demultiplex the Channel from the Input and place the result in Self (Data property). *) TSignalDemux = class(TSignal) strict private FChannel: integer; procedure SetChannel(const Value: integer); strict protected function InternalUpdate: TPipeState; override; public constructor Create(AOwner: TComponent); override; published property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; (* Specifies the channel number to be demultiplexed from the Input. *) property Channel: integer read FChannel write SetChannel default 0; end; (* Multiplex input signal. Use this component to multiplex channels from the Inputs and place the result in Self (Data property). Complex,Length, SamplingFrequency and ChannelCount properties are adjusted automatically. All Inputs must be of the same length and same sampling frequency. *) TSignalMux = class(TSignal) strict private FInputList: TAnalysisList; procedure SetInputList(const Value: TAnalysisList); strict protected function InternalUpdate: TPipeState; override; function GetChannelCount: integer; override; public property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; procedure MatchInputs; virtual; function InternalPull: TPipeState; override; constructor Create(AOwner: TComponent); override; published property Inputs: TSignalCollection read GetInputs1 write SetInputs1 stored InputsStored; (* Component list connector. When this property is assigned it will override any changes made to the Inputs property the first time when the Update method is called. The inputs property will be set like this: Inputs.Count := InputList.Count; for i := 0 InputList.Count-1 do Inputs[i].Input := InputList[i]; *) property InputList: TAnalysisList read FInputList write SetInputList stored InputsStored; end; (* Form complex signal. Use this component to join real and imaginary channels from the RealInput and ImagInput in to a complex signal and place the result in Self (Data property). Complex,Length, SamplingFrequency and ChannelCount properties are adjusted automatically. All Inputs must be of the same length and same sampling frequency. *) TSignalToComplex = class(TSignal) strict private procedure SetImagInput(const Value: TSignal); procedure SetRealInput(const Value: TSignal); function GetRealInput: TSignal; function GetImagInput: TSignal; strict protected function InternalUpdate: TPipeState; override; public function InternalPull: TPipeState; override; constructor Create(AOwner: TComponent); override; published (* Real component of the complex signal. *) property RealInput: TSignal read GetRealInput write SetRealInput; (* Imaginary component of the complex signal. *) property ImagInput: TSignal read GetImagInput write SetImagInput; end; (* Increase buffer length. Use this component to increase the length of the data blocks being processed by Factor. Each time an update request is received, a new block of data is appended to the existing buffer. Once the Length exceeds the Input.Length*Factor the buffer is shifted left for each new block of data. *) TSignalIncBuffer = class(TSignal) strict private FFactor: integer; procedure SetFactor(const Value: integer); strict protected function InternalUpdate: TPipeState; override; public (* Reset buffer Length to zero. *) procedure Reset; override; constructor Create(AOwner: TComponent); override; published property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; (* Defines the factor by which to increase the data block. *) property Factor: integer read FFactor write SetFactor default 1; end; (* Decrease buffer length. Use this component to decrease the length of the data blocks being processed by Factor. Each time an update request is received, a new block of data is obtained from the input buffer. Until the Input buffer is empty, the Pull request is not passed to it. *) TSignalDecBuffer = class(TSignal) strict private FPosition: integer; FFactor: integer; procedure SetFactor(const Value: integer); strict protected function InternalUpdate: TPipeState; override; public function InternalPull: TPipeState; override; constructor Create(AOwner: TComponent); override; (* Reset the read position of the input buffer. *) procedure Reset; override; published property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; (* Defines the factor by which to decrease the data block. *) property Factor: integer read FFactor write SetFactor default 1; end; TBandFilterRecordDouble = packed record BandStart: double; BandEnd: double; Gain: double; AttRipple: double; Achieved: double; Checked: byte; end; (* Used by TSignalFilter for filter definition. Structure used by the TSignalFilter component to define the filter parameters of multiband filters. *) TBandFilterRecord = class public (* Defines the start of the pass band. *) BandStart: double; (* Defines the end of the pass band. *) BandEnd: double; (* Defines the gain of the band. If it is bigger of equal to one it is a passband. If it is zero it is a stopband. *) Gain: double; (*AttRipple is the required amplitude ripple across the frequency band.*) AttRipple: double; (* Contains the achieved amplitude ripple across the frequency band after the filter has been computed. *) Achieved: double; (* If checked is True, the frequency band should be included in the filter design process. *) Checked: boolean; constructor Create; virtual; (* Assign all fields from Src. *) procedure Assign(Src: TBandFilterRecord); overload; (* Assign all fields from Src. *) procedure Assign(const Src: TBandFilterRecordDouble); overload; (* Assign all fields to Dst. *) procedure AssignTo(var Dst: TBandFilterRecordDouble); overload; end; (* Array of TBandFilterRecords. Dynamic array of TBandFilterRecords. *) TBandFilterRecordArray = array of TBandFilterRecordDouble; (* Defines possible comb filter design errors. Detects filter errors, when designing filters and specifying filter bands. *) TFilterError = ( (*Filter was designed successfully.*) ferNone, (*At least two bands are required for a lowpass/highpass/bandstop/bandpass filter.*) ferNeedTwoBands, (*You can not have two consecutive stop bands, but you can have two consecutive pass bands.*) ferConsecutiveStopBands, (*Frequency band specification is invalid.*) ferInvalidBand, (*Pass/stop frequency bands are overlapping. You need some empty space between two bands for the transition region. Very narrow transition regions result in filters with long time delays and long computation times.*) ferOverlappingBands, (*Frequency band exceeds the frequency range.*) ferRangeError, (*Invalid value of Gain or Ripple. Gain can be 1, >1, or 0. Ripple must smaller then 1 and bigger then zero.*) ferGainRipple); TSignalFilter = class; (* Stores digital filter design specification. A list of frequency bands used for digital filter definition. The list items are owned by the list. *) TBandFilterList = class(TStreamedList) strict private function GetItems(Index: integer): TBandFilterRecord; procedure SetItems(Index: integer; const Value: TBandFilterRecord); procedure WriteArrayOfRecords(var Src: TBandFilterRecordArray; Dst: TStream; Len: integer); overload; procedure ReadArrayOfRecords(Src: TStream; var Dst: TBandFilterRecordArray; Len: integer); overload; strict protected FSignalFilter: TSignalFilter; public (* Default array property allows access to individual list items. *) property Item[Index: integer]: TBandFilterRecord read GetItems write SetItems; default; (* Add a new band definition. Object is owned by the list and will be deleted/freed when the deleted from the list. *) function Add(const Item: TBandFilterRecord): integer; (* Delete all frequency bands which have overlapping borders. *) function DeleteOverlappingBands: boolean; (* Save the list to stream. *) procedure SaveToStream(Dst: TStream); override; (* Load the list from stream. *) procedure LoadFromStream(Src: TStream); override; (* Check the validity of the filter and return the error as a result. BandIndex will contain the index of the invalid band on exit. *) function CheckFilter(out BandIndex: integer): TFilterError; (* Sort the frequency bands by frequency. *) procedure Sort; (* Create the list and pass a valid TSignalFilter object as the owner. *) constructor Create(AOwner: TSignalFilter); end; (* Manages a list of filter definitions. Manages a list of filter definitions. *) TFilterList = class(TStreamTemplates) strict private function GetItems(Index: Integer): TBandFilterRecord; procedure SetItems(Index: Integer; const Value: TBandFilterRecord); procedure SetTemplate(const Value: TBandFilterList); function GetTemplate: TBandFilterList; strict protected function GetStreamedTemplate: TStreamedList; override; public (* Default array property allows you to access individual list items. *) property Items[Index: Integer]: TBandFilterRecord read GetItems write SetItems; default; (*Create the list and pass TSpectrumBandsAnalyzer component as the owner. *) constructor Create(AOwner: TSignalFilter); published (* Holds a list of objects. All changes made to this list of objects are preserved, when you change TemplateName or TemplateIndex. *) property Template: TBandFilterList read GetTemplate write SetTemplate; end; (* Defines method used for FIR filter design. Defines the methods avaliable to design a FIR filter. For run time filter design the windowed method is recommended, because the resulting filters are more likely to be valid filters. *) TFirFilterMethod = ( (*A window based FIR filter will be designed.*) fimWindow, (*The remez exchange optimal fir filter design algorithm will be used.*) fimParksMcClellan); (* Defines filter response type. *) TResponseType = ( (*A unit impulse response equals to no filter.*) rstUnit, (*Finite impulse response filter.*) rstFIR, (*FIR based differentiator.*) rstDifferentiator, (*FIR based double differentiator.*) rstDoubleDifferentiator, (*FIR based integrator.*) rstIntegrator, (*FIR based double integrator.*) rstDoubleIntegrator, (*90 degree phase shift all-pass FIR.*) rstHilbert, (*Infinite Impulse Response filter.*) rstIIR, (*Savitzky-Golay FIR.*)rstSavGolay, (*Moving average FIR.*) rstMovingAverage, (*Non-linear median filter. Requires Taps.FloatPrecision to be set.*) rstMedian, (*Moving average based envelope detector (FIR).*) rstEnvelope, (*Wavelet response.*) rstWavelet, (*Delay by integer number of samples. Requires Taps.FloatPrecision to be set.*) rstIntegerDelay, (*Fractional delay filter.*) rstFractionalDelay ); (* Specifies the form in which the IIR filter can be returned. *) TIirResultType = ( (*Numerator and denumerator.*) irtNumDen, (*Zero pole form.*) irtZeroPole, (*State space form.*) irtStateSpace ); (* Apply digital filter to the input signal. Use this component to filter a signal, select from a large range of different digital filters and maintain a lists of predefined filters. TSignalFilterDialog is the component editor for this object. *) TSignalFilter = class(TSignal) strict private FFirLength: integer; FUpSample: integer; FDownSample: integer; FUpDelay: integer; FDownDelay: integer; FTaps: TVec; FExternalTaps: boolean; FNormalizedFrequency: Double; FFilters: TFilterList; FUseNormalizedFrequency: boolean; FFirMethod: TFirFilterMethod; FWindow: TSignalWindowType; FResponse: TResponseType; FBeta: Double; FGridDensity: integer; FIgnoreAtt: boolean; FPrepared: boolean; FOrder: integer; FIirMethod: TIirFilterMethod; FAutoFilterOrder: boolean; FWaveletP1: integer; FWaveletP2: integer; FWavelet: TWaveletType; FWaveletDecomp: TWaveletDecomp; FIIRFrequencyTransform: TIirFrequencyTransform; FScaleFactor: Double; FFractionalDelay: double; FFractionalDesignSpec: TFractionalImpulse; FFracDelay: double; procedure SetFractionalDesignSpec(const Value: TFractionalImpulse); procedure SetFractionalDelay(const Value: double); procedure SetFirLength(const Value: integer); procedure SetScaleFactor(const Value: Double); procedure SetDownSample(const Value: integer); procedure SetUpSample(const Value: integer); procedure SetDownDelay(const Value: integer); procedure SetUpDelay(const Value: integer); procedure SetNormalizedFrequency(const Value: Double); procedure SetFilters(const Value: TFilterList); procedure SetUseNormalizedFrequency(const Value: boolean); procedure SetFirMethod(const Value: TFirFilterMethod); procedure SetWindow(const Value: TSignalWindowType); procedure SetResponse(const Value: TResponseType); procedure SetBeta(const Value: Double); procedure SetGridDensity(const Value: integer); procedure SetIgnoreAtt(const Value: boolean); procedure SetPrepared(const Value: boolean); procedure SetTaps(const Value: TVec); procedure SetOrder(const Value: integer); procedure ComputeWindowFilter(FS,W1,W2,W3,W4: Double; FilterType: TFilterType; Gain,r: Double; SectionTaps: TVec); procedure ComputeIirFilter(ResultType: TIirResultType; const aResult: array of TObject; out aConst: Double); procedure ComputeParksMcClellan; procedure ComputeWindowOrIIr; procedure SetIirMethod(const Value: TIirFilterMethod); procedure SetAutoFilterOrder(const Value: boolean); procedure SetWavelet(const Value: TWaveletType); procedure SetWaveletP1(const Value: integer); procedure SetWaveletP2(const Value: integer); procedure SetWaveletDecomp(const Value: TWaveletDecomp); procedure ComputeParksHilbert; procedure ComputeWindowHilbert; procedure ComputeParksDiff; procedure ComputeWindowDiff; procedure ComputeParksDiff2; procedure ComputeParksIntegrator; procedure ComputeParksIntegrator2; procedure SetIIRFrequencyTransform(const Value: TIirFrequencyTransform); protected class function EditorClass: string; override; strict protected procedure ApplyChanges; function InternalUpdate: TPipeState; override; procedure InternalParamUpdate(Sender: TObject);override; public (* The record holds the initialized FIR filter ready to be passed to FirFilter routine. *) FIRState: TFirState; (* The record holds the initialized IIR filter ready to be passed to FirFilter routine. *) IIRState: TIirState; (* The record holds the initialized Median filter ready to be passed to MedianFilter routine. *) MedianState: TMedianState; DelayFilterState: TDelayFilterState; (* Same as setting Prepared to false. Reinitializes the delay lines. *) procedure Reset; override; (* Returns true, if the filter has been succesfully initialized. Set Prepared to False to request reinitialization of the filter. *) property Prepared: boolean read FPrepared write SetPrepared; (* Initialize streaming filter with Taps. Return True if succesfull. This method is called by the Prepare method. *) function Init: boolean; (* Prepare the defined filter for streaming. *) procedure Prepare; (* Filter a single sample. *) function ProcessSample(Sample: Double): Double; overload; (* Filter a single sample. *) function ProcessSample(const Sample: TCplx): TCplx; overload; (* Filter data in Src and place the result in Dst. *) procedure Process(Src,Dst: TVec); (* Save the component state to stream. *) procedure SaveToStream(Stream: TStream); override; (* Load the component from stream. *) procedure LoadFromStream(Stream: TStream); override; procedure Assign(Source: TPersistent); override; constructor Create(AOwner:TComponent); override; destructor Destroy; override; (* Returns the currently designed IIR filter in state space form. If the current design is not an IIR filter, the function returns false. For this call to return True, the IIRFrequencyTransform must be ftStateSpaceAnalog. *) function StateSpaceIIR(a: TMtx; b,c: TVec; d: Double): boolean; (* Returns the currently designed IIR filter in zero pole form. If the current design is not an IIR filter, the function returns false. *) function ZeroPoleIIR(z,p: TVec; k: Double): boolean; (* Returns the currently designed IIR filter in zero pole form. If the current design is not an IIR filter, the function returns false. *) function TransferFunIIR(num,den: TVec): boolean; published property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; (* Holds a list of defined filters. *) property Filters: TFilterList read FFilters write SetFilters; (* Holds the taps for the filter. This taps can be FIR or IIR type. If taps are from an IIR filter, they are stored as second order sections interleaved like this: B0[0], B0[1], B0[2], A0[0], A0[1], A0[2], B1[0], B1[1], B1[2], A1[0], A1[1], A1[2],... *) property Taps: TVec read FTaps write SetTaps; (* The upsample factor used by the multi-rate FIR filter. *) property UpSample: integer read FUpSample write SetUpSample default 1; (* The phase delay (must be less then UpSample) used by the multi-rate FIR filter. *) property UpDelay: integer read FUpDelay write SetUpDelay default 0; (* The downsample factor used by the multi-rate FIR filter. *) property DownSample: integer read FDownSample write SetDownSample default 1; (* The phase delay (must be less then DownSample) used by the multi-rate FIR filter. *) property DownDelay: integer read FDownDelay write SetDownDelay default 0; (* Set this property to True, if you have defined your own filter response and have placed the taps in the Taps property. Be sure to define the correct Response, to match the type of your filter. (Iir of Fir) When changing this property, the component has to be notified when the taps have been changed. Set the Prepared property to False or call the Init method after changing the values of taps. *) property ExternalTaps: boolean read FExternalTaps write FExternalTaps default false ; (* Specifies the FIR filter design method to be used. *) property FirMethod: TFirFilterMethod read FFirMethod write SetFirMethod default fimWindow ; (* Specifies the IIR filter design method to be used. *) property IirMethod: TIirFilterMethod read FIirMethod write SetIirMethod default fimButter ; (* Specifies the IIR frequency transformation method to use. *) property IIRFrequencyTransform: TIirFrequencyTransform read FIIRFrequencyTransform write SetIIRFrequencyTransform default ftStateSpaceAnalog ; (* Specifies the window type to be used when designing windowed FIR filters. *) property Window: TSignalWindowType read FWindow write SetWindow default wtKaiser ; (* Specifies the type of the filter, to be designed. *) property Response: TResponseType read FResponse write SetResponse default rstUnit ; (* Beta is the parameter for the Kaiser window. Kaiser window is used to design a FIR filter when Window property is set to wtKaiser and FirMethod property is set to to fimWindowed. The value of this property is used only, if IgnoreAtt is set to True. If IgnoreAtt is False, the beta parameter is estimated from the requested stopband ripple. *) property Beta: Double read FBeta write SetBeta; (* Grid density used by the remez exchange algorithm for optimal FIR filter design. *) property GridDensity: integer read FGridDensity write SetGridDensity default 16; (* Defines the order of the IIR filters, if AutoFilterOrder is false. The order parameter is used also by the Savitzky Golay polynomial FIR filter. *) property Order: integer read FOrder write SetOrder default 4; (* The value of the normalized frequency used when defining multiband filters. Normalized frequency is usually 1 or 2. The Nyquist frequency is then 0.5 or 1. *) property NormalizedFrequency: Double read FNormalizedFrequency write SetNormalizedFrequency; (* True, if you want to normalize the frequency of the defined bands. Frequency normalized filters will not have its pass/cuttoff frequencies defined absolutely in Hz, but rather as a fraction of the sampling frequency. *) property UseNormalizedFrequency: boolean read FUseNormalizedFrequency write SetUseNormalizedFrequency default True ; (* If True, the Order/FirLength of the IIR/FIR filters will be automatically estimed. *) property AutoFilterOrder: boolean read FAutoFilterOrder write SetAutoFilterOrder default True ; (* Defines the length of the FIR/Median filter, if the AutoFilterOrder property is false. For windowed FIR filters other then Kaiser, the length can not be estimated. *) property FirLength: integer read FFirLength write SetFirLength default 1; (* Set it to True, if you want to explicitelly define the Beta parameter for the Kaiser window, when designing a FIR filter windowed with a Kaiser window. *) property IgnoreAtt: boolean read FIgnoreAtt write SetIgnoreAtt default False ; (* Set the wavelet to be used for wavelete decomposition. *) property Wavelet: TWaveletType read FWavelet write SetWavelet default wtHaar ; (* Defines the first wavelet parameter (if required). *) property WaveletP1: integer read FWaveletP1 write SetWaveletP1 default 1; (* Defines the second wavelet parameter (if required.) *) property WaveletP2: integer read FWaveletP2 write SetWaveletP2 default 1; (* Selects the result of wavelet decomposition. *) property WaveletDecomp: TWaveletDecomp read FWaveletDecomp write SetWaveletDecomp default wtApproximation ; (* Defines the factor by which to multiply the input signal. The purpose of this parameter is to save a separate scale operation after the filtering. *) property ScaleFactor: Double read FScaleFactor write SetScaleFactor; (* Defines the combined fractional delay of the input signal (integer + fraction). Uses fractional FIR filter with Kaiser window. This filter is combined with Integer sample delay filter to achieve desired combined delay. The fractional filter will always also include some integer sample delay. How much depends on the filter specification defined with FractionaDesignSpec property. If the specified (integer part) of the delay is too short for the specified FractionalDesignSpec, an exception will be raised. Considered only, when Response is rstFractionalDelay. *) property FractionalDelay: double read FFractionalDelay write SetFractionalDelay; (* Defines the FIR filter properties used for fractional delay filtering. Used only, when Response is rstFractionalDelay. *) property FractionalDesignSpec: TFractionalImpulse read FFractionalDesignSpec write SetFractionalDesignSpec default falp_100dB; end; (* Specifies signal types, which can be generated by TSignalGenerator. Defines the type of the signal you want to generate. *) TFuncSignalType = ( (*P1-frequency, P2-amplitude, P3-phase.*) funSine, (*No parameters. Used for filter design.*) funUnitImpulse, (*Defined with duty cycles: High time - P1, Raise time - P3, Fall time - P4, Amplitude of High - P5.*) funImpulse, (*P1-frequency, P2-amplitude, P3-phase; P4-asymetry.*) funTriangle, (*P1-freqyency, P2-phase, P3-amplitude, P4-DC offset.*) funSquare, (*P1-mean, P2-stddev.*) funRandGauss, (*P1-Low, P2-high.*) funRandUniform, (*P1-starting frequency, P3 - starting amplitude, P5-ending frequency, P6-ending amplitude, P7- period [s].*) funChirp, (*P1-constant value, P2-imaginary part, if constant is complex.*) funConstant, (*Used to reference the Inputs property of the TSignalFilter component.*) funInput0, (*Used to reference the Inputs property of the TSignalFilter component.*) funInput1, (*Used to reference the Inputs property of the TSignalFilter component.*) funInput2, (*Used to reference the Inputs property of the TSignalFilter component.*) funInput3, (*Used to reference the Inputs property of the TSignalFilter component.*) funInput4, (*Used to reference the Inputs property of the TSignalFilter component.*) funInput5, (*Used to reference the Inputs property of the TSignalFilter component.*) funInput6, (*Used to reference the Inputs property of the TSignalFilter component.*) funInput7, (*No signal.*) funNone ); (* Defines the operator types used by expression parser. Defines the operator type for TSignalGenerator. *) TOperSignalType = ( (*Addition.*) opAdd, (*Subtraction.*) opSub, (*Multiplication.*) opMul, (*Division.*) opDiv, (*Sine function*) opSin, (*Sine function*) opCos, (*Tangens function*) opTan, (*Exponent function.*) opExp, (*Natural logarithm.*) opLn, (*Logarithm with base 10.*) opLog, (*Rectification.*) opAbs, (*Round to the closest integer.*) opRound, (*Truncate towards zero.*) opTrunc, (*P1-lower clip limit, P2-upper clip limit.*) opLimit, (*No operation.*) opNon ); (* Defines the type of the operand for expression parser. Defines the type of the operand for the expression parser of TSignalGenerator component. *) TOpType = ( (*The operand is a function.*)optFunction, (*The operand is an operator.*) optOperator ); TCToneState = class State: TToneState; end; TCTriangleState = class State: TTriangleState; end; TCSquareState = class State: TSquareToneState; end; TFuncSignalRecordDouble = packed record Checked: byte; (* Specifies if operand is a function or operator. *) OpType: byte; (* Specifies operand if function type. *) Func: byte; (* Specifies operand if operator type. *) Oper: byte; (* True, if the operation is complex. *) Cplx: byte; (* Value of the 1st parameter. *) P1: double; (* Value of the 1nd parameter. *) P2: double; (* Value of the 3rd parameter. *) P3: double; (* Value of the 4th parameter. *) P4: double; (* Value of the 5th parameter. *) P5: double; (* Value of the 6th parameter. *) P6: double; (* Value of the 7th parameter. *) P7: double; Continuous: byte; ResetContinuous: byte; Data: Int32; Pointer: Int32; Counter: Int32; Done: byte; end; TFuncSignalRecordDoubleArray = array of TFuncSignalRecordDouble; (* Expression parser record definition. This record holds the definition of a formula element. Formula element can be either a function or an operator. (OpType). It can be checked or unchecked. Complex or real.. *) TFuncSignalRecord = class (* If True, this element will be used in the function evaluation. *) Checked: boolean; (* Defines, if this element is a function or an operator. *) OpType: TopType; (* Defines which function to generate, if OpType is optFunction.. *) Func: TFuncSignalType; (* Specifies which operator to apply, if OpType is optOperator. *) Oper: TOperSignalType; (* If True, the operator or function will have a complex result. *) Cplx: boolean; (* Function parameter. *) P1: double; (* Function parameter. *) P2: double; (* Function parameter. *) P3: double; (* Function parameter. *) P4: double; (* Function parameter. *) P5: double; (* Function parameter. *) P6: double; (* Function parameter. *) P7: double; (* True, if the function is to be streamed. *) Continuous: boolean; (* If True, the function will be reset to its initial state on its next evaluation. *) ResetContinuous: boolean; (* A pointer to the TVec object type holding the result. *) Data: TVec; (* A pointer to signal generation specific structures. *) Pointer: TObject; (* A "Time counter" used by some signal generation functions. *) Counter: Int64; (* Internal parameter. *) Done: boolean; (* Assign all fields from Src. *) procedure Assign(Src: TFuncSignalRecord); overload; (* Assign all fields from Src. *) procedure Assign(const Src: TFuncSignalRecordDouble); overload; (* Assign all fields to Dst. *) procedure AssignTo(var Dst: TFuncSignalRecordDouble); overload; end; (* Holds expresion definition. The list holds the formula to be computed in postfix notation. (HP calculator style). The elements of the list are owned by the list. *) TFuncSignalList = class(TStreamedList) strict private function GetItems(Index: integer): TFuncSignalRecord; procedure SetItems(Index: integer; const Value: TFuncSignalRecord); procedure WriteArrayOfRecords(var Src: TFuncSignalRecordDoubleArray; Dst: TStream; Len: integer); overload; procedure ReadArrayOfRecords(Src: TStream; var Dst: TFuncSignalRecordDoubleArray; Len: integer); overload; public constructor Create(AOwnsObjects: boolean); overload; (* Add a new formula item definition and allocate the record internally. *) function Add(const Item: TFuncSignalRecord): integer; (* Save the list to stream. *) procedure SaveToStream(Dst: TStream); override; (* Load the list from stream. *) procedure LoadFromStream(Src: TStream); override; (* Default array property allows access to individual list items. *) property Item[Index: integer]: TFuncSignalRecord read GetItems write SetItems; default; end; (* Triggered when reseting function phase. Triggers when the function's phase is reset to its initial value. *) TOnResync = procedure(Sender: TObject; LineIndex: integer) of object; (* Manages a list of different sound formula definitions. Manages a list of different sound definitions. *) TSignalSounds = class(TStreamTemplates) strict private function GetItems(Index: Integer): TFuncSignalRecord; procedure SetItems(Index: Integer; const Value: TFuncSignalRecord); function GetTemplate: TFuncSignalList; strict protected function GetStreamedTemplate: TStreamedList; override; public constructor Create(AOwner: TComponent); override; (* Holds a list of TFuncSignalRecord objects. All changes made to this list of objects are preserved, when you change TemplateName or TemplateIndex. *) property Template: TFuncSignalList read GetTemplate; (* Default array property allows you to access individual list items. *) property Items[Index: Integer]: TFuncSignalRecord read GetItems write SetItems; default; end; (* Generates signals in real time. Use this component as a function parser for signal generator applications. The component features a stack based vectorized function evaluator. This component allows you to generate very complex signals ten to hundred times times faster then a simple function evaluator. The component has a component editor TSignalGeneratorDialog. *) TSignalGenerator = class(TSignal) strict private stack: TFuncSignalList; a: array [0..20] of TFuncSignalRecord; FOnResync: TOnResync; FSounds: TSignalSounds; FPrepared: boolean; FComputeMessage: string; procedure ResetStack; procedure InitFunc(aj: TFuncSignalRecord); procedure FillValues(aj: TFuncSignalRecord); procedure ResetFuncs(aj: TFuncSignalRecord); procedure Compute; procedure SetOnResync(const Value: TOnResync); procedure SetSounds(const Value: TSignalSounds); procedure SetPrepared(const Value: boolean); procedure SetComputeMessage(const Value: string); protected class function EditorClass: string; override; strict protected function InternalUpdate: TPipeState; override; procedure InternalParamUpdate(Sender: TObject);override; procedure UpdateSetLength(Sender: TObject); override; public (* Contains any error messages encountered while trying to evalute the functions. *) property ComputeMessage: string read FComputeMessage write SetComputeMessage; (* Returns true, if all function generators are initialized. *) property Prepared: boolean read FPrepared write SetPrepared; (* Update the function generator with the new parameters from the sounds property item at position i. *) procedure PartialUpdate(i: integer); (* Resets all functions to its initial phase. *) procedure ResyncAll; (* Resets the function at TemplatLineIndex position to its initial phase. *) procedure ResyncFunction(TemplateLineIndex: integer); (* Initialize the function (sound) to be generated. *) procedure Prepare; (* Initialize the function (sound) to be generated. *) procedure Reset; override; constructor Create(AOwner:TComponent); override; destructor Destroy; override; procedure SaveToStream(Stream: TStream); override; procedure LoadFromStream(Stream: TStream); override; published property Inputs: TSignalCollection read GetInputs1 write SetInputs1 stored InputsStored; (* Signal generator sound definition object. *) property Sounds: TSignalSounds read FSounds write SetSounds; (* Event triggered when the user calls the ResyncFunction method. *) property OnResync: TOnResync read FOnResync write SetOnResync; end; (* Used for multi-rate signal filtering. Used by TSignalMultiRate component to enable multi-stage filtering. Multi-stage filtering can be faster then a single stage filter, if you need very sharp cutoff frequencies. Data contains the filtered data on the current stage and State holds the filter definitions for the current filter stage. *) TTapsStages = record Data: TVec; State: TFirState; end; (* Abstract class for multi-rate digital filters. Abstract class for multi-stage, multi-rate decimation and interpolation FIR filters. An in depth explanation of the methods used can be found in [1] Chapter 7.3 (Digital resampling). References: [1] "Understanding digital signal processing.", Richard G. Lyons, Prentice-Hall, 2001. *) TSignalMultiRate = class(TSignal) strict protected TempSignal: TSignal; TapsStages: array [0..MaxRateConversionPowerFactor-1] of TTapsStages; Stages: integer; FInitialized: boolean; FExternalTaps: boolean; FOnGetFilterTaps: TNotifyEvent; FFirInitialized: boolean; FFilterDelay: Double; FScaleFactor: Double; FAudioSignal: boolean; FHalfBand: boolean; procedure SetAudioSignal(const Value: boolean); procedure SetInitialized(const Value: boolean); procedure SetExternalTaps(const Value: boolean); procedure SetOnGetFilterTaps(const Value: TNotifyEvent); procedure SetFirInitialized(const Value: boolean); procedure SetFilterDelay(const Value: Double); procedure SetScaleFactor(const Value: Double); function GetFilterDelay: Double; strict protected FFactor: integer; FRipple: Double; FTransBW: Double; procedure AllocFirBuffers; virtual; abstract; procedure FreeFirBuffers; virtual; abstract; procedure Initialize; virtual; procedure SetRipple(const Value: Double); procedure SetTransBW(const Value: Double); procedure SetFactor(const Value: integer); virtual; procedure SetHalfBand(const Value: boolean); virtual; property FirInitialized: boolean read FFirInitialized write SetFirInitialized; public (* Contains the FIR filter taps for all filter stages except for the last. *) FirTaps: TVec; (* Contains the FIR filter taps for the last filter stage in case of decimation and for the first stage filter in case of the interpolator. When the sampling frequency is increased by twice in case of the interpolation, the signal has one zero inserted after each sample. This zero stuffing introduces high frequency noise which is exactly the mirror of the low frequencies. To filter out this high frequencies a lowpass filter is needed. If the sampling frequency is 2Hz, then the lowpass filter should pass everything below 0.5Hz and stop everything above 0.5Hz. The TransBW property defines the width of the transition region of the lowpass filter centered on 0.5 Hz. The narrower the transition region, the longer the filter and more CPU demanding computation is required. *) FinalStageTaps: TVec; (* The same as setting Initialized to false. Resets all delay lines. *) procedure Reset; override; (* When set to false, the next call to the update method will reinitialize the FIR filters. This is usefull, when ExternalTaps is True. Do not reset the filter while streaming is under way. *) property Initialized: boolean read FInitialized write SetInitialized; (* Returns the filter delay in number of samples of the final sampling frequency. Read-only. *) property FilterDelay: Double read GetFilterDelay write SetFilterDelay; (* Filter Src and place the result in Dst. *) procedure Process(Src, Dst: TVec); virtual; constructor Create(AOwner: TComponent); override; destructor Destroy; override; (* FirTaps and FinalStageTaps do not hold valid filter taps until InitFilters has been called. The routine is called automatically before the first data block is filtered. *) procedure InitFilters; virtual; published (* Adjust filters for audio signal. When designing filters, the transition region can allow some aliasing to reduce the filter load in the last 5% of the frequency spectrum. From the analysis point of view, if the last 5% are just attenuated and/or also aliased plays no role. If the signal is an audio file, those 5% of aliasing would add some real audible distortion to the signal. If the AudioSignal processing is True, no aliasing will occure. *) property AudioSignal: boolean read FAudioSignal write SetAudioSignal default false ; (* Enables user specified filter. If set to false, the user is required to set the filter taps stored in FirTaps and FinalStageTaps fields. This property is True by default. The filters taps should be set before the first call to the Update method is made. Alternatively the OnGetFilterTaps event is also triggered, when the filter taps are needed. The component has to be notified to use new set of taps, once they were changed. Set the Initialized property to false to notify the component that new taps should be used. *) property ExternalTaps: boolean read FExternalTaps write SetExternalTaps default False ; property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; (* Triggered when ExternalTaps is false. User should set values of Taps and FinalStageTaps vectors. *) property OnGetFilterTaps: TNotifyEvent read FOnGetFilterTaps write SetOnGetFilterTaps; (* Defines the factor by which the signal will be scaled. This scaling operation is "free" since it does not require additional CPU power, but a change to the scale factor requires reinitialization of the filter bank. If ExternalTaps are used or the OnGetFilterTaps event, the value of the ScaleFactor is ignored. ScaleFactor can not be zero. *) property ScaleFactor: Double read FScaleFactor write SetScaleFactor; (* Filters out upper half of the bandwidth. In case of decimation, the last filter will filter upper half of the result from 0.5 to 1, if the sampling frequency is 2. In case of interpolation, the first filter will filter out the upper half from 0.5 to 1. *) property HalfBand: boolean read FHalfBand write SetHalfBand default false ; (* Maximum ripple allowed in the passband and in the stopband. *) property Ripple: Double read FRipple write SetRipple; (* Decimation/Interpolation factor. *) property Factor: integer read FFactor write SetFactor default 2; (* Defines the width of the transition band of the FIR filter used for interpolation/decimation. The normalized sampling frequency is 2. This parameter applies only to the last stage of the decimation and the first stage of interpolation. *) property TransBW: Double read FTransBW write SetTransBW; end; (* Performs signal interpolation. Signal interpolation component. Use this component to raise the sampling frequency of the signal by power of two integer factor using fast multi-rate, multi-stage filters (See [1]). The CPU load increases nearly linear with the interpolation factor. References: *) TSignalInterpolator = class(TSignalMultiRate) strict protected procedure FreeFirBuffers; override; procedure AllocFirBuffers; override; function InternalUpdate: TPipeState ; override; public constructor Create(AOwner: TComponent); override; published end; (* Performs signal decimation. Signal decimation component. Use this component to lower the sampling frequency of the signal by powers of two using fast multi-rate, multi-stage filters (See [1]). The component also features a high quality envelope detection, which computes the absolute value of the source signal before the decimation. Decimation factors can be as high as 1024 or 4096. This makes it possible to detect very low frequencies with the envelope detector. The CPU load increases by only about twice when the decimation factor is increased from 2 to any number (can be 1024). References: *) TSignalDecimator = class(TSignalMultiRate) strict protected FAbsVec: TVec; FEnvelopeFactor: integer; FLastStageFilterOnly: boolean; procedure SetLastStageFilterOnly(const Value: boolean); strict protected procedure ApplyDecimation(Src: TVec); virtual; procedure FreeFirBuffers; override; procedure AllocFirBuffers; override; function InternalUpdate: TPipeState; override; procedure SetFactor(const Value: integer); override; procedure SetEnvelopeFactor(const Value: integer); virtual; procedure SetHalfBand(const Value: boolean); override; public destructor Destroy; override; constructor Create(AOwner: TComponent); override; published (* Defines the filter stage at which to apply envelope detection. If envelopeFactor is 0 then envelope detection is left out. If EnvelopeFactor is 1, then envelope detection is performed before the first lowpass filter is applied. If envelopeFactor is 2, the envelope detection is applied after the first lowpass, and so on... *) property EnvelopeFactor: integer read FEnvelopeFactor write SetEnvelopeFactor default 0; (* The component lowers the sampling frequency in steps by a factor of two. Before each stage a lowpass antialiasing filter is applied. If this property is True, the final stage lowpass filter will be applied, but the signal will not be downcoverted by a further factor of 2. *) property LastStageFilterOnly: boolean read FLastStageFilterOnly write SetLastStageFilterOnly default false ; end; (* Changes sampling frequency by a rational factor. The sampling frequency of the Input is changed by a rational factor by using bandlimited interpolation. An example is resampling of an audio signal with 48kHz to 44kHz. The interpolation quality can achieve SNR (signal to noise ratio) of up to 180dB in double precision. This component does not feature antialising filters. More about backgrounds of bandlimited interpolation can be found in [1]. Note: Data.Length may vary from iteration to iteration. If the input has integer number of samples and the rate is changed by a rational factor the output will have a rational number of samples (in general). For some pairs of input buffer length and resampling factors (least common nominator) the output will also have fixed length. If the frequency is changed from 8000Hz to 11025Hz, the input buffer must have a length which is a multiple of 320 and the output will be a multiple of 441. (8000/25 = 320, 11025/25 = 441) References: [1] Bandlimited interpolation *) TBandlimitedInterpolator = class(TSignal) strict private FilterPrepared: boolean; FractionalDelay: Double; Buffer: TVec; FractionalTaps: TVec; Breaks: TVec; Coeffs: TMtx; FResampleFactor: Double; FFilterStart: Double; FImpulseFactor: integer; FFilterStop: Double; FRipple: Double; FAutoFilterLength: boolean; FFilterLength: integer; FScaleFactor: Double; FFilterDelay: Double; procedure SetFilterStart(const Value: Double); procedure SetFilterStop(const Value: Double); procedure SetImpulseFactor(const Value: integer); procedure SetResampleFactor(const Value: Double); procedure SetRipple(const Value: Double); procedure SetAutoFilterLength(const Value: boolean); procedure Linear1DSetup(YTaps: TVec); procedure SetScaleFactor(const Value: Double); function GetFilterDelay: Double; strict protected Taps: TVec; (* If True, the length of the interpolation filter will be computed and the FilterLength property will be set, when the PrepareFilter method is called. *) property AutoFilterLength: boolean read FAutoFilterLength write SetAutoFilterLength default false; (* Defines the starting frequency of the transition band of the FIR lowpass filter used for interpolation. *) property FilterStart: Double read FFilterStart write SetFilterStart; (* Defines the stop frequency of the transition band of the FIR lowpass filter used for interpolation. *) property FilterStop: Double read FFilterStop write SetFilterStop; (* Defines the interpolation accuracy of the fractional FIR impulse. *) property ImpulseFactor: integer read FImpulseFactor write SetImpulseFactor; function InternalUpdate: TPipeState; override; procedure SetFilterLength(const Value: integer); virtual; public (* Returns length of the filter. Read FilterLength after the PrepareFilter method was called, or after the first call the Update method was made to obtain the length of FIR filter used for interpolation. Filter length affects the following parameters: *the width of transition region of the lowpass filter. *the level of attenuation in the stopband of the lowpass filter. *the quality of interpolation. The filter must be long enough to meet the most strict specification. The automatic FIR length estimation takes in to account only the width of the transition region and the level of attenuation, but not the quality of interpolation. The longer the filter, the higher is the interpolation quality. The interpolation quality is also affected by the value of the ImpulseFactor property which defines the factor by which the FIR impulse response is oversampled (linear interpolation grid density) and the amount of allowed Ripple in the passband. Smaller ripple forces the edges of the impulse response quicker to zero thus making the "truncated" impulse response less sensitive to the assumption that taps are zero outside the given impulse response. Another important issue is the aliasing. If the resamplingFactor is smaller than 1 (the sampling frequency is being lowered), then the lowpass filter must attenuate all frequencies above the FS*ResamplingFactor frequency. (FS = original sampling frequency.) Usually it is best to use multirate FIR filters as antialiasing filters and then use this component only to apply interpolation where the interpolation filter is very short (7,9,11 or 13 taps). Such a setup can achieve SNR (Signal to noise ratio) up to 85dB. With more taps SNR of 180dB (double precision) can be achieved. The value of the allowed ripple which defines the beta parameter of the Kaiser window has a significant impact on the quality of the interpolation. If lowpass antialiasing filter is applied with a separate filter, then the lowpass interpolation filter can concentrate on the quality of interpolation only. *) property FilterLength: integer read FFilterLength write SetFilterLength; (* Sets the internal FilterPrepared flag to false. When the Update method is called the next time, it will call the PrepareFilter method first. *) procedure Reset; override; (* Returns the delay of the filter in number of samples of the final sampling frequency. *) property FilterDelay: Double read GetFilterDelay; (* Initializes the lowpass interpolation filter. *) procedure PrepareFilter; virtual; constructor Create(AOwner: TComponent); override; destructor Destroy; override; published (* Defines the ripple error of the anti-aliasing filters. The attenuation in dB can can be computed as: Att = -20*log10(Ripple). The ripple can fall in range between 0.001 (60dB) and 1E-8 (160dB) for double precision and between 0.001 and 0.0001 (80dB) for single precision. *) property Ripple: Double read FRipple write SetRipple; (* Specifies the factor by which to change the sampling frequency. The new sampling frequency is computed as: SamplingFrequency = Input.SamplingFrequency*ResampleFactor The recommendation is that the factor should take values only between 0.5 and 1. For values smaller (larger) TSignalDecimator (TSignalInterpolator) should be used as a preprocessor. It is the users responsibility to ensure antialiasing protection. All the signals entering (with normalized sampling frequency 2Hz) may not have frequencies above 0.5Hz and not above the ResampleFactor Hz. Usually this condition can be met by interpolating the signal first with the TSignalInterpolator component by a factor of 2. The resample factor can change and the filter bank will not be recalculated. *) property ResampleFactor: Double read FResampleFactor write SetResampleFactor; property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; (* Scaling factor can optionally be applied free of charge without raising the cost of the processing required. The scale factor can not be zero. *) property ScaleFactor: Double read FScaleFactor write SetScaleFactor; end; TVariableBandlimitedInterpolator = class(TBandlimitedInterpolator) strict private FResampleVector: TVec; ResampleBuffer: TVec; procedure SetResampleVector(const Value: TVec); strict protected function InternalUpdate: TPipeState; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; (* Specifies resample points as units of samples. *) property ResampleVector: TVec read FResampleVector write SetResampleVector; end; (* Changes the sampling frequency by any integer or rational factor. This component makes use of , and for fast (real time) rate conversion. The quality of the rate conversion depends upon the floating point precision. With single precision no more then 80dB SNR (Signal to noise ratio) can be obtained. Double precision allows SNR up to 180 dB. The limiting factor is the quality of the bandlimited interpolation. Bandlimited interpolation is used only, if the factor is not an integer power of two. Bandlimited interpolation with SNR of 80dB requires about 5x more CPU then simple power of two interpolation. Very high SNR up to 160dB may increase the required processing power by further 3x. (which may no longer be real time) If explicit multi-rate filtering is desired the TSignalFilter component should be used where the UpSample and DownSample factors and filter type can be defined explicitely. *) TSignalRateConverter = class(TSignal) strict protected Initialized: boolean; DownSample: TSignalDecimator; UpSample: TSignalInterpolator; FirstUpsample: TSignalInterpolator; BLSample: TBandlimitedInterpolator; FRipple: Double; FFactor: Double; FTransBW: Double; FAudioSignal: boolean; FScaleFactor: Double; FHalfBand: boolean; FFilterDelay: Double; procedure SetAudioSignal(const Value: boolean); procedure SetRipple(const Value: Double); procedure SetTransBW(const Value: Double); procedure SetScaleFactor(const Value: Double); function GetFilterDelay: Double; strict protected EpsR: Double; function InternalUpdate: TPipeState; override; procedure SetHalfBand(const Value: boolean); virtual; procedure SetFactor(const Value: Double); virtual; public (* Initializes filtering structures after Factor and other properties have been set. This function can be called optionally before the filtering the starts. The component will call it automatically, if it was not yet called, before the filtering will start. *) procedure Initialize; virtual; (* The bandwidth will be halved during downsampling. This property is applicable only during downsampling (Factor < 1). When the value is True, the resulting bandwidth will have the upper half filtered out, which can be usefull for furhter processing. The lowpass will be applied from BW/2 to BW, where BW is the bandwidth of the converted signal. The final sampling frequency will not be affected. *) property HalfBand: boolean read FHalfBand write SetHalfBand; (* Returns the filter delay in number of samples of the final sampling frequency. Read-only. *) property FilterDelay: Double read GetFilterDelay; (* Reset all delay lines. *) procedure Reset; override; constructor Create(AOwner: TComponent); override; destructor Destroy; override; published (* Enable when the signal processed is Audio signal. When designing filters, the transition region can allow some aliasing to reduce the filter load in the last 5% of the frequency spectrum. From the analysis point of view, if the last 5% are just attenuated and/or also aliased plays no role. If the signal is an audio file, those 5% of aliasing could add some real audible distortion to the signal. If the AudioSignal processing is True, no aliasing will occure, but the transition bandwidth will double to 10% of the total bandwidth. *) property AudioSignal: boolean read FAudioSignal write SetAudioSignal default True ; property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; (* Defines the ripple of the passband and of the stopband of the FIR lowpass filter used for interpolation. *) property Ripple: Double read FRipple write SetRipple; (* Factor by which to change the sampling frequency of the signal connected to the Input property. *) property Factor: Double read FFactor write SetFactor; (* Defines the width of the transition band of the FIR filter used for interpolation/decimation. The normalized sampling frequency is 2. This parameter applies only to the last stage of the decimation and the first stage of interpolation. If TransBW is 0.1 then the last 10% of the bandwidth of the original will be aliased on the right edge. *) property TransBW: Double read FTransBW write SetTransBW; (* Additional scaling factor which is applied without extra performance cost. Scale factor can not be 0. *) property ScaleFactor: Double read FScaleFactor write SetScaleFactor; end; (* Demodulates a signal. Use the component to bring a prespecified frequency band down to DC. The process is also called amplitude demodulation. By lowering the sampling frequency using high speed decimation, very narrow frequency bands can be extracted in real time. The resulting signal can then be used for high-resolution (mili/micro Hertz). frequency analysis of the obtained frequency band. This process is also referred to as zoom-spectrum. *) TSignalDemodulator = class(TSignalRateConverter) strict protected FSubBandWidth: Double; FSubBandFrequency: Double; DownToneState: TToneState; UpToneState: TToneState; ToneData: TVec; WorkSignal: TSignal; WorkConverter: TSignalRateConverter; procedure SetSubBandWidth(const Value: Double); procedure SetSubBandFrequency(const Value: Double); strict protected procedure ComputeFactor; virtual; function InternalUpdate: TPipeState; override; procedure SetFactor(const Value: Double); override; procedure SetHalfBand(const Value: boolean); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Initialize; override; published (* Defines the starting frequency of the frequency band to be extracted. The normalized sampling frequency is 1 and this value can be between 0 and 0.5. *) property SubBandFrequency: Double read FSubBandFrequency write SetSubBandFrequency; (* Defines the width of the frequency band to be demodulated. Demodulation will move the frequency band of interest down to the DC. The normalized sampling frequency is 1 and this value can be between 0 and 0.5. SubBandFrequency + SubBandWidth may not exceed 0.5 (FS/2). *) property SubBandWidth: Double read FSubBandWidth write SetSubBandWidth; end; (* Modulates a signal. Use the component to move the signal to a specified carrier frequency. The process is also called modulation. By increasing the sampling frequency using high speed linear phase interpolation, narrow frequency bands can be moved to any carrier frequency in real time. This is the inverse procedure to the signal demodulator. The target sampling frequency and carrier frequency can be independently controlled. The procedure generates only one sided upper side band, to the right of the carrier. The target sampling frequency can be specified with the Factor property and the CarrierFrequency property defines the carrier frequency. Example: Signal.SamplingFrequency := 10; //in Hz SignalModulator.Input := Signal; SignalModulator.Factor := 12; SignalModulator.CarrierFrequency := 4.5; The output sampling frequency will be: 10*12 = 120Hz The output signal will have non-zero frequencies from 45 to 50Hz which will hold the information originally stored in the range from 0 to 5Hz. *) TSignalModulator = class(TSignalRateConverter) strict protected DownToneState: TToneState; UpToneState: TToneState; ToneData: TVec; WorkSignal: TSignal; WorkConverter: TSignalRateConverter; FCarrierFrequency: Double; procedure SetCarrierFrequency(const Value: Double); strict protected function InternalUpdate: TPipeState; override; procedure SetFactor(const Value: Double); override; procedure SetHalfBand(const Value: boolean); override; procedure UpdateToneState; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Initialize; override; published (* Specifies where to move the Input signal in the frequency domain. The unit for the CarrierFrequency is a multiple of the Input.SamplingFrequency. *) property CarrierFrequency: Double read FCarrierFrequency write SetCarrierFrequency; end; (* High speed narrow band linear phase bandpass filter. Use the component to apply a narrow band linear phase bandpass filter. Bandpass filters which would otherwise require 10 000 or 100 000 taps long impulse responses, can be applied at a small fraction of the processing cost. When the filter pass band width, is less than 25% of the total signal bandwidth it becomes meaningfull to use this component. For bandpass widths of less than 1%, the processing requires 10-100x less CPU than direct filter methods. *) TSignalBandpass = class(TSignalDemodulator) strict protected UpConverter: TSignalRateConverter; strict protected function InternalUpdate: TPipeState; override; procedure ComputeFactor; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Initialize; override; end; (* Variable frequency rate converter. Use the component to vary the sampling frequency as a function of time. The function is passed as a vector of sampling points specified at the sampling frequency of the input signal with RateVector property. The component uses a fixed filter bank and delivers aliasing free output, with linear phase and user definable attenuation of the lowpass filters and witdh of the FIR transition band in the final signal. Fixed filter bank allows for exceptional performance as it can take full advantage code vectorization. The filter bank is specified by defining the MaxFactor and MinFactor properties, which define the maximum and minimum factor by which the sampling frequency can change with the reference to the sampling frequency of the input signal. Within this range, the sampling frequency can then be freely adjusted from one sample to the next without danger of aliasing. The performance of the rate conversion process depends upon the MaxFactor/MinFactor where the processing load will increase nearly linearly with the increase of these factors. *) TSignalVariableRateConverter = class(TSignalRateConverter) strict protected intFilterDelay: integer; FRateVector: TVec; FSampleVector: TVec; FMaxFactor: Double; FMinFactor: Double; BLSample2: TVariableBandlimitedInterpolator; procedure SetRateVector(const Value: TVec); procedure SetMaxFactor(const Value: Double); procedure SetMinFactor(const Value: Double); strict protected function InternalUpdate: TPipeState; override; public procedure Initialize; override; (* Defines sample positions at which to resample the Input signal. To increase the sampling frequency by 2x this vector would contain values: 0, 0.5, 1, 1.5, 2... To decrease sampling frequency by 2x this vector would contain values: 0, 2, 4, 6, 8.... The sampling frequency can also vary from sample to sample: 0, 2, 4.5, 6.3, 8.... However, the maximum increase and maximum decrease factors have to be specified as parameters to MaxFactor and MinFactor properties. Attempting to sample the signal out of this range would cause aliasing in the signal. *) property RateVector: TVec read FRateVector write SetRateVector; constructor Create(AOwner: TComponent); override; destructor Destroy; override; published (* Specifies the maximum Factor up to which the rate converter can vary the sampling frequency, without introducing aliasing. The minimum value for the MaxFactor is 1. The value must be an integer and a power of two (2, 4, 8,...). *) property MaxFactor: Double read FMaxFactor write SetMaxFactor; (* Specifies the minimum Factor down to which the rate converter can vary the sampling, without introducing aliasing. The maximum value for the minFactor is limited to 1. The value must be an inverse of an integer with power of two (1/2, 1/4, 1/8 etc...). *) property MinFactor: Double read FMinFactor write SetMinFactor; end; (* Demodulates a signal. Uses a hilbert transformer filter to get 90 degrees phase shifted version of the original and then forms the envelope like this: envelope[i] = sqrt(sqr(X[i-HilbertDelay]) + sqr(Y[i])); This is an alternative way to detect the envelope in comparison to the method offered by . This approach is slower, but could result in significant noise reduction. *) TSignalEnvelopeDetector = class(TSignalDecimator) strict protected HilbertTaps: TVec; HilbertState: TFirState; DelayState: TDelayFilterState; FHilbertRipple: Double; FHilbertTransBW: Double; procedure SetHilbertRipple(const Value: Double); procedure SetHilbertTransBW(const Value: Double); strict protected procedure FreeFirBuffers; override; procedure AllocFirBuffers; override; function InternalUpdate: TPipeState; override; public procedure InitFilters; override; constructor Create(AOwner: TComponent); override; destructor Destroy; override; published (* Transition bandwidth of the type III Hilbert transformer normalized to a sampling frequency of 2 Hz. *) property HilbertTransBW: Double read FHilbertTransBW write SetHilbertTransBW; (* Passband ripple of the type III Hilbert transformer. *) property HilbertRipple: Double read FHilbertRipple write SetHilbertRipple; end; (* Defines signal buffering type. Defines how the data is managed within the buffer. *) TBufferType = ( (*Once the data is read, it is "deleted" from the buffer. This mode is usefull for filtering and signal processing.*) bftStreaming, (*The data resides in the buffer until new data comming in to the buffer pushes it out. This mode is usefull for signal analysis.*) bftScrolling, (*The buffer will be increasing until some user defined size is reached. The buffer must then be reset.*) bftStoring ); (* Keeps signal processing data pipe synchronized. Use the component to synchronize data processing pipe, where you need a large input buffer to perform filtering or a large scrolling buffer for signal analysis. The output data length is specified with the value of the Length property. This value may be bigger or smaller then the value of Input.Length property. The size of the buffer must be at least Max(Input.Length,Length). The size of the buffer can also be set automatically by the component, if you set AutoBufferSize to True. *) TSignalBuffer = class(TSignal) strict protected Buffer: TVec; BufferState: TCircularBufferState; FBufferType: TBufferType; FOverlapping: Double; FBufferSizeLimit: integer; FIncrementStep: Double; FetchDataNextTime: boolean; FStrideLength: integer; StridePosition: integer; procedure SetBufferType(const Value: TBufferType); procedure SetOverlapping(const Value: Double); procedure SetBufferSizeLimit(const Value: integer); procedure SetIncrementStep(const Value: Double); procedure SetStrideLength(const Value: integer); procedure RewindCircularBuffer(var aLen: integer); strict protected function InternalUpdate: TPipeState; override; public (* Reads Dst.Length of most recent data, without advancing the read or write cursor. The function returns true, if the buffer was long enough to fill the dst, independently of the state of the buffer. *) function Monitor(Dst: TVec; MonitorMode: TMonitorMode): boolean; (* Reads any data still in the buffer, but less than specified Length. Missing samples become zeros. *) function ReadReminder: boolean; function InternalPull: TPipeState; override; constructor Create(AOwner: TComponent); override; destructor Destroy; override; (* Returns true, if there is enough data in buffer to fill Data.Length. Call this function, if you want to prevent the Pull to update components connected to the Input and only want to use up the data already buffered. *) function DataInBuffer: boolean; (* Returns the number of samples currently stored in the buffer. *) function BufferedSamplesCount: integer; (* Reset the buffer. *) procedure Reset(); overload; override; (* Reset the buffer. *) procedure Reset(BufferSize: integer); reintroduce; overload; (* Returns True, if the internal buffer has been underrun. This means that reading from the buffer was to quick and some he data not yet written was read. *) function BufferUnderrun: boolean; (* Returns True, if the internal buffer has been overrun. This means that writing to the buffer was to quick and the some of the data not yet read has been overwritten. *) function BufferOverrun: boolean; (* Returns the current circular buffer write position. *) function BufferWritePosition: integer; (* Returns the current circular buffer read position. *) function BufferReadPosition: integer; (* Defines the length of the internal buffer in samples. *) function BufferLength: integer; (* Reads data from internal Buffer to the Data property. Returns true, if sufficent data was available. Data.Length defines the amount of data to read. *) function Read: boolean; (* Writes data to the buffer and resizes the buffer, if it is not big enough to hold everything. *) procedure Write; overload; (* Writes data to the buffer and resizes the buffer, if it is not big enough to hold everything. *) procedure Write(Src: TVec); overload; (* Writes data to the buffer and resizes the buffer, if it is not big enough to hold everything. Converts precision single/double if needed. *) procedure WriteTo(Src: TVec); overload; published (* Defines the minimum factor by which the buffer size will be increased. *) property IncrementStep: Double read FIncrementStep write SetIncrementStep; (* Overlapping defines the percent of previously read data to be read again. Old data is discarded. Overlapping is used, if the buffer is in bftStreaming mode. *) property Overlapping: Double read FOverlapping write SetOverlapping; (* StrideLength defines the number of samples that will be skipped between consecutive reads from the buffer. If StrideLength is 1, then on each read the buffer would move forward for 1 sample only. That would mean (1-1/Data.Length)*100% of overlapping. Setting StrideLength will set Overlapping to -1 and Overlapping parameter is ignored. When overlapp would have to be negative or there is a need to specify fordward step in number of samples, set the strideLength. It is possible to set StrideLength to 1000 and have Data.Lengt of 100. This means that the buffer will ignore 900 samples out of every 1000 written. *) property StrideLength: integer read FStrideLength write SetStrideLength default 0; (* Defines the type of the buffer. *) property BufferType: TBufferType read FBufferType write SetBufferType default bftStreaming ; (* Limits the maximum buffer size, if AutoBufferSize is True. If this limit is breached, an exception is raised. Applicable only when BufferType equals bftStoring *) property BufferSizeLimit: integer read FBufferSizeLimit write SetBufferSizeLimit default 2000000; property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; end; (* Stores data and allows indexed access to elements. Stores all incoming data in the Data property increasing the length as neccessary without too many resizes. Uses SetSubRange to report the correct length of the Data property. *) TSignalStoreBuffer = class(TSignal) strict protected FIncrementStep: Double; FActualBufferSize: integer; FInitialBufferSize: integer; FMaxBufferSize: integer; procedure SetIncrementStep(const Value: Double); procedure SetActualBufferSize(const Value: integer); procedure SetInitialBufferSize(const Value: integer); procedure SetMaxBufferSize(const Value: integer); procedure PushDataInBuffer; strict protected function InternalUpdate: TPipeState; override; procedure SetIsDouble(const Value: boolean); override; procedure SetFloatPrecision(const Value: TMtxFloatPrecision); override; public procedure Append(Src: TVec); reintroduce; overload; procedure Append(Sample: Double); overload; override; (* Reset the buffer size to initial size and no data. *) procedure Reset; override; (* Shift the data left or right by Offset samples. *) procedure Shift(Offset: integer); constructor Create(AOwner: TComponent); override; published (* If this property is bigger than 0, only latest MaxBufferSize samples will be retained in the buffer. *) property MaxBufferLength: integer read FMaxBufferSize write SetMaxBufferSize default -1; (* InitialBufferSize defines the initial size of the buffer in the number of samples. *) property InitialBufferSize: integer read FInitialBufferSize write SetInitialBufferSize default 16; (* Current buffer size in number of samples. The number of samples actually stored is available via the Length property. *) property ActualBufferSize: integer read FActualBufferSize write SetActualBufferSize; (* When the buffer is too small to store a new block of data, it will be increased at least by a factor of IncrementStep. *) property IncrementStep: Double read FIncrementStep write SetIncrementStep; property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; end; (* Manages a list of TSignalStoreBuffer components. Manage a list of TSignalStoreBuffer components. *) TSignalStoreBufferList = class(TSignalList) strict private function GetItems(index: integer): TSignalStoreBuffer; reintroduce; procedure SetItems(index: integer; const Value: TSignalStoreBuffer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalStoreBuffer read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manages a list of TSignalToComplexList components. Manage a list of TSignalToComplexList components. *) TSignalToComplexList = class(TSignalList) strict private FRealInputs: TSignalList; FImagInputs: TSignalList; function GetItems(index: integer): TSignalToComplex; reintroduce; procedure SetItems(index: integer; const Value: TSignalToComplex); procedure SetImagInputs(const Value: TSignalList); procedure SetRealInputs(const Value: TSignalList); strict protected function AddItem: TMtxComponent; override; procedure InternalMatchInputs; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalToComplex read GetItems write SetItems; default; published property RealInputs: TSignalList read FRealInputs write SetRealInputs; property ImagInputs: TSignalList read FImagInputs write SetImagInputs; end; (* Manages a list of TSignalBufferList components. Manage a list of TSignalBufferList components. *) TSignalBufferList = class(TSignalList) strict private function GetItems(index: integer): TSignalBuffer; reintroduce; procedure SetItems(index: integer; const Value: TSignalBuffer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalBuffer read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manages a list of TSignalFilter components. Manage a list of TSignalFilter components. *) TSignalFilterList = class(TSignalList) strict private function GetItems(index: integer): TSignalFilter; reintroduce; procedure SetItems(index: integer; const Value: TSignalFilter); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalFilter read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TSignalGenerator components. Manage a list of TSignalGenerator components. *) TSignalGeneratorList = class(TSignalList) strict private function GetItems(index: integer): TSignalGenerator; reintroduce; procedure SetItems(index: integer; const Value: TSignalGenerator); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalGenerator read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; TSpectrumFilter = class(TSignal) strict private FSmoothing: integer; procedure SetSmoothing(const Value: integer); strict protected function InternalUpdate: TPipeState; override; public constructor Create(AOwner: TComponent); override; published property Smoothing: integer read FSmoothing write SetSmoothing; property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; end; (* Manage a list of TSignalIncBuffer components. Manage a list of TSignalIncBuffer components. *) TSignalIncBufferList = class(TSignalList) strict private function GetItems(index: integer): TSignalIncBuffer; reintroduce; procedure SetItems(index: integer; const Value: TSignalIncBuffer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalIncBuffer read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TSignalIncBuffer components. Manage a list of TSignalIncBuffer components. *) TSignalDecBufferList = class(TSignalList) strict private function GetItems(index: integer): TSignalDecBuffer; reintroduce; procedure SetItems(index: integer; const Value: TSignalDecBuffer); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalDecBuffer read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TSignalDecimator components. Manage a list of TSignalDecimator components. *) TSignalDecimatorList = class(TSignalList) strict private function GetItems(index: integer): TSignalDecimator; reintroduce; procedure SetItems(index: integer; const Value: TSignalDecimator); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalDecimator read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TSignalInterpolator components. Manage a list of TSignalInterpolator components. *) TSignalInterpolatorList = class(TSignalList) strict private function GetItems(index: integer): TSignalInterpolator; reintroduce; procedure SetItems(index: integer; const Value: TSignalInterpolator); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalInterpolator read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TSignalDemodulator components. Manage a list of TSignalDemodulator components. *) TSignalDemodulatorList = class(TSignalList) strict private function GetItems(index: integer): TSignalDemodulator; reintroduce; procedure SetItems(index: integer; const Value: TSignalDemodulator); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalDemodulator read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TSignalModulator components. Manage a list of TSignalModulator components. *) TSignalModulatorList = class(TSignalList) strict private function GetItems(index: integer): TSignalModulator; reintroduce; procedure SetItems(index: integer; const Value: TSignalModulator); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalModulator read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TSignalBandpass components. Manage a list of TSignalBandpass components. *) TSignalBandpassList = class(TSignalList) strict private function GetItems(index: integer): TSignalBandpass; reintroduce; procedure SetItems(index: integer; const Value: TSignalBandpass); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalBandpass read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TSignalRateConverter components. Manage a list of TSignalRateConverter components. *) TSignalRateConverterList = class(TSignalList) strict private function GetItems(index: integer): TSignalRateConverter; reintroduce; procedure SetItems(index: integer; const Value: TSignalRateConverter); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalRateConverter read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TSignalVariableRateConverter components. Manage a list of TSignalVariableRateConverter components. *) TSignalVariableRateConverterList = class(TSignalList) strict private function GetItems(index: integer): TSignalVariableRateConverter; reintroduce; procedure SetItems(index: integer; const Value: TSignalVariableRateConverter); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalVariableRateConverter read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TBandlimitedInterpolator components. Manage a list of TBandlimitedInterpolator components. *) TBandlimitedInterpolatorList = class(TSignalList) strict private function GetItems(index: integer): TBandlimitedInterpolator; reintroduce; procedure SetItems(index: integer; const Value: TBandlimitedInterpolator); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TBandlimitedInterpolator read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Manage a list of TSignalEnvelopeDetector components. Manage a list of TSignalEnvelopeDetector components. *) TSignalEnvelopeDetectorList = class(TSignalList) strict private function GetItems(index: integer): TSignalEnvelopeDetector; reintroduce; procedure SetItems(index: integer; const Value: TSignalEnvelopeDetector); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalEnvelopeDetector read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; (*Data source. Defines the data source for the component, if the source is a single component. Setting the value of this property to other then nil, will set Inputs property to nil. *) property Input: TAnalysis read FInput write SetInput; end; (* Manage a list of TSignalDemux components. Manage a list of TSignalDemux components. *) TSignalDemuxList = class(TSignalList) strict private function GetItems(index: integer): TSignalDemux; reintroduce; procedure SetItems(index: integer; const Value: TSignalDemux); strict protected function AddItem: TMtxComponent; override; procedure InternalMatchInputs; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalDemux read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; (*Data source. Defines the data source for the component, if the source is a single component. Setting the value of this property to other then nil, will set Inputs property to nil. *) property Input: TAnalysis read FInput write SetInput; end; (* Manage a list of TSignalMux components. Manage a list of TSignalMux components. *) TSignalMuxList = class(TSignalList) strict private function GetItems(index: integer): TSignalMux; reintroduce; procedure SetItems(index: integer; const Value: TSignalMux); strict protected function AddItem: TMtxComponent; override; procedure InternalMatchInputs; override; public constructor Create(AOwner: TComponent); override; (* Default array property allows access to individual list items. *) property Items[index: integer]: TSignalMux read GetItems write SetItems; default; published (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; end; (* Convert a function type to string. Convert a TFuncSignalType type to string. *) function FuncToString(const par: TFuncSignalType): string; overload; (* Convert a string to function type. Convert a string to TFuncSignalType type. *) function StringToFunc(const par: string): TFuncSignalType; overload; (* Convert a string to operand type. Convert a string to TOperSignalType type. *) function StringToOp(const par: string): TOperSignalType; overload; (* Convert an operand type to string. Convert an TOperSignalType type to string. *) function OpToString(const par: TOperSignalType): string; overload; {$I BdsppDefs.inc} {$WARN SYMBOL_DEPRECATED OFF} (*General purpose components for signal processing and signal analysis.*) unit SignalTools; interface uses MtxVec, Math387, MtxBaseComp, SignalUtils, MtxVecBase, AbstractMtxVec, MtxVecInt ,SysUtils ,Classes ,Contnrs ; type (*Defined to support double/single compatibility. The record type is defined to support component configuration compatibility between signal processing package compiled in double and single precision *) TMarkRecordDouble = packed record Freq: double; Amplt: double; Phase: double; Time: double; end; TMarkRecordDoubleArray = array of TMarkRecordDouble; (*Stores mark position and value info. The type used by TSpectrumAnalyzer component to keep track of marks on the frequency spectrum. It can also be used to to keep track of marks on the time series. *) TMarkRecord = class public Freq: Double; Amplt: Double; Phase: Double; Time: Double; procedure Assign(Src: TMarkRecord); overload; procedure Assign(const Src: TMarkRecordDouble); overload; end; (*Defines how the marks will be sorted. The type is used to indicate by which parameter to sort the marks. *) TSortType = ( (*Sort by frequency ascending.*)srtFreq, (*sort by amplitude ascending.*)srtAmplt, (*sort by phase ascending.*)srtPhase, (*sort by time ascending.*)srtTime, (*sort by frequency descending.*)srtFreqDescending, (*sort by amplitude descending.*)srtAmpltDescending, (*sort by phase descending.*)srtPhaseDescending, (*sort by time descending.*)srtTimeDescending ); (*Defines the measuring units for the phase. Defines the units in which phase of the frequency component is calculated. *) TPhaseUnits = ( (*the phase will be calculated in radians.*)puRad, (*the phase will be calculated in degrees.*)puDeg); (*Defines the range of the computed phase spectrum.*) TPhaseRange = ( (*phase ranges from -Pi..+Pi.*)prPiPi, (*phase ranges from 0..2*Pi.*)prZeroTwoPi); (*Defines how to handle the phase of the frequency spectrum.*) TPhaseMode = ( (*The phase will not be calculated (via ArcTan2) and you can save some computation time.*)pmIgnore, (*The phase will be computed within -Pi..+Pi range in radians, or within -180..+180 range in degrees.*)pm360, (*Compute the phase delay.*)pmPhaseDelay, (*Compute group delay in samples.*)pmGroupDelay); (*Manages a list of marks. Handles, sorts and maintaines a list of TMarkRecord records. *) TMarkList = class(TObjectsList) strict protected FSortType: TSortType; procedure WriteArrayOfRecords(var Src: TMarkRecordDoubleArray; Dst: TStream; Len: integer); overload; procedure ReadArrayOfRecords(Src: TStream; var Dst: TMarkRecordDoubleArray; Len: integer); overload; function GetValue(Index: integer): TMarkRecord; procedure SetValue(Index: integer; Mark: TMarkRecord); procedure SetSortType(const Value: TSortType); procedure SetCount(const Value: integer); reintroduce; function GetCount: integer; reintroduce; public property Count: integer read GetCount write SetCount; (*Default array property allows you to access any record element by index.*) property Item[Index: Integer]: TMarkRecord read GetValue write SetValue; default; (*Defines the parameter by which to sort the list of TMarkRecord records.*) property SortType: TSortType read FSortType write SetSortType; (*Copy all data from Source.*) procedure Assign(Source: TMarkList); (*Add a new Mark to the list. The method creates a copy of the Mark record and adds it to the list.*) function Add(const Mark: TMarkRecord): integer; overload; (*Add a new Mark to the list.*) function Add: integer; overload; (*Save all marks to Dst stream.*) procedure SaveToStream(Dst: TStream); overload; (*Load marks from the Src stream.*) procedure LoadFromStream(Src: TStream); overload; (*Sort the list according to the value of the SortType property.*) procedure Sort; (*Sort the list relative to the Amplt field of the TMarkRecord in ascending order.*) procedure SortAmplt; (*Sort the list relative to the Freq field of the TMarkRecord in ascending order.*) procedure SortFreq; (*Sort the list relative to the Phase field of the TMarkRecord in ascending order.*) procedure SortPhase; (*Sort the list relative to the Time field of the TMarkRecord in ascending order.*) procedure SortTime; (*Sort the list relative to the Amplt field of the TMarkRecord in descending order.*) procedure SortAmpltDescending; (*Sort the list relative to the Freq field of the TMarkRecord in descending order.*) procedure SortFreqDescending; (*Sort the list relative to the Phase field of the TMarkRecord in descending order.*) procedure SortPhaseDescending; (*Sort the list relative to the Time field of the TMarkRecord in descending order.*) procedure SortTimeDescending; (*Copy contents of list item fields to corresponding dense array storage.*) procedure CopyToDense(const dstFreq, dstAmplt, dstPhase: TVec); overload; (*Copy contents of list item fields to corresponding dense array storage.*) procedure CopyToDense(const dstAmplt, dstTime: TVec); overload; (*Copy contents of list item fields from corresponding dense array storage.*) procedure CopyFromDense(const srcFreq, srcAmplt, srcPhase: TVec); overload; (*Copy contents of list item fields from corresponding dense array storage.*) procedure CopyFromDense(const srcAmplt, srcTime: TVec); overload; (*Copy contents of list item Freq field to dstFreq.*) procedure CopyFreqToDense(const dstFreq: TVec); constructor Create; end; (*Defines the state of the processing pipe. There are four possible states. Two of them, pipeStream and pipeBuffer, occure only, if TSignalBuffer component is connected in the processing chain. *) TPipeState = ( (*The connected component has returned a valid block of data.*)pipeOK, (*The connected component has not returned a valid block of data, because there is no more data to be processed. The connected components have not recalucated (no data). The processing can stop.*)pipeEnd, (*The buffer has not returned a new block of data, but the stream has not ended yet. All components in the chain before the buffer have been recalculated.*)pipeStream, (*The connected component has returned a valid block of data, but it came from the buffer and the update request did not go beyond the buffer component. All components from the buffer forward have recalculated. All components in the chain before the buffer have not yet recalculated.*)pipeBuffer ); (*Abstract class for all DSP components. The basic object of the Signal processing package components implements behaviour common to all derived components with a number of protected properties and methods, which can be made public (published), if appropriate, in the derived classes. The component implements two basic mechanisms: A way to connect components in to a chain of components and and way to distribute centralized recalculate request to all components in the chain. The components are connected via two different properties: Input (Single channel) or Inputs (multiple channel). The recalculation request for one component is done with call of the Update method and the recalcuation request for the entire chain of components is done with a call of the Pull method of the last component in the chain. Most procesing components (blocks) can also function as servers featuring their own component editors. They do not have to be conected with other components. *) TSignal = class; TSpectrum = class; TAnalysis = class; (*Abstract item class that enables connectivity of components. Abstract item class that enables connectivity of signal processing components. *) TMtxConnectItem = class(TMtxCollectionItem) strict protected FReference: TReferenceList; FOnInputUpdated: TNotifyEvent; procedure SetOnInputUpdated(const Value: TNotifyEvent); public (*The event will be called when the Input is Updated.*) property OnInputUpdated: TNotifyEvent read FOnInputUpdated write SetOnInputUpdated; constructor Create(Collection: TCollection); override; destructor Destroy; override; procedure CustomEvent(Sender: TObject); override; end; (*Item class that enables connectivity of components. Item class that enables connectivity of signal processing components. *) TSignalConnectItem = class(TMtxConnectItem) strict protected FDirty: boolean; FInput: TAnalysis; procedure SetInput(const Value: TAnalysis); procedure SetDirty(const Value: boolean); procedure ReferenceRemoved(Sender: TObject); public (*Used internally to signal new data. Used internally to signal if the Input has been updated or not. The Input property must be set by the component connected to the Input property as soon as it has fresh data. Set this property from inside InternalUpdate or InternalPull when developing new signal processing component. *) property Dirty: boolean read FDirty write SetDirty; (*To connect the component as input assign it to this property.*) property Input: TAnalysis read FInput write SetInput; constructor Create(Collection: TCollection); override; procedure Assign(Source: TPersistent); override; (*Set Input to nil.*) procedure Reset; end; (*Collection item. The class supports handling of TSignal as a TCollection item and maintains a list of references to TSignal objects. This list is editable from within Delphi IDE in design-time. *) TSignalItem = class(TSignalConnectItem) strict protected function GetInput: TSignal; procedure SetInput(const Value: TSignal); public constructor Create(Collection: TCollection); override; published (*Connect a TSignal object to the Input.*) property Input: TSignal read GetInput write SetInput; end; (*Abstract class for TSpectrum and TSignal Collections.*) TConnectorsCollection = class(TMtxCollection) strict protected FOnInputUpdated: TNotifyEvent; procedure SetOnInputUpdated(const Value: TNotifyEvent); strict protected FOwner: TObject; function GetOwner: TPersistent; override; public (*Event to be called when any component pointed to by a collection item, recieves a recalculation request.*) property OnInputUpdated: TNotifyEvent read FOnInputUpdated write SetOnInputUpdated; end; (*Allows design time and run-time connectivity of TAnalysis. Allows design time and run-time connectivity of TAnalysis and its descendants. This object is used to enabled the Input/Inputs property capabilities to signal processing components. *) TAnalysisConnectorsCollection = class(TConnectorsCollection) strict protected function GetItems(index: Integer): TSignalConnectItem; procedure SetItems(index: Integer; const Value: TSignalConnectItem); public (*Default array property allows access to list items without typecasting. *) property Items[index: Integer]: TSignalConnectItem read GetItems write SetItems; default; end; (*A collection for a list of references to TSignal objects. The class is used to handle a collection of references to TSignal objects. This list is editable from within Delphi IDE at design-time. *) TSignalCollection = class(TAnalysisConnectorsCollection) strict protected function GetItems(index: Integer): TSignalItem; reintroduce; procedure SetItems(index: Integer; const Value: TSignalItem); public function Add: TCollectionItem; (*Default array property allows access to list items without typecasting.*) property Items[index: Integer]: TSignalItem read GetItems write SetItems; default; constructor Create(AOwner: TComponent); end; (*Spectrum collection item. The class supports handling of TSpectrum as a TCollection item and maintains a list of references to TSpectrum objects. This list is editable from within Delphi IDE at design-time. *) TSpectrumItem = class(TSignalConnectItem) strict protected function GetInput: TSpectrum; procedure SetInput(const Value: TSpectrum); public constructor Create(Collection: TCollection); override; published (*Connect a TSpectrum object to the Input.*) property Input: TSpectrum read GetInput write SetInput; end; (*A collection for a list of references to TSpectrum objects. The class is used to handle a collection of references to TSpectrum objects. This list is editable from within Delphi IDE in design-time. *) TSpectrumCollection = class(TAnalysisConnectorsCollection) strict protected function GetItems(index: Integer): TSpectrumItem; reintroduce; procedure SetItems(index: Integer; const Value: TSpectrumItem); public property Items[index: Integer]: TSpectrumItem read GetItems write SetItems; default; (*Create a new TSpectrumItem.*) function Add: TCollectionItem; constructor Create(AOwner: TComponent); end; TPipeStateArray = array of TPipeState; (*Abstract class for TSignal and TSpectrum components.*) TAnalysis = class(TMtxComponent) strict private FOnAfterUpdate: TNotifyEvent; FOnBeforeUpdate: TNotifyEvent; FOnParameterUpdate: TNotifyEvent; FMultiChannel: boolean; FUsesInputs: boolean; FOnNotifyUpdate: TNotifyEvent; FContinuous: boolean; FOnDisplayUpdate: TNotifyEvent; FSuspendNotifyUpdate: boolean; FOnGetInput: TNotifyEvent; FPipeState: TPipeState; FItemNumber: integer; aStates: TPipeStateArray; procedure InternalPullInputs(const aStates: TPipeStateArray); function ResolveInputStates(const aStates: TPipeStateArray): TPipeState; procedure SetPipeDirty(const Value: boolean); procedure SetMultiChannel(const Value: boolean); procedure SetOnAfterUpdate(const Value: TNotifyEvent); procedure SetUsesInputs(const Value: boolean); procedure SetOnBeforeUpdate(const Value: TNotifyEvent); procedure SetOnParameterUpdate(const Value: TNotifyEvent); procedure SetOnNotifyUpdate(const Value: TNotifyEvent); procedure SetContinuous(const Value: boolean); procedure SetOnDisplayUpdate(const Value: TNotifyEvent); procedure SetSuspendNotifyUpdate(const Value: boolean); procedure SetOnGetInput(const Value: TNotifyEvent); procedure SetPipeState(const Value: TPipeState); procedure SetItemNumber(const Value: integer); strict protected FInputs: TAnalysisConnectorsCollection; FIsDouble: boolean; FFloatPrecision: TMtxFloatPrecision; FComplex: boolean; FFloatPrecisionLock: boolean; (*True, if there are still buffers whose Dirty property is true inside the pipe.*) FPipeDirty: boolean; (*Holds the value of the Active property.*) FActive: boolean; (*Sets property Dirty of all connected components which have the current one for its source, to True.*) procedure NotifyDirty; (*Sets property Dirty of all connected components which have the current one for its source, to false.*) procedure NotifyResetDirty; (*Sets the value of the Inputs property.*) procedure SetInputs(const Value: TAnalysisConnectorsCollection); (*Sets the value of the Input property.*) procedure SetInput(const Value: TAnalysis); virtual; (*Gets the value of the Input property.*) function GetInput: TAnalysis; virtual; (*Gets the value of the Inputs property.*) function GetInputs: TAnalysisConnectorsCollection; virtual; (*Sets the value of the Active property.*) procedure SetActive(const Value: boolean); virtual; (*Defines if the inputs collection should be streamed or not.*) function InputsStored: boolean; virtual; (*Notifies connected components that this component has fresh data and sets all inputs Dirty properties to false.*) procedure InvalidateInputs; (*Override this method to create custom TConnectorsCollection type descendants, when the component is created. By default the method creates TSignalCollection. *) function CreateInputs: TAnalysisConnectorsCollection; virtual; strict protected property PipeDirty: boolean read FPipeDirty write SetPipeDirty; (*Connector property to obtain a pointer to a list of TSignal components holding the data to be processed. The result of the processing is to be placed in Self. Publish this property in the derived classes, if your processing algorithm requires a multi-channel input. *) property Inputs: TAnalysisConnectorsCollection read GetInputs write SetInputs stored InputsStored; (*Set MultiChannel to True, if Inputs property will be published, and Input property will remain hidden. This property is to be set only in the Create method of the derived classes and should be set to true, only if Inputs property is published and Input property remains hidden. *) property MultiChannel: boolean read FMultiChannel write SetMultiChannel; (*Request recalculation of the data stored in TSignal object connected to Input/Inputs and place the result in Self. The Update request is not propagated. The call returns the state of the pipe. *) function InternalUpdate: TPipeState; virtual; (*The method is called by the ParamUpdate method before a call is made to OnParameterUpdate event. ParamUpdate method is of TNotifyEvent type and can be assigned to other event handlers to trigger recalculation of the data in Self. *) procedure InternalParamUpdate(Sender: TObject); virtual; (*The method is called just before a call is made to InternalUpdate by the Update method.*) procedure BeforeInternalUpdate; virtual; procedure SetComplex(const Value: boolean); virtual; procedure SetFloatPrecision(const Value: TMtxFloatPrecision); virtual; procedure SetFloatPrecisionLock(const Value: boolean); virtual; procedure SetIsDouble(const Value: boolean); virtual; public procedure Reset; override; (*Recursively processes all connected components backward and calls Reset method.*) procedure PipeReset; virtual; procedure DetectChannelCount; virtual; (*If all Inputs[i] are dirty the function returns true.*) function InputsDirty: boolean; (*Number to label the object when it is an item in the List. Used by List components to numerate each TAnalysis in the List. *) property ItemNumber: integer read FItemNumber write SetItemNumber; (*Returns the state of the pipe after the last update.*) property PipeState: TPipeState read FPipeState write SetPipeState; (*Set this property to True, to prevent calling OnNotifyUpdate from the Update Method. This can be usefull, when the screen should not be updated for every call to Update. *) property SuspendNotifyUpdate: boolean read FSuspendNotifyUpdate write SetSuspendNotifyUpdate; (*Set it to True to indicate, that valid signals will be connected to the Input or Inputs properties. The property must be false, if the Input/Inputs properties are nil. *) property UsesInputs: boolean read FUsesInputs write SetUsesInputs; (*Call this method only, if SuspendNotifyUpdate is True. This routine will simply call OnNotifyEvent, if assigned. *) procedure UpdateNotify; virtual; (*Notifies all connected components that this component has fresh data.*) procedure Invalidate; (*When called, the method will pass Update requests recursively to all connected objects. The InternalUpdate method will be called in order, starting from the last object in the chain. The call returns the state of the pipe. *) function InternalPull: TPipeState; virtual; (*Request recalculation of the data and place the result in Self. Return pipeOk, if the recalculation was successfull. *) function Update: TPipeState; reintroduce; virtual; (*Request recalculation of the entire chain of all connected components. Returns pipeOK, if the recalculation was successfull. If you have a processing chain of many connected components, the entire chain will be recalculated by calling the Pull method of the last component in the chain. If the Pull returns pipeStreaming, call the Pull method until it returns pipeOk. When the Pull method returns pipeEnd, the end of the data stream has been reached. *) function Pull: TPipeState; virtual; (*The method can be assigned to some other component even handler, as a notification that a parameter has changed and that Self should consider a recalculation. The method calls the InternalParamUpdate method and triggers the OnParameterUpdate event. *) procedure ParamUpdate(Sender: TObject); (*Calls Pull until pipeEnd is returned. Calls Pull in a loop until the condition returned by at least one item in the list is pipeEnd. The SuspendNotifyUpdate is set to false for the duration of this loop. *) procedure PullUntilEnd; virtual; procedure Assign(Source: TPersistent); override; constructor Create(AOwner:TComponent); override; destructor Destroy; override; (*Connector property to obtain a pointer to the TSignal component holding the data to be processed. The result of the processing is to be placed in Self.Publish this property in the derived classes, if your processing algorithm requires only a single channel input. *) property Input: TAnalysis read GetInput write SetInput stored InputsStored; published (* Set this property to false, to request computation in 32bit floating point precision, 64bit, if true. The value of this property is typically picked up from Input or Inputs property. Setting this value is only meaningfull, if this component is the first in the computational pipe. Aditionally, by setting FloatPrecisionLock to True, will prevent both IsDouble and Complex properties of all items to have their corresponding properties changed. Changing IsDouble property will also change the value of FloatPrecision property. *) property IsDouble: boolean read FIsDouble write SetIsDouble; (* Set this property to true, to request computation using complex numbers. The value of this property is typically picked up from Input or Inputs property. Setting this value is only meaningfull, if this component is the first in the computational pipe. Aditionally, by setting FloatPrecisionLock to True, will prevent both IsDouble and Complex properties of all items to have their corresponding properties changed. Changing Complex/IsDobule properties will also change the value of FloatPrecision property. *) property Complex: boolean read FComplex write SetComplex default False ; (* Set this property to request computation in 32bit/64bit precision with real/complex numbers. The value of this property is typically picked up from Input or Inputs property. Setting this value is only meaningfull, if this component is the first in the computational pipe. Aditionally, by setting FloatPrecisionLock to True, will prevent both IsDouble and Complex properties of all items to have their corresponding properties changed. Changing IsDouble/Complex properties will also change the value of FloatPrecision property. *) property FloatPrecision: TMtxFloatPrecision read FFloatPrecision write SetFloatPrecision; (* Set this property to true to lock changes to FloatPrecision property. The value of this property is NOT picked up from Input or Inputs property. *) property FloatPrecisionLock: boolean read FFloatPrecisionLock write SetFloatPrecisionLock default False ; (*Set active to false to suppress the propagation of the Pull request and the subsequent call to the Update method. The components connected to the Input property and the component itself will not be updated, if the Pull method will be called and the Active property will be false. A direct call to the Update method will still work! *) property Active: boolean read FActive write SetActive default True ; (*If True, the component will pass on the udpate request to the connected component. Calling Pull will always execute the Update method. *) property Continuous: boolean read FContinuous write SetContinuous default True ; (*The event is triggered after the call to the Update method.*) property OnAfterUpdate: TNotifyEvent read FOnAfterUpdate write SetOnAfterUpdate; (*The event is triggered just before the call to the Update method.*) property OnBeforeUpdate: TNotifyEvent read FOnBeforeUpdate write SetOnBeforeUpdate; (*The event is triggered when ParamUpdate method is called. The ParamUpdate method is usually called by component editors when a value was changed by the user and the editor was "Live". *) property OnParameterUpdate: TNotifyEvent read FOnParameterUpdate write SetOnParameterUpdate; (*The event is triggered after the OnAfterUpdate event. Use this event to update any associated charts or graphs or result tables. The event will be triggered only, if UpdateEditors property is True. *) property OnDisplayUpdate: TNotifyEvent read FOnDisplayUpdate write SetOnDisplayUpdate; (*If the Input property is not assigned, the component will call OnGetInput.*) property OnGetInput: TNotifyEvent read FOnGetInput write SetOnGetInput; (* The event is called after a call to InternalUpdate, OnAfterUpdate and OnDisplayUpdate from within the Update method.*) property OnNotifyUpdate: TNotifyEvent read FOnNotifyUpdate write SetOnNotifyUpdate; end; (*Defines how to process pipes. Pipes connected with TAnalysisList components can be processed in two ways. *) TPipeProcessing = ( (*All pipes are called one after another on each processing stage, before proceeding to the next. This type of operation is usefull when all channels are interdependent. If a signal is multiplexed (for example) then the next block of data for any of the channels can not be fetched until all channels have processed their current data.*) ppParallel, (*Each pipe is first processed in full length before the processing of the next pipe begins. The channels are independent. Fetching a new block of data for one channel will not affect the other channels in any way.*) ppSerial ); (*Abstract class for all components, which hold a list of TAnalysis components. The component is a generic class designed to handle lists of descendants from TAnalysis class. Descendants from the TAnalysisList class have to override the AddItem method and possibly declare a default array property to access typed list elements. Save and load the list from stream and file, is automatically available. You can propagate the changes made to properties of the first element in the list, to all other elements, by calling the Propagate method. Update and Pull methods are also centralized. *) TAnalysisList = class(TMtxComponentList) strict private FSuspendNotifyUpdate: boolean; FOnDisplayUpdate: TNotifyEvent; FOnNotifyUpdate: TNotifyEvent; FOnBeforeUpdate: TNotifyEvent; FOnAfterUpdate: TNotifyEvent; FOnParameterUpdate: TNotifyEvent; FEditIndex: integer; procedure SetSuspendNotifyUpdate(const Value: boolean); function GetItemsp(index: integer): TAnalysis; procedure SetItemsp(index: integer; const Value: TAnalysis); procedure SetOnAfterUpdate(const Value: TNotifyEvent); procedure SetOnBeforeUpdate(const Value: TNotifyEvent); procedure SetOnDisplayUpdate(const Value: TNotifyEvent); procedure SetOnNotifyUpdate(const Value: TNotifyEvent); procedure SetOnParameterUpdate(const Value: TNotifyEvent); procedure DoAfterUpdate(Sender: TObject); procedure DoBeforeUpdate(Sender: TObject); procedure DoDisplayUpdate(Sender: TObject); procedure DoNotifyUpdate(Sender: TObject); procedure DoParameterUpdate(Sender: TObject); procedure SetEditIndex(const Value: integer); strict protected FInput: TAnalysis; FInputs: TAnalysisList; FActive: boolean; FContinuous: boolean; FIsDouble: boolean; FFloatPrecision: TMtxFloatPrecision; FComplex: boolean; FFloatPrecisionLock: boolean; procedure SetInput(const Value: TAnalysis); procedure SetInputs(const Value: TAnalysisList); procedure SetActive(const Value: boolean); virtual; procedure SetContinuous(const Value: boolean); virtual; procedure SetFloatPrecision(const Value: TMtxFloatPrecision); virtual; procedure SetFloatPrecisionLock(const Value: boolean); virtual; procedure SetComplex(const Value: boolean); virtual; procedure SetIsDouble(const Value: boolean); virtual; (*Defines if the inputs collection should be streamed or not.*) function InputsStored: boolean; virtual; (*Data source. Defines the data source for the component, if the source is a list of components. Setting this property to other then nil, will set Input property to nil. *) property Inputs: TAnalysisList read FInputs write SetInputs; (*Data source. Defines the data source for the component, if the source is a single component. Setting the value of this property to other then nil, will set Inputs property to nil. *) property Input: TAnalysis read FInput write SetInput; (*Called automatically by Pull and PullUntilEnd. Ensures the Count property will be set to the same number of channels as it's connected component list. The state of the first component in the list will be used as a template for all new components added to the list. *) procedure InternalMatchInputs; virtual; procedure Add(MtxComponent: TMtxComponent); override; public (*Assign consecutive numbers to individual items. Assign consecutive numbers starting with 0 to every items ItemNumber property. This numbers are assigned when the Items are added automatically. However, if the items in the list are moved or deleted, renumbering is neccessary, if there are parts of code that depend upon ItemNumber property and ItemNumber must coincide with the Index in the list at which this component is located. *) procedure Renumerate; virtual; procedure ReferenceRemoved(Sender: TObject); override; (*Call this method only, if SuspendNotifyUpdate is True. This routine will simply call OnNotifyEvent, if assigned. OnNotifyEvent events of the items are called by the Update methods, if SuspendNotifyUpdate is false. *) procedure UpdateNotify; (* Match the connections of Inputs with the component. Recursive function will be passed to the chain of all connected components. Its purpose is best illustrated with an example. If we have a chain of TAnalysisList components and the first item in the chain reads a file, that file may have any ChannelCount. All connected components must therefore adjust their Count property and connect the Input property for individual Items in their lists with corresponding Items in the Inputs property list. This is especially usefull when there is a need to handle signal multiplexing and demultiplexing. *) procedure MatchInputs; virtual; (*Default array property allows access to individual list items *) property Items[index: integer]: TAnalysis read GetItemsp write SetItemsp; default; (*Distribute Update request to all objects in the list. Return value of the PipeState property for the first item. Inputs or Input must have valid data. The value of their PipeState property will not be checked. *) function Update: TPipeState; reintroduce; virtual; (*Distribute Pull request to all objects in the list. Return true if the recalculation was successfull for all of them. *) function Pull: TPipeState; virtual; (*Calls Pull until pipeEnd is returned. Calls Pull in a loop until the condition returned by at least one item in the list is pipeEnd. *) procedure PullUntilEnd; virtual; (*Propagates the settings of the component at SrcIndex to all other components. The settings are assigned using the SaveTemplateToStream and LoadTemplateFromStream methods. *) procedure Propagate(SrcIndex: integer); overload; constructor Create(AOwner: TComponent); override; (* Set this property to false, to request computation in 32bit floating point precision, 64bit, if true. The value of this property is typically picked up from Input or Inputs property. Setting this value is only meaningfull, if this component is the first in the computational pipe. Aditionally, by setting FloatPrecisionLock to True, will prevent both IsDouble and Complex properties of all items to have their corresponding properties changed. Changing IsDouble property will also change vaue of FloatPrecision property. *) property IsDouble: boolean read FIsDouble write SetIsDouble; (* Set this property to true, to request computation using complex numbers. The value of this property is typically picked up from Input or Inputs property. Setting this value is only meaningfull, if this component is the first in the computational pipe. Aditionally, by setting FloatPrecisionLock to True, will prevent both IsDouble and Complex properties of all items to have their corresponding properties changed. Changing Complex/IsDobule properties will also change value of FloatPrecision property. *) property Complex: boolean read FComplex write SetComplex; published (* Set this property to request computation in 32bit/64bit precision with real/complex numbers. The value of this property is typically picked up from Input or Inputs property. Setting this value is only meaningfull, if this component is the first in the computational pipe. Aditionally, by setting FloatPrecisionLock to True, will prevent both IsDouble and Complex properties of all items to have their corresponding properties changed. Changing IsDouble/Complex properties will also change value of FloatPrecision property. *) property FloatPrecision: TMtxFloatPrecision read FFloatPrecision write SetFloatPrecision; (* Set this property to true to lock changes to FloatPrecision property. The value of this property is NOT picked up from Input or Inputs property. *) property FloatPrecisionLock: boolean read FFloatPrecisionLock write SetFloatPrecisionLock; (*Specifies index of the item in the list for which to display the design-time editor. Default value is -1, which means that all items in the list will get to have the same settings specified by editor once. Used only by the design-time editor within the IDE. *) property EditIndex: integer read FEditIndex write SetEditIndex default -1 ; (*Set active to false to suppress the propagation of the Pull request and the subsequent call to the Update method. The components connected to the Input property and the component itself will not be updated, if the Pull method will be called and the Active property will be false. A direct call to the Update method will still work! *) property Active: boolean read FActive write SetActive default True ; (*If True, the component will pass on the udpate request to the connected component. Calling Pull will always execute the Update method. *) property Continuous: boolean read FContinuous write SetContinuous default True ; (*Set this property to True, to prevent calling OnNotifyUpdate from the Update Method. This can be usefull, when the screen should not be updated for every call to Update. *) property SuspendNotifyUpdate: boolean read FSuspendNotifyUpdate write SetSuspendNotifyUpdate default false ; (*The event is triggered after the call to the Update method.*) property OnAfterUpdate: TNotifyEvent read FOnAfterUpdate write SetOnAfterUpdate; (*The event is triggered just before the call to the Update method.*) property OnBeforeUpdate: TNotifyEvent read FOnBeforeUpdate write SetOnBeforeUpdate; (*The event is triggered when ParamUpdate method is called. The ParamUpdate method is usually called by component editors when a value was changed by the user and the editor was "Live". *) property OnParameterUpdate: TNotifyEvent read FOnParameterUpdate write SetOnParameterUpdate; (*The event is triggered after the OnAfterUpdate event. Use this event to update any associated charts or graphs or result tables when the associated dialog is to be updated. The event will be triggered only, if TMtxComponent.EditorActive property is True. *) property OnDisplayUpdate: TNotifyEvent read FOnDisplayUpdate write SetOnDisplayUpdate; (* The event is called after a call to InternalUpdate, OnAfterUpdate and OnDisplayUpdate from within the Update method.*) property OnNotifyUpdate: TNotifyEvent read FOnNotifyUpdate write SetOnNotifyUpdate; end; (*Class defined to store uniformly sampled data. Use TSignal component to store uniformly sampled data, with given sampling frequency. *) TSignal = class(TAnalysis) strict private fBandwidth: Double; fBandwidthH: Double; fDt: Double; FMarks: TMarkList; FMinX: Double; fMaxX: Double; FBandWidthL: Double; fHzRes: Double; FSamplingFrequency: Double; FSamplingTime: Double; FData: TVec; FLength: integer; FMinAmplt: Double; FMaxAmplt: Double; FChannelCount: integer; procedure setLength(const Value: integer); procedure SetBandWidthL(const Value: Double); procedure SetSamplingTime(const Value: Double); procedure SetHzRes(const Value: Double); procedure SetDt(const Value: Double); procedure SetData(const Value: TVec); strict protected function GetInput1: TSignal; procedure SetInput1(const Value: TSignal); function GetInputs1: TSignalCollection; procedure SetInputs1(const Value: TSignalCollection); protected procedure DefineProperties(Filer: TFiler); override; strict protected procedure set_Values(const i: integer; const value: Double); function get_CValues(const i: integer): TCplx; procedure set_CValues(const i: integer; const Value: TCplx); function get_Values(const i: integer): Double; strict protected property Inputs: TSignalCollection read GetInputs1 write SetInputs1 stored InputsStored; function GetChannelCount: integer; virtual; procedure SetChannelCount(const Value: integer); virtual; procedure SetSamplingFrequency(const Value: Double); virtual; procedure BeforeInternalUpdate; override; procedure SaveBinaryStream(Stream: TStream); overload; virtual; procedure LoadBinaryStream(Stream: TStream); overload; virtual; procedure UpdateSetLength(Sender: TObject); virtual; procedure SetIsDouble(const Value: boolean); override; procedure SetFloatPrecision(const Value: TMtxFloatPrecision); override; procedure SetComplex(const Value: boolean); override; public property Input: TSignal read GetInput1 write SetInput1 stored InputsStored; (*Stores the minimum amplitude of the signal found by the MinMax method.*) property MinAmplt: Double read FMinAmplt; (*Stores the maximum amplitude of the signal found by the MinMax method.*) property MaxAmplt: Double read FMaxAmplt; (*Returns the upper bandwidth edge of the signal. It is calculated as BandwdithL+ Bandwidth. *) property BandwidthH: Double read fBandWidtH; (*Returns the lower bandwidth edge of the signal. Usually this property is zero. *) property BandwidthL: Double read FBandWidthL write SetBandWidthL; (*Stores the time stamps and amplitude of selected samples.*) property Marks: TMarkList read FMarks; (*Use the MaxX property to store the time stamp (in seconds) of the right most sample. Each time the SamplingTime property changes (the length of the signal in seconds), MaxX is recalculated as: MaxX := MinX + SamplingTime. *) property MaxX: Double read FMaxX write fMaxX; (*Use the MinX property to store the time stamp of the left-most sample. MaxX value depends on the value of MinX. *) property MinX: Double read FMinX write fMinX; (*Frequency resolution obtained, if an FFT algorithm would be run on the data.*) property HzRes: Double read FHzRes write SetHzRes; (*Default property to access values of a complex signal. Use the Values property to work with a real signal. You can determine, if the signal is real or complex, by reading the Complex property. *) property CValues[const i: integer]: TCplx read get_CValues write set_CValues; (*Default property to access values of a real signal. Use the CValues property to work with a complex signal. You can determine, if the signal is real or complex, by reading the Complex property. *) property Values[const i: integer]: Double read get_Values write set_Values; default; (*The length of the signal in seconds. SamplingTime will change, if the Length property changes. The length of the signal in seconds is kept proportional to the length of the signal in samples. The Length property will not change, if you change the SamplingTime. *) property SamplingTime: Double read FSamplingTime write SetSamplingTime stored AllowStreaming; (*Time between two consecutive samples.*) property Dt: Double read FDt write SetDt; (*Find minimum and maximum amplitude and store the result in the MinAmplt and MaxAmplt properties.*) procedure MinMax; (*Compute the CREST parameter.*) function Crest: Double; (*Find the maximum deviation of the signal from its mean value.*) function Peak: Double; (*Assign the size of the signal from Src. The method will assign: Complex, Length and SamplingFrequency properties in that order. *) procedure Size(Src: TSignal); (*Convert marks to strings. The strings will be stored in TStrings object with two columns, aligned for display with a fixed charachter width Font. The Header of each column is defined with the XTitle and YTitle properties. The header line will not be displayed, if both XTitle and YTitle are set to an empty string. The width of the columns is defined with global variable: SignalTextColumnWidth. TimeFormat and AmplitudeFormat define the number formating (for example '0.00##') If TimeFormat or AmplitudeFromat is an empty string, full numeric precision will be used. *) procedure MarksToStrings(Dst: TStrings; const TimeFormat: string = ''; const AmplitudeFormat: string = ''; const XTitle: string = 'Time [s]'; const YTitle: string = 'Amplitude'; const Delimiter: string =''); virtual; (*Convert values to strings. The Strings will be stored in TStrings object with two columns aligned for display with a fixec charachter width Font. The head of each column is defined the XTitle and YTitle properties. both XTitle and YTitle are set to an empty string. The width of the columns is defined with global variable: SignalTextColumnWidth. TimeFormat and AmplitudeFormat define the number formating (for example '0.00##') If TimeFormat or AmplitudeFromat is an empty string, full numeric precision will be used. *) procedure ValuesToStrings(Dst: TStrings; const TimeFormat: string = ''; const AmplitudeFormat: string = ''; const XTitle: string = 'Time [s]'; const YTitle: string = 'Amplitude'; const Delimiter: string = ''); virtual; (*Copies signal Data and sampling frequency information.*) procedure Copy(Src: TSignal); (*Appends Sample to the end of the signal. This procedure should be avoided when large sets of data need to be appended. For each new value appended, the entire signal is copied twice. Use Data.Copy in that case (TVec method). *) procedure Append(Sample: Double); virtual; destructor Destroy; override; constructor Create(AOwner: TComponent); override; function AllowStreaming: Boolean; override; published (*Get or set the number of channels multiplexed in the data property.*) property ChannelCount: integer read GetChannelCount write SetChannelCount default 1; (*Return the bandwidth of the signal.*) property BandWidth: Double read fBandwidth; (*Stores the result of the processing.*) property Data: TVec read FData write SetData stored AllowStreaming; (*Get or set the length of the signal in samples.*) property Length: integer read FLength write SetLength default 128; (*Defines the sampling frequency of the signal. Sampling frequency will remain fixed, when you change the Length property. *) property SamplingFrequency: Double read FSamplingFrequency write SetSamplingFrequency; end; (*Manages a list of TSignal objects. The component overrides the AddItem method and declares a new default array property returning TSignal type. *) TSignalList = class(TAnalysisList) strict private function GetItems(index: integer): TSignal; reintroduce; procedure SetItems(index: integer; const Value: TSignal); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSignal objects by Index.*) property Items[index: integer]: TSignal read GetItems write SetItems; default; end; (*Class defined to store and manage a frequency spectrum. Use TSpectrum to compute and store frequency spectrum and work with amplitude and phase. *) TSpectrum = class(TAnalysis) strict private FMainlobeWidth: integer; fAverages: integer; FPhaseMode: TPhaseMode; FPhaseUnits: TPhaseUnits; FLength: integer; FSamplingFrequency: Double; FSamplingTime: Double; FMaxAmplt: Double; FMinPhase: Double; FMinAmplt: Double; FMaxPhase: Double; FAmplt, FPhase: TVec; FMarks: TMarkList; FHzRes: Double; fBandwidth: Double; fBandwidthH: Double; fBandwidthL: Double; FHarmonics: integer; FDt,ComplexFix: Double; FZeroPadding: integer; FMaxFreqIndex: integer; FPhaseRange: TPhaseRange; function SumOfAmpltSquares(Index, Len: integer): Double; procedure SetMainlobeWidth(const Value: integer); procedure SetPhaseMode(const Value: TPhaseMode); procedure SetPhaseUnits(const Value: TPhaseUnits); procedure SetLength(const Value: integer); procedure SetSamplingTime(const Value: Double); procedure SetMaxAmplt(const Value: Double); procedure SetMaxPhase(const Value: Double); procedure SetMinAmplt(const Value: Double); procedure SetMinPhase(const Value: Double); procedure SetHzRes(const Value: Double); procedure SetBandWidthL(const Value: Double); procedure SetHarmonics(const Value: integer); procedure SetDt(const Value: Double); procedure SetMaxFreqIndex(const Value: integer); procedure SetSamplingFrequency(const Value: Double); procedure SetAmplt(const Value: TVec); procedure SetPhase(const Value: TVec); procedure SetPhaseRange(const Value: TPhaseRange); strict protected fIsSpectrumAnalyzer: boolean; FConjExtend: boolean; FConjFlip: boolean; FIntegrateWork: TVec; function GetInput1: TSignal; function GetInputs1: TSignalCollection; procedure SetInput1(const Value: TSignal); procedure SetInputs1(const Value: TSignalCollection); procedure SetConjExtend(const Value: boolean); procedure SetConjFlip(const Value: boolean); protected procedure DefineProperties(Filer: TFiler); override; strict protected property Inputs: TSignalCollection read GetInputs1 write SetInputs1 stored InputsStored; procedure ComputeGroupDelay; virtual; procedure ComputePhaseDelay; virtual; procedure UpdateSetLength(Sender: TObject); virtual; procedure SetZeroPadding(const Value: integer); virtual; procedure BeforeInternalUpdate; override; procedure SaveBinaryStream(Stream: TStream); overload; virtual; procedure LoadBinaryStream(Stream: TStream); overload; virtual; function AllowDStreaming: boolean; virtual; procedure SetComplex(const Value: boolean); override; procedure SetIsDouble(const Value: boolean); override; procedure SetFloatPrecision(const Value: TMtxFloatPrecision); override; procedure PhaseCorrection(const SrcDst: TVec); virtual; public (*Returns true, if the descendant class is derived from TSpectrumAnalyzer. *) property IsSpectrumAnalyzer: boolean read fIsSpectrumAnalyzer; property Input: TSignal read GetInput1 write SetInput1 stored InputsStored ; (*Returns the upper bandwidth edge of the signal. It is calculated as BandwdithL+ Bandwidth. *) property BandwidthH: Double read fBandWidthH; (*Returns the lower bandwidth edge of the signal. Usually this property is zero. *) property BandwidthL: Double read FBandWidthL write SetBandWidthL; (*Stores the frequency, amplitude and phase of selected frequency bins.*) property Marks: TMarkList read FMarks; (*Holds the index at which the maximum amplitude was found by the MinMaxAmplt method.*) property MaxFreqIndex: integer read FMaxFreqIndex write SetMaxFreqIndex; (*Stores the minimum value of the Amplt spectrum found by the MinMaxAmplt method.*) property MinAmplt: Double read FMinAmplt write SetMinAmplt; (*Stores the maximum value of the Amplt spectrum found by the MinMaxAmplt method.*) property MaxAmplt: Double read FMaxAmplt write SetMaxAmplt; (*Stores the minimum value of the Phase spectrum found by the MinMaxPhase method.*) property MinPhase: Double read FMinPhase write SetMinPhase; (*Stores the maximum value of the Phase spectrum found by the MinMaxPhase method.*) property MaxPhase: Double read FMaxPhase write SetMaxPhase; (*Used to store the number of averages made.*) property Averages: integer read fAverages write fAverages; (*If True, the conjugate symmetric part of the frequency spectrum will be placed in 0.5..1.0 Fs (as returned by the FFT), and not between -0.5..0.0 Fs. The conjugate symmetic part of the frequency spectrum from the real time series can be placed before or after its original.) The property is used in derived classes. *) property ConjFlip: boolean read FConjFlip write SetConjFlip; (*If True, the conjugate symmetric part of the frequency spectrum is to be computed. The property is used in derived classes. The conjugate symmetic part of teh frequency spectrum can be used to detect aliasing, if you search for harmonics. *) property ConjExtend: boolean read FConjExtend write SetConjExtend; (*Compute amplitude spectrum from X.*) procedure AmpltSpectrum(X: TVec); virtual; (*Return amplitude at frequency Freq. Frequency which is not accurately alligned with the frequency bin of the spectrum is rounded to the closest frequency bin. *) function AmpltEst(Freq: Double): Double; overload; virtual; (*Return phase at frequency Freq. Frequency which is not accurately alligned with the frequency bin of the spectrum is rounded to the closest frequency bin. *) function PhaseEst(Freq: Double): Double; overload; virtual; (*Returns the amplitude of the frequency Freq computed from SpcFreqBin. SpcFreqBin must contain complex spectrum bin estimated at Freq. The Freq parameter is needed only to apply differentiation/integration in derived classes. *) function AmpltEst(Freq: Double; const SpcFreqBin: TCplx): Double; overload; virtual; (*Returns the amplitudes of the frequency spectrum at Freq frequencies. Frequencies which are not accurately alligned with frequency bins of the spectrum are rounded to the closest frequency bin. The value of the Freq vector is preserved *) procedure AmpltEst(Freq, DstAmplt: TVec); overload; virtual; (*Returns the amplitudes of the amplitudes at Freq frequencies. SrcSpectrum must contain complex spectrum estimated at frequencies defined in Freq. *) procedure AmpltEst(Freq, SrcSpectrum, DstAmplt: TVec); overload; virtual; (*Returns the phases of the frequency Freq according to the value of the Peaks property. SpcFreqBin must contain complex spectrum bin estimated at Freq. *) function PhaseEst(Freq: Double; const SpcFreqBin: TCplx): Double; overload; virtual; (*Returns the phases of the frequency spectrum at Freq frequencies. Frequencies which are not accurately alligned with frequency bins of the spectrum are rounded to the closest frequency bin. The value of the Freq vector is preserved. *) procedure PhaseEst(Freq, DstPhase: TVec); overload; virtual; (*Returns the phases of the frequency spectrum at Freq frequencies. SrcSpectrum must contain complex spectrum estimated at frequencies defined in Freq. DstPhase will hold the result. The phase is corrected for peak processing. *) procedure PhaseEst(Freq, SrcSpectrum, DstPhase: TVec); overload; virtual; (*Find minimum and maximum value of the Amplt spectrum. The result is stored in the MinAmplt and MaxAmplt properties. *) procedure MinMaxAmplt; (*Find minimum and maximum value of the Phase spectrum. The result is stored in the MinPhase and MaxPhase properties. *) procedure MinMaxPhase; (*Return the peak closest to the Freq frequency.*) function PeakApproximate(Freq: Double): Double; overload; virtual; (*Filter the peak at Marks position Index from the signal and recalculate the spectrum.*) procedure FilterPeak(Index: integer); virtual; (*Add amplitude and phase of the ASpectrum to the current spectrum.*) procedure Add(ASpectrum: TSpectrum); (*Flip the frequency spectrum on the frequency axis.*) procedure BandFlip; (*Copy ASpectrum to the calling spectrum.*) procedure Copy(ASpectrum: TSpectrum); overload; virtual; (*Differentiate the signal in the frequency domain. The diffCount parameter defines the differentiation count. Valid values for diffCount are 0 to 8. *) procedure Differentiate(const diffCount: integer); (*Divide the calling the spectrum with ASpectrum. Amplitude part is divided and phase part is subtracted. *) procedure Divide(ASpectrum: TSpectrum); overload; virtual; (*Subtract ASpectrum from the calling spectrum.*) procedure Subtract(ASpectrum: TSpectrum); (*Scale the calling spectrum with Factor.*) procedure Scale(Factor: Double); (*Square the Amplt and Phase of the ASpectrum and copy it to self.*) procedure Sqr(ASpectrum: TSpectrum); virtual; (*Computes range checked array indexes in the spectrum from frequency values. The routine considers the value of the BandwidthL property, the HzRes and Bandwidth property. Any values outside of the valid range will be clipped to the edge of the valid range. *) procedure FreqToFreqIndex(const Freq: TVec; const DstFreqIndex: TVecInt); overload; (*Integrate the signal in the frequency domain. The intCount parameter defines the differentiation count. Valid values for intCount are 0 to 8. *) procedure Integrate(const intCount: integer); (*Compute the logarithm of the amplitude spectrum and convert to dB. Base defines the base of the logarithm (usually 10). Span defines the span in dB of the result, and AMax is the maximum amplitude found in the spectrum. If Normalized is True, the highest value of the spectrum will positioned at 0. *) function LogAmplt(Base, Span, aMax, aScale: Double; Normalized: boolean): Double; virtual; (*Calculates the average of the noise. The signal is considered to be marked and is ignored. The width of the peaks is determined with the MainlobeWidth property. Marks are stored in Marks property. *) function NF(ActualZeroPadding: Double = 1): Double; (*Multiply the calling the spectrum with ASpectrum. Amplitude part is mulitplied and phase part is added. *) procedure Mul(ASpectrum: TSpectrum); (*Calls MinMaxAmplt and MinMaxPhase.*) procedure MinMax; (*Compute power spectrum from X.*) procedure PowerSpectrum(X: TVec); virtual; (*Compute phase spectrum from X.*) procedure PhaseSpectrum(X: TVec); virtual; (*Compute RMS spectrum from X.*) procedure RMSSpectrum(X: TVec); virtual; (*Initialize spectrum to zero.*) procedure SetZero; overload; virtual; (*Assign Length, Complex and SamplingFrequency properties.*) procedure Size(Src: TSpectrum); (*Convert values to strings. The strings will be stored in TStrings object with three columns, alligned for display with a fixed charachter width Font. The Header of each column is defined with the XTitle and ATitle and PTitle properties. The header line will not be displayed, if you set XTitle, ATitle and PTitle to empty strings. The width of the columns is defined with global variable: SignalTextColumnWidth. If FrequencyFormat, AmplitudeFormat or PhaseFormat is an empty string, full numeric precision will be used for the coresponding column. *) procedure ValuesToStrings(Dst: TStrings; const FrequencyFormat: string = ''; const AmplitudeFormat: string = ''; const PhaseFormat: string = ''; const XTitle: string = 'Frequency [Hz]'; const ATitle: string = 'Amplitude'; const PTitle: string = 'Phase'; IncludePhase: boolean = True; const Delimiter: string = ''); virtual; (*Convert marks to strings. The strings will be stored in TStrings object with two columns, alligned for display with a fixed charachter width Font. The Header of each column is defined with the XTitle and ATitle and PTitle properties. The header line will not be displayed, if XTitle, ATitle and PTitle are set to empty strings. The width of the columns is defined with the global variable: SignalTextColumnWidth. If FrequencyFormat, AmplitudeFormat or PhaseFormat is an empty string, full numeric precision will be used for the coresponding column. *) procedure MarksToStrings(Dst: TStrings; const FrequencyFormat: string = ''; const AmplitudeFormat: string = ''; const PhaseFormat: string = ''; const XTitle: string = 'Frequency [Hz]'; const ATitle: string = 'Amplitude'; const PTitle: string = 'Phase'; IncludePhase: boolean = True; const Delimiter: string = ''); (*Calculates the signal-to-noise ratio. The signal is considered to be marked. The method sums the marks and calculates the ratio towards the rest and returns the result in dB. The width of the peaks is determined with the MainlobeWidth property. Marks are stored in Marks property. *) function SNR(ActualZeroPadding: Double = 1): Double; (*Signal-to-noise-and-distortion calculates the the ratio of the maximum mark towards the rest (noise). Marked peaks (except for the maximum) are considered to be the noise and are summed separately to compensate for FFT leakage error and avoid using Parsevals theorem. *) function SINAD(ActualZeroPadding: Double = 1): Double; (*Spurious-free-dynamic-range computes the ratio of the largest marked peak towards the second largest. Marked peaks are stored in the Marks property. *) function SFDR: Double; (*Returns the ratio of RMS of the marked peaks, excluding the first, towards the first marked peak and returns the result in [%]. Marked peaks are stored in the Marks property *) function THD: Double; virtual; (*Searches for the closest peaks of the amplitude spectrum. Returns the frequency as the result. *) function ConvergeToMaximum(Freq: Double): Double; overload; (*Searches for the closest peaks of the amplitude spectrum. Stores the new peak frequency back in to the Freqs parameter. The center holds the central spectral line and is guaranteed not to be on the edge of the array. Even if the maximum value is in fact on the edge of the array both Freqs and Center will be moved one line inside to starting index 1 and final index Length-2. *) procedure ConvergeToMaximum(const Freqs: TVec; const Center: TVecInt); overload; (*Returns the RMS of the spectrum. *) function RMS(ActualZeroPadding: Double = 1): Double; (*The total-harmonic-distortion-and-noise computes the sum of sqares of the rest towards the maximum marked peak. All other marked peaks are considered to be noise. Marked peaks are stored in the Marks property. The width of the peaks is determined with the MainlobeWidth property. *) function THDN(ActualZeroPadding: Double = 1): Double; virtual; function IsNumericalInterpolationSupported: boolean; virtual; destructor Destroy; override; constructor Create(AOwner: TComponent); override; published (*Stores the amplitude of the spectrum.*) property Amplt: TVec read FAmplt write SetAmplt stored AllowDStreaming; (*Stores the phase of the spectrum. *) property Phase: TVec read FPhase write SetPhase stored AllowDStreaming; (*Returns the bandwidth of the spectrum in [Hz].*) property BandWidth: Double read fBandwidth; (*Defines the length of the spectrum. *) property Length: integer read FLength write SetLength default 64; (*Defines the width of the main lobe. Each frequency component in the frequency spectrum should theoretically be infinitely thin. Because of spectral leakege (Gibbs phenomenon), each spectral component has certain width (in [Hz]). This width depends on the spectral window used. (Hanning, Hamming etc..) The derived classes can set this property to match up to the spectral window used when computing the frequency spectrum. This parameter is used by different statistical methods to improve the accuracy of the estimate. (THDN, SINAD, RMS...). *) property MainlobeWidth: integer read FMainlobeWidth write SetMainlobeWidth default 8; (*This property determines how to handle the phase of the frequency spectrum. If you are not interested in phase set this property to pmIgnore. This can save considerable computation time. *) property PhaseMode: TPhaseMode read FPhaseMode write SetPhaseMode default pm360 ; (*Defines the units of phase. (radian, degrees).*) property PhaseUnits: TPhaseUnits read FPhaseUnits write SetPhaseUnits default puDeg ; (*Specify the range of the phase spectrum.*) property PhaseRange: TPhaseRange read FPhaseRange write SetPhaseRange; (*Defines the sampling frequency of the signal on which the frequency spectrum is based.*) property SamplingFrequency: Double read FSamplingFrequency write SetSamplingFrequency; (*Defines the length of the signal on which the frequency spectrum is based in seconds.*) property SamplingTime: Double read FSamplingTime write SetSamplingTime; (*Returns the frequency resolution in Hz.*) property HzRes: Double read FHzRes write SetHzRes; (*Returns the distance in seconds between two consecutive samples of the signal on which the frequency spectrum is based.*) property Dt:Double read FDt write SetDt; (*Defines number of harmonics to be used when calculating statistical parameters from the amplitude spectrum.*) property Harmonics: integer read FHarmonics write SetHarmonics default 10; (*Defines the level of zero padding of the source signal. While zero padding does not increase frequency resolution, it does improve the accuracy of the frequency, amplitude and phase estimation of the spectral component. *) property ZeroPadding: integer read FZeroPadding write SetZeroPadding default 1; end; (* Manages a list of TSpectrum objects. The component overrides the AddItem method and declares a new default array property returning TSpectrum type. *) TSpectrumList = class(TAnalysisList) strict private function GetItems(index: integer): TSpectrum; reintroduce; procedure SetItems(index: integer; const Value: TSpectrum); strict protected function AddItem: TMtxComponent; override; public constructor Create(AOwner: TComponent); override; (*Access TSpectrum objects by Index.*) property Items[index: integer]: TSpectrum read GetItems write SetItems; default; end; (*Handles a list of memory streams.*) TStringStreamList = class(TStringList ) strict private function GetStreams(Index: integer): TMemoryStream; procedure SetStreams(Index: integer; const Value: TMemoryStream); strict protected procedure ReadData(Reader: TReader); procedure WriteData(Writer: TWriter); procedure DefineProperties(Filer: TFiler); override; procedure SaveBinaryStream(Dst: TStream); overload; virtual; procedure LoadBinaryStream(Src: TStream); overload; virtual; procedure GetObjectToByteArray(var DstBuffer: Math387.TByteArray); procedure SetObjectFromByteArray(const SrcBuffer: Math387.TByteArray); public (*Initialize a new TMemoryStream named S and return the position in the list as the result.*) function Add(const S: string): integer; override; procedure Assign(Source: TPersistent); override; procedure Delete(Index: integer); override; procedure Clear; override; (*Use the streams property to access memory streams owned by the TStringStreamList.*) property Streams[Index: integer]: TMemoryStream read GetStreams write SetStreams; procedure LoadFromStream(Stream: TStream); overload; override; procedure SaveToStream(Stream: TStream); overload; override; (*Append new items from stream.*) procedure AppendFromStream(Stream: TStream); overload; virtual; (*Append new items from another list.*) procedure AppendFromList(List: TStringStreamList); virtual; destructor Destroy; override; end; (*An abstract class for stream capable TList. The class add's generic streaming capability to the basic TList object and also owns its items. All methods are virtual, but they could also be abstract. *) TStreamedList = class(TObjectsList) public procedure SaveToStream(Dst: TStream); overload; virtual; procedure LoadFromStream(Src: TStream); overload; virtual; constructor Create(AOwnsObjects: boolean); overload; procedure ClearPointers; virtual; procedure Update; virtual; end; (*Streamable list of lists. The class handles storing, loading, decoding and encoding of a list of streams. The list of streams is stored in the Templates property. Each stream holds another list of objects. This lists are of TStreamedList type. When the user sets the TemplateIndex or the TemplateName property, the appropriate stream is decoded to Template property, where individual objects contained in the list can be accessed. All changes to those objects are retained and Templates property is kept up-to-date. For TStreamTemplates to handle user defined type of objects, a new class should be derived from TStreamedList type. *) TStreamTemplates = class(TPersistent) strict private FTemplate: TStreamedList; FTemplateIndex: integer; FTemplates: TStringStreamList; procedure SetTemplateIndex(const Value: integer); procedure SetTemplateName(const Value: string); procedure SetTemplates(const Value: TStringStreamList); function GetTemplateName: string; strict protected FOwner: TComponent; function GetStreamedTemplate: TStreamedList; virtual; procedure SetTemplate(const Value: TStreamedList); public (*Holds a list of objects. All changes made to this list of objects are preserved, when you change TemplateName or TemplateIndex. *) property Template: TStreamedList read FTemplate write SetTemplate; (*Returns the owner object.*) property Owner: TComponent read FOwner; procedure Assign(Source: TPersistent); override; (*Add a new template (TMemoryStream) named S to the list.*) procedure AddTemplate(const S: string); virtual; (*Add a new template named S to the list of templates and set TemplateIndex to point to the template added. *) procedure AddCopyTemplate(const S: string); virtual; (*Clears the Templates and Template properties.*) procedure Clear; virtual; (*Delete the template at position Index from the Templates property.*) procedure Delete(Index: integer); virtual; constructor Create(AOwner: TComponent); overload; virtual; destructor Destroy; override; (*Call this method to ensure that Template and Templates properties are synchronized.*) procedure Consolidate; (*Calls the Template.Update method.*) procedure Update; virtual; (*Save all data to the stream. *) procedure SaveToStream(Stream: TStream); overload; virtual; (*Load all data from the stream.*) procedure LoadFromStream(Stream: TStream); overload; virtual; (*Save the object to the file.*) procedure SaveToFile(FileName: string); virtual; (*Load the object from the file.*) procedure LoadFromFile(FileName: String); virtual; published (*Holds the list of streams containing data to be decoded to the Template property. , once you set TemplateIndex of TemplateName property. *) property Templates: TStringStreamList read FTemplates write SetTemplates; (* Set TemplateIndex to decode the stream at position TemplateIndex to Template property. When the stream is decoded, a list of objects is created. Before the new stream is decoded, the data in Template property is encoded and stored to the Templates. *) property TemplateIndex: integer read FTemplateIndex write SetTemplateIndex default -1 ; (*Set TemplateName to decode the stream with name TemplateName to Template property. When the stream is decoded, a list of objects is created. Before the new stream is decoded, the data already in the Template property is encoded and stored to the Templates property. *) property TemplateName: string read GetTemplateName write SetTemplateName stored false; end; (*Copies a spectrum.. Copies Src from SrcIndex to Dst from Index to Index+Len. *) procedure CopySpectrum(Src,Dst: TSpectrum; SrcIndex, Index, Len: integer); overload; (*Defins fixed column width for text reports.. Defines the fixed column width for some ValuesToStrings, MarksToStrings, etc.. multi-column report generating routines. *) var SignalTextColumnWidth: integer = 25; (*General purpose signal processing routines.*) unit SignalUtils; interface {$I BdsppDefs.inc} {$DEFINE DOWAVELETS} uses AbstractMtxVec, MtxVec, MtxVecInt, Math387,Polynoms, MtxVecBase ,ippspl ,ippsplSingle ,SysUtils ; const (*Maximum number of taps for FIR filters. Defines the maximum number of taps returned by FIR length estimation routines. *) MaxFirLength = 10000; (*Product information string.*) DspForMtxVec: string = 'DSP Master v6.3.5'; type (* Defines signal monitoring type. When there is a need to monitor most recent data written use mmFromEnd. To monitor most recent data read, use mmFromStart. Typically when when we are recording, we want to monitor most recent data written and when there is playback we want to see the oldest data from the buffer just to be played. *) TMonitorMode = ( (*The data which is monitored from the buffer is the oldest and to be read next.*) mmFromStart, (*The data which is monitored from the buffer is the newest and to exit the buffer as the last.*) mmFromEnd); (*Defines FIR filter types. Not all routines accepting TFilterType variable can design all filter types. All IIR filter design routines are limited to ftLowpass, ftBandpass, ftHighpass and ftBandstop filter types. *) TFilterType = ( (*design a lowpass filter.*)ftLowpass, (*design a highpass filter.*)ftHighpass, (*design bandpass filter*)ftBandpass, (*design a bandstop filter*)ftBandstop, (*design a type III hilbert transformer.*)ftHilbertIII, (*design a type IV hilbert transformer.*)ftHilbertIV, (*design a type III differentiator.*)ftDifferentiatorIII, (*design a type IV differentiator.*)ftDifferentiatorIV, (*design a type III differentiator.*)ftDoubleDifferentiatorIII, (*design a type IV differentiator.*)ftDoubleDifferentiatorIV, (*design a type III integrator.*)ftIntegratorIII, (*design a type IV integrator.*)ftIntegratorIV, (*design a type III integrator.*)ftDoubleIntegratorIII, (*design a type IV integrator.*)ftDoubleIntegratorIV ); TMusicalNotePitch = ( (*C Note*) mnC, (*C# Note*)mnCSharp, (*D Note*) mnD, (*D# Note*)mnDSharp, (*E Note*)mnE, (*F Note*)mnF, (*F# Note*)mnFSharp, (*G Note*)mnG, (*G# Note*)mnGSharp, (*A Note*)mnA, (*A# Note*)mnASharp, (*B Note*)mnB); TGoertzPhaseCorrection = ( (*Simple DFT*) gpcNone, (*Bonzanigo phase correction*) gpcBonzanigo); TDelayFilterState = record SampleCount: integer; Data: TVec; end; (*Filter state used by FIR filtering methods. FIRState is the filter state defined by Intel IPP. UpSample stores the upsampling factor and DownSample stores the downsampling factor of a multi-rate filter. ComplexImpulse is True, if FIR filter has a complex impulse response. This state is initialized by FirInit method. *) TFirState = record State_Re: PAPointer; State_Im: PAPointer; Statec: PAPointer; UpSample: integer; DownSample: integer; ComplexImpulse: boolean; Initialized: boolean; IsDouble: boolean; FloatPrecision: TMtxFloatPrecision; end; (*Filter state used by sample-and-hold filtering routines. The value parameter stores the value to be held and the hold parameter specifies the time-out. *) TSampleAndHoldState = record Value: TCplx; Hold: integer; end; (*Type used by circular buffering. Record used to hold the state for circular buffering. WritePosition is the current write position index in the buffer and the ReadPosition is the current read position index. BufferSize is initialized with a call to InitCircularBuffer. BufferOverrun flag is set by WriteToCircularBuffer routine, if unread data is overwritten. BufferUnderrun flag is set by the ReadFromCircularBuffer routine, if the data read was already overwritten before it was read for the first time. *) TCircularBufferState = record ReadPosition: integer; BufferSize: integer; DataLen: integer; IsBufferOverrun: boolean; IsBufferUnderrun: boolean; IncrementStep: double; end; TCztState = class protected srcLen: integer; srcFloatPrecision: TMtxFloatPrecision; k: integer; FStart: double; FStop: double; FS: double; RStart: double; RStop: double; av: TVec; bv: TVec; dv: TVec; b2: TVec; public constructor Create; destructor Destroy; override; end; (*Type used by median filter. The record type is used to hold the configuration and the delay line of the median filter between consecutive calls to the MedianFilter method. The record is initialized when passed to the MedianInit method. *) TMedianState = record State: PAPointer; FloatPrecision: TMtxFloatPrecision; MaskSize: integer; Data: TVec; Buffer: TVec; end; (*Stereo channel selection. Select one of the two channels, right or left. The type is used by components dealing with stereo audio signals. *) TChannel = ( (**)chLeft, (**)chRight, (**)chThird, (**)chFourth, (**)chFifth, (**)chSixth ); (*Defines wavelet types. Some of the wavelets require aditional parameters. The par1 and par2 arguments have the following meaning: (Reference: Intel Signal Processing library v4.5, Manual, 10-4) *) TWaveletType = ( (*par1 and par2 are dummies.*) wtHaar, (*par1 is the order of the wavelet (1 to 10).*) wtDaublet, (*par1 is the order of the wavelet (1 to 7).*) wtSymmlet, (*par1 is the filter length parameter (1 to 5).*) wtCoiflet, (*par1 and par2 are dummies.*) wtVaidyanathan, (*admissable combinations: (1,1), (1,3), (1,5) for box splines; (2,2), (2,4), (2,6), (2,8) for linear splines; (3,1), (3,3), (3,5), (3,7), (3,9) for quadratic splines.*) wtBSpline, (*admissable combinations: (1,1), (1,3), (1,5) for box splines; (2,2), (2,4), (2,6), (2,8) for linear splines; (3,1), (3,3), (3,5), (3,7), (3,9) for quadratic splines.*) wtBSplineDual, (*par1 and par2 are dummies.*) wtLinSpline, (*par1 and par2 are dummies.*) wtQuadSpline, (**) wtByFilter ); (*Defines the result type of wavelet decomposition.*) TWaveletDecomp = ( (*the result will be the "approximation".*)wtApproximation, (*the result will be the "detail".*)wtDetail); (*Window function definition. Specifies window function type to be applied to the signal prior to be passed to the FFT algorithm. *) TSignalWindowType = ( (*apply no windowing.*) wtRectangular, (*hanning window.*) wtHanning, (*hamming window.*) wtHamming, (*flat-top window.*) wtFlatTop, (*Bartlet window.*) wtBartlett, (*Blackman window.*) wtBlackman, (*blackman-harris window.*) wtBlackmanHarris, (*exact Blackman window.*) wtBlackmanExact, (*cosine tappered window.*) wtCosineTappered, (*Kaiser window.*) wtKaiser, (*Chebyshev window.*) wtChebyshev, (*Exponent-down window.*) wtExponent ); (*Window function definition.*) TSignalWindowMode = ( (*The initial and last zero element are left out, if present. This is required for windowed digital filter design. The window is symmetric.*) wmSymmetric, (*The initial element is kept and the last is left out. This is required to retain periodicity of the signal windowed and analyzed with frequency analysis (FFT) for the following window types: Hanning, Hamming, FlatTop and variants of the Blackman window.*) wmPeriodic); (*Defines frequency spectrum types. *) TSpectrumType = ( (*amplitude spectrum.*)spAmplt, (*RMS spectrum.*)spRMS, (*Power spectrum.*)spPower, (*Peak-to-Peak spectrum.*)spPeakPeak, (*Real cepstrum.*) spRealCepstrum, (*Complex spectrum.*)spComplex ); (*Set of predefined FIR allpass filters suitable for fractional delay filtering. Filter specifications have been relaxed to allow variation of the amplitude in the stop band between 0 and 1 linear scale, but keeping the ripple specification in the passband. When unclear what to select, falp_60dB is recommended. *) TFractionalImpulse = ( (*Passband ripple -160dB, cutoff at 0.82, integer delay of 30 samples.*) falp_160dB, (*Passband ripple -140dB, cutoff at 0.85, integer delay of 30 samples.*) falp_140dB, (*Passband ripple -140dB, cutoff at 0.94, integer delay of 80 samples.*) falp_140dB2, (*Passband ripple -100dB, cutoff at 0.9, integer delay of 30 samples*) falp_100dB, (*Passband ripple -60dB, cutoff at 0.93, integer delay of 30 samples.*) falp_60dB, (*Passband ripple -50dB, cutoff at 0.82, integer delay of 7 samples.*) falp_50dB, (*Passband ripple -50dB, cutoff at 0.6, integer delay of 3 samples.*) falp_50dB2); (*Filter state type used by ConsistentParameter filter method. Filter state stores the parameter (Param) to be tracked and the total Time, within which this parameter has not changed its value. *) TConsistentParam = record Param: double; Time: TDateTime; end; (*Filter state used by IIR filtering methods. NSPIirState is the filter state defined by Intel IPP. ComplexImpulse is True, if Iir filter has a complex impulse response. This state is initialized by IirInit method. *) TIirState = record NSPIirState: PAPointer; NSPIirStatec: PAPointer; Buffer: Math387.TByteArray; Bufferc: Math387.TByteArray; IsDouble: boolean; ComplexImpulse: boolean; Initialized: boolean; end; (*Type used for sine function generation. Type required for tone (sine) signal generation. Complex is True, if the resulting sine wave should be complex. *) TToneState = record Freq: double; Mag: double; Phase: double; Complex: boolean; Negative: boolean; EdgeFreq: boolean; Initialized: boolean; end; (*Type used for triangle tone generation. Type required for (triangle) signal generation. Complex is True, if the resulting sine wave should be complex. *) TTriangleState = record Freq: double; Mag: double; Phase: double; Asym: double; Complex: boolean; Initialized: boolean; end; TToneStateArray = array of TToneState; (*Type used for square signal function generation. Type required for square signal generation. Complex is True, if the resulting sine wave should be complex. *) TSquareToneState = record Freq: double; Mag: double; Phase: double; Initialized: boolean; Tones: TToneStateArray; end; (*Type used by the PhaseUnwrap procedure. *) TRunningPhase = ( (*The phase will be unwrapped and no further processing will be applied.*) rpDefault, (*The phase will be unwrapped and, if actual amount of zero padding is known, the resulting phase spectrum will be showing the exact phase relations between frequencies with the first value of the phase spectrum having zero phase. This type of a phase spectrum can be used to compute the delay in ms separately for each frequency.*) rpConstPhase, (*Integer lag will be subtracted, but the phase will not be unwrapped.*) rpIntegerLag, (*The phase will be unwrapped and the resulting phase spectrum will be showing the approximate phase relations between frequencies with the first value and the value at FS/2 (at Pi radians) having zero phase.*) rpPartial); (*Type used by the numerical differentiation routine. The type stores the initial conditions for the numerical differentiation. Start2R and Start2I are real and imaginary part of the first condition (x[-2]) and Start1R, Start1I are the real and imaginary part of the second condition (x[-1]). *) TDiffState = record Start2R, Start2I, Start1R, Start1I: double; end; (*Type used by the numerical integration routine. The type stores the initial conditions for the numerical integration. StartR2 and StartI2 are real and imaginary part of the first condition (x[-2]) and StartR1, StartI1 are the real and imaginary part of the second condition (x[-1]). *) TIntegrateState = record SumR1, SumI1, StartR1, StartI1, StartR2, StartI2: double; end; (*Bartlett window. Applies Bartlett window to Src vector. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. Bartlett window is a "triangular" window defined as [1] p. 248: 2*n M - 1 w[n] = -------, 0 <= n <= ------ M - 1 2 2*n M - 1 w[n] = 2 - -------, ------ <= n <= M - 1 M - 1 2 w[n] = 0, for other n's References: [1] Digital signal processing, Vinay K. Ingle and John G. Proakis, Brooks-Cole, 2000. Compute the frequency response of a lowpass filter with a cutoff at 40 Hz, if the sampling frequency is 200Hz and the filter is designed with the bartlett window. using Dew.Math; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector h = new Vector(100); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); SignalUtils.FirImpulse(h, new double[1] {40},0, TFilterType.ftLowpass, TSignalWindowType.wtRectangular,1,200); SignalUtils.Bartlett(h); //window the sinc impulse response SignalUtils.FrequencyResponse(h,null,Response,8,false, TSignalWindowType.wtRectangular, 0); FreqFr.Size(Response.Length); FreqFr.Ramp(0,200*0.5/Response.Length); MtxVecTee.DrawIt(FreqFr, Response,"Frequency response",false); } *) function Bartlett(const Src: TVec): TVec; overload; (*Bartlett window. Applies Bartlett window to Src vector from element at Index to element at Index + Len - 1. *) function Bartlett(const Src: TVec; Index: integer; Len: integer): TVec; overload; (*Blackman window. Applies Blackman window with alfa parameter to Src. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. The window is defined as [1] p. 6-7: alpha + 1 2*Pi*n alpha 4*Pi*n w[n] = ---------- - 0.5*cos -------- - ------ * cos(-------) 2 n - 1 2 n - 1 alpha = -0.16 (standard window) alpha = - 0.25 (asymptotic optimal window for large n) w[n] = 0.42 - 0.5*cos(2*Pi*n/(M-1)) + 0.08*cos(4*Pi*n/(M-1)) 0 <= n <= M - 1 References: [1] Intel IPP SPL v5.3 manual Compute the frequency response of a lowpass filter with a cutoff at 40 Hz, if the sampling frequency is 200Hz and the filter is designed with the blackman window. using Dew.Math; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector h = new Vector(100); Vector Response = new Vector(0); Vector FreqFr = new Vector(0); SignalUtils.FirImpulse(h, new double[1] {40},0, TFilterType.ftLowpass, TSignalWindowType.wtRectangular,1,200); SignalUtils.Blackman(h,0.1,TSignalWindowMode.wmSymmetric); //window the sinc impulse response SignalUtils.FrequencyResponse(h,null,Response,8,false, TSignalWindowType.wtRectangular, 0); FreqFr.Size(Response.Length); FreqFr.Ramp(0,200*0.5/Response.Length); MtxVecTee.DrawIt(FreqFr, Response,"Frequency response",false); } *) function Blackman(const Src: TVec; alfa: double; WindowMode: TSignalWindowMode): TVec; overload; (*Blackman window. Applies Blackman window to Src vector from element at [Index] to element at [Index + Len - 1]. *) function Blackman(const Src: TVec; alfa: double; WindowMode: TSignalWindowMode; Index: integer; Len: integer): TVec; overload; (*Exact blackman window. Applies exact Blackman window to Src. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. w[n] := 0.42659071 - 0.49656062*cos(2*Pi*n/(n-1)) + 0.07684867*cos(4*Pi*n/(n-1)) 0 <= n <= M - 1 Reduce spectral leakage of the FFT by first applying exact Blackman window to the the signal. using Dew.Math; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector h = MtxExpr.Sin(MtxExpr.Ramp(256, TMtxFloatPrecision.mvDouble,0, 2 * Math387.PI * 0.1)); Vector h1 = new Vector(0); Vector Response = new Vector(0); Vector Response1 = new Vector(0); h1.Copy(h); SignalUtils.BlackmanExact(h, TSignalWindowMode.wmSymmetric); //window the sinc impulse response MtxVecTee.DrawIt(h,"",false); h = h * 2.344168; //scale the signal to compensate for energy loss SignalUtils.FrequencyResponse(h1,null, Response,8,false, TSignalWindowType.wtRectangular, 0); SignalUtils.FrequencyResponse(h, null, Response1, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(new TVec[2] { Response, Response1 }, new string[2] { "Rectangular window", "Exact blackman" }, "Frequency response", false); } *) function BlackmanExact(const Src: TVec; WindowMode: TSignalWindowMode): TVec; overload; (*Exact Blackman window. Applies Exact Blackman window to Src vector from element at Index to element at Index + Len - 1. *) function BlackmanExact(const Src: TVec; WindowMode: TSignalWindowMode; Index: integer; Len: integer): TVec; overload; (*Blackman-Harris window. Applies BlackmanHarris window to Src. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. w[n] := 0.42323 - 0.49755*cos(2*Pi*n/(n-1)) + 0.07922*cos(4*Pi*n/(n-1)) 0 <= n <= M - 1 *) function BlackmanHarris(const Src: TVec; WindowMode: TSignalWindowMode): TVec; overload; (*Blackman Harris window. Applies exact Blackman Harris window to Src vector from element at Index to element at Index + Len - 1. *) function BlackmanHarris(const Src: TVec; WindowMode: TSignalWindowMode; Index: integer; Len: integer): TVec; overload; (*Optimal Blackman window. Applies optimal Blackman window to Src. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. The following function defines the Blackman window: alpha + 1 2*Pi*n alpha 4*Pi*n w[n] = ---------- - 0.5*cos -------- - ------ * cos(-------) 2 n - 1 2 n - 1 sin(Pi/(n-1)) alpha = - (---------------)^2 sin(2*Pi/(n-1)) *) function BlackmanOptimal(const Src: TVec; Index: integer = 0; Len: integer = -1): TVec; overload; (*Chebyshev's window. Applies Chebyshevs equiripple window to Src. The attenuation of the spectral leakage is defined with the AttdB parameter in [dB]. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. *) function Chebyshev(const Src: TVec; AttdB: double; Index: integer = 0; Len: integer = -1): TVec; overload; (*Cosine tappered window. Applies Cosine tappered window to Src. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. When percent parameter is 100, the CosineTappered window becomes equal to the hanning window. When the Percent parameter is 0, then the window remains rectangular (box car). *) function CosineTappered(const Src: TVec; Percent: double; Index: integer = 0; Len: integer = -1): TVec; overload; (*Exponential window. Applies Exponential window to Src. Exponential window is used with impulse responses to attenuate noise. With impulse response, the noise is present throughout the signal and its average value is higher then the value of the signal because the signal has a much shorther duration. Because the noise is broadband, the modal frequency might not show up in the frequency spectrum or it would be very small, if the signal would not be windowed with an exponential window. *) function ExponentWindow(const Src: TVec; Att: double; Index: integer = 0; Len: integer = -1): TVec; overload; (*Flat-top window. Applies Flat top window to Src. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. Flat top window was used to get more accurate amplitude information when frequency interpolation routines where not yet common. w[i] = 0.2810639 - 0.5208972*cos(2*Pi*i/(n-1)) + 0.1980399*cos(4*Pi*i/(n-1))) 0 < i < n-1 *) function FlatTop(const Src: TVec; WindowMode: TSignalWindowMode): TVec; overload; (*Flat top window. Applies Flat top window to Src vector from element at Index to element at Index + Len - 1. *) function FlatTop(const Src: TVec; WindowMode: TSignalWindowMode; Index: integer; Len: integer): TVec; overload; (*Hamming window. Applies Hamming window to Src. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. Hamming window can be found in [1] p. 249 w[i] = 0.54 - 0.46*cos(2*Pi*i/(n-1)) 0 <= i <= n-1 References: [1] Digital signal processing, Vinay K. Ingle and John G. Proakis, Brooks-Cole, 2000. *) function Hamming(const Src: TVec; WindowMode: TSignalWindowMode): TVec; overload; (*Hamming window. Applies Hamming window to Src vector from element at Index to element at Index + Len - 1. *) function Hamming(const Src: TVec; WindowMode: TSignalWindowMode; Index: integer; Len: integer): TVec; overload; (*Hanning window. Applies Hanning window to Src. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. Hanning window is very widely used and sufficient for most applications. The equation for the Hanning window can be found in [1] p. 249: w[i] = 0.5*(1-cos(2*Pi*i/(n-1)) 0 < i < n-1 References: [1] Digital signal processing, Vinay K. Ingle and John G. Proakis, Brooks-Cole, 2000. *) function Hanning(const Src: TVec; WindowMode: TSignalWindowMode): TVec; overload; (*Hamming window. Applies Hamming window to Src vector from element at Index to element at Index + Len - 1. *) function Hanning(const Src: TVec; WindowMode: TSignalWindowMode; Index: integer; Len: integer): TVec; overload; (*Kaiser window. Applies Kaiser window to Src. Beta parameter controls the width the of the mainlobe and spectral leakage. Beta allows you to find a compromise between the frequency resolution and attenuation of the spectral leakage. Window functions are applied to the signal prior to conversion to frequency domain with the FFT algorithm, to reduce the spectral leakage. Their side-effect is a lower frequency resolution. If a specified sidelobe attenuation is required for spectrum analysis, the appropriate beta can be found with the routine. If a specified sidelobe attenuation is required for a FIR filter, the appropriate beta can be found with the routine. The equation for Kaiser window and application to the FIR filter design can be found in [1] p. 453. References: [1] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989. *) function Kaiser(const Src: TVec; Beta: double): TVec; overload; (*Kaiser window. Applies Kaiser window to Src vector from element at Index to element at Index + Len - 1. *) function Kaiser(const Src: TVec; Beta: double; Index: integer; Len: integer): TVec; overload; (*Fractional Kaiser window. Additional parameters in compare to the function, are the Offset and Step. They allow the windowing function to be sampled at the same points as . This function should therefore be called when windowing an impulse response generated with FractionalFirImpulse. *) function FractionalKaiser(const Src: TVec; Beta: double; Offset, Step: double): TVec; overload; (*Fractional Kaiser window. Applies fractional Kaiser window to Src vector from element at Index to element at Index + Len - 1. *) function FractionalKaiser(const Src: TVec; Beta: double; Offset, Step: double; Index, Len: integer): TVec; overload; (*Convert a TSignalWindowType to a string. The function returns a description of a TSignalWindowType specified by the WindowType variable. *) function SignalWindowToString(WindowType: TSignalWindowType): string; (*Apply a time window function to the signal. Apply a window to data in Vec. WindowParam should contain the parameter, required by the window. If the window does not have a parameter, windowParam can have any value. Set ScaleNorm to true, to request scaling of Vec such, that the energy of the stationary signal will be preserved after windowing. *) procedure SignalWindow(const Vec: TVec; WindowType: TSignalWindowType; WindowParam: double; WindowMode: TSignalWindowMode; ScaleNorm: boolean = false); overload; (*AApply a time window function to the signal. Applies user window to Vec vector from element at Index to element at Index + Len - 1. *) procedure SignalWindow(const Vec: TVec; WindowType: TSignalWindowType; WindowParam: double; WindowMode: TSignalWindowMode; ScaleNorm: boolean; Index: integer; Len: integer); overload; (*Applies hilbert transform to Src. Applies hilbert transform to Src. Src must be real signal. The result is complex. Hilbert transform generates a 90 degree phase shifted version of the original. This becomes the imaginary part of the complex signal. This routine is very usefull for single block processing, but can not be used for streaming data. Use a digital FIR filter based hilbert transformer for streaming data or resort to quadrature sampling techniques [1], p. 297. References: [1] Understanding digital signal processing. Richard G. Lyons, Prentice-Hall, 2001. Hilbert transform of a sine signal is computed for two cases: * frequency of the sine falls exactly on the spectral bin (ideal case) * frequency of the sine is not alligned with the frequency spectrum grid. using Dew.Math; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Math.Editors; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector h = MtxExpr.Sin(MtxExpr.Ramp(256, TMtxFloatPrecision.mvDouble, 0, 2 * Math387.PI * 6/256)); Vector Re = new Vector(0); Vector Im = new Vector(0); Vector h1 = new Vector(0); SignalUtils.Hilbert(h); h.CplxToReal(Re, Im); MtxVecTee.DrawIt(new TVec[2] {Re,Im}, new string[2] {"Real","Imag"} ,"Integer frequency",false); h1.SetIt(false,new double[1] {Re.DotProd(Im)}); MtxVecEdit.ViewValues(h1,"Dot product between Re and Im",true); h = MtxExpr.Sin(MtxExpr.Ramp(256, TMtxFloatPrecision.mvDouble,0, 2 * Math387.PI * 6.5/256)); SignalUtils.Hilbert(h); h.CplxToReal(Re, Im); MtxVecTee.DrawIt(new TVec[2] {Re,Im}, new string[2] {"Real","Imag"} ,"Non-integer frequency",false); h1.SetIt(false,new double[1] {Re.DotProd(Im)}); MtxVecEdit.ViewValues(h1,"Dot product between Re and Im",true); h = MtxExpr.Sin(MtxExpr.Ramp(256, TMtxFloatPrecision.mvDouble, 0, 2 * Math387.PI * 6.5/256)); Re.Copy(h); Im.Copy(h); SignalUtils.KaiserImpulse(h1,new double[2] {0.95,1}, 0.01, TFilterType.ftHilbertIII,1,2,true); //Or use remez: OptimalFIR.RemezImpulse(h1,new double[2] {0.05,0.95}, 0.01, TFilterType.ftHilbertIII); SignalUtils.FirFilter(Im,h1,1,1); //also compensates for integer filter delay (if filter is Odd length (type III)) MtxVecTee.DrawIt(new TVec[2] {Re,Im}, new string[2] {"Real","Imag"} ,"With FIR Filter",false); h1.SetIt(false,new double[1] {Re.DotProd(Im)}); //dot product between Re and Im should be zero MtxVecEdit.ViewValues(h1, "Dot product between Re and Im", true); } *) function Hilbert(const SrcDst: TVec): TVec; overload; (*Differentiate the signal. Differentiate the signal Src and place the result in Dst, The routine can also be used for streaming data, if the value of the State parameter is preserved between consecutive calls. dT defines the sampling period. Src can be complex or real. For setting the initial conditions to other then 0 see the TDiffState description. The following formula is used for numeric differentiation: y[i] = (x[i] - x[i-2]) / (2 * dT) dT ... sampling period x - input y - output The filter does not preserve linear phase, but it is more accurate than: y[i] = (x[i]-x[i-1])/dT which can be rewriten as: y[i] = ((x[i] - x[i-1]) + (x[i-1] - x[i-2]))/2 * 1/dT To apply a differentiator preserving linear phase see the and routines. Note Record types as well as all other variables are automatically initialized to zero, if they are declared as fields of an object. The state variable of the Differentiate routine has to be initialized before it can be used, if declared within a routine. *) function Differentiate(const Src, Dst: TVec; var State: TDiffState; Dt: double): TVec; overload; (*Integrate signal. Use Simpson's formula to integrate the Src and place the result in Dst. Src can be real or complex. dT defines the sampling period (dT = 1/FS). The State variable holds the initial conditions. j Dst[j] = 1/6* Sum ( Src[i-2] + Src[i-1]*4 + Src[i])*dT) i=0 j = 0,1...n-1 The integrate routine does not preserve linear phase. An analytical solution for a linear phase integrator does not exists. In general there are two types of applications for integration: 1. The signal has a more or less fixed mean value. (DC offset). An example of such a signal is the signal comming from the accelerometer measuring vibrations. The numerical integration will work well on signals whose mean value is exactly zero (otherwise it will rise or fall in infinity). Because this is often not the case, the signal must be passed through a DC filter first. The DC filter can be IIR or FIR type. An alternative to numerical integration and the Integrate routine is a linear phase integration filter designed with the routine. Linear phase integrator also removes the DC offset. 2. The signal does not have a mean value. An example of such a signal is the signal comming from the accelerometer measuring the acceleration and deceleration of a driving car. In this case the Integrator routine can be used directly to obtain speed and/or distance from the acceleration data. Single block and streaming application example: using Dew.Math; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Math.Editors; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { int n,i; double FS = 1; //sampling frequency TIntegrateState State2 = new TIntegrateState(); TDiffState State = new TDiffState(); Vector h = MtxExpr.Ramp(30, TMtxFloatPrecision.mvDouble, 0, 1); Vector b = new Vector(h.Length); Vector c = new Vector(h.Length); //single block SignalUtils.Integrate(h, b, ref State2, 1.0/FS); SignalUtils.Differentiate(b, c, ref State, 1.0 / FS); MtxVecTee.DrawIt(c,"Processed in one block",false); //streaming //reset initial conditions State2 = new TIntegrateState(); State = new TDiffState(); n = h.Length / 10; //integer division (!) for (i = 0; i < 10; i++) { h.SetSubRange(i*n,n); b.SetSubRange(i*n,n); c.SetSubRange(i*n,n); SignalUtils.Integrate(h,b, ref State2, 1.0/FS); // Should be: b = [0 , 1 , 3, 6, 10, 15, 21,... ] // But becomes: b = [0, 0.1666, 1.1666, 3.166, 6.166, 10.166, 15.166, 21.166,... ] // because of Simpson SignalUtils.Differentiate(b,c, ref State,1.0/FS); // Should be: c = [0,1,2,3,4,5,6....] // But becomes: c = [0, 0.08333, 0.5833, 1.5, 2.5, 3.5, 4.5....] } c.SetFullRange(); MtxVecTee.DrawIt(c,"Processed per partes, but same result",false); } *) procedure Integrate(const Src, Dst: TVec; var State: TIntegrateState; Dt: double); overload; (*Demultiplex a channel. Dempultiplex ChannelNr from Src to Dst, if number of channels is ChannelCount. ChannelNr is zero based. Multiplex and demultiplex test. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); Vector h = MtxExpr.Ramp(6, TMtxFloatPrecision.mvDouble,0,1); // h = [0 1 2 3 4 5] SignalUtils.Multiplex(h,b,3,2); // b = [0 0 0 0 0 1 0 0 2 0 0 3 0 0 4 0 0 5] SignalUtils.Demultiplex(b,c,3,2); // c = [0 1 2 3 4 5] MtxVecTee.DrawIt(c,"",false); } *) procedure Demultiplex(const Src, Dst: TVec; ChannelCount, ChannelNr: integer); overload; (*Multiplex a channel. Multiplex Src as channel ChannelNr to Dst, if there is ChannelCount channels. ChannelNr is zero based. *) procedure Multiplex(const Src, Dst: TVec; ChannelCount, ChannelNr: integer); overload; (*Bessel function of the first kind. Bessel function of the first kind used by the Kaiser routine. The description can be found in [1] p. 457. References: [1] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989. *) function BesselI0(X: Double): Double; overload; (*Returns beta parameter for the Kaiser window FIR filter. Compute Beta parameter for the Kaiser window applied to the FIR filter, where the passband has specified Ripple. The definition can be found in [1] p. 453. References: [1] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989. *) function KaiserBetaFir(Ripple: double): double; overload; (*Returns beta parameter for frequency analysis with the Kaiser window. Compute Beta parameter for the Kaiser window applied to the signal for the purpose of frequency analysis, where spectral leakage is limited with Ripple. The definition can be found in [1] p. 704, eq. 11.13. References: [1] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989. *) function KaiserBetaWindow(Ripple: double): double; overload; (*Returns beta parameter for frequency analysis with the Kaiser window. Compute Beta parameter for the Kaiser window applied to the signal, where spectral leakage is attenuated at least Att decibels. Attenuation is estimated from ripple as: Att[dB] = -20*Log10(Ripple); Ripple = Exp10(Att/-20); *) function KaiserBetaWindowAtt(Att: double): double; overload; (*Estimate the length of a windowed FIR filter. Returns the length of the FIR filter, windowed with the Kaiser window, where the maximum allowed ripple of the pass band is Ripple and sampling frequency is FS. The W array holds two parameters: the start and the stop of the narrowest transition band, relative to the specified sampling frequency. The equation can be found in [1] p. 453, eq. 7.93. The length of the filter designed with kaiser window is about 10% bigger then the length of the filter with the same specifications designed with the remez algorithm. The ripple of the passband and the stopband attenuation of a FIR filter designed with a Kaiser window are related with the equations: Att[dB] = -20*Log10(Ripple); Ripple = Exp10(Att/-20); References: [1] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989. Design a highpass filter with at least 80 dB attenuation in the stopband and not more then 0.0001 ripple in the passband. Transition band is between 0.5 and 0.6 Hz. Sampling frequency is 2 Hz. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector h = new Vector(0); Vector response = new Vector(0); double FS = 2; double Ripple = 0.0001; int n = SignalUtils.KaiserFirLength(new double[2] {0.5,0.6}, Ripple, FS); n = Math387.EnsureRange(4, n, SignalUtils.MaxFirLength); if (n % 2 == 0) { n++; //must be odd, if passband at FS/2 } SignalUtils.FirImpulse(h.Size(n), new double[2] { 0.5, 0.6 }, TFilterType.ftHighpass, FS); //get impulse response SignalUtils.Kaiser(h,SignalUtils.KaiserBetaFir(Ripple)); //apply Kaiser window SignalUtils.FrequencyResponse(h,null,response,8,false,TSignalWindowType.wtRectangular,0); //zero padd by 8x MtxVecTee.DrawIt(20*MtxExpr.Log10(MtxExpr.Abs(response)),"Highpass FIR filter",false); } *) function KaiserFirLength(W: array of double; Ripple: double; FS: double = 2): integer; overload; (*Design a FIR filter with rectangular window. Compute a FIR impulse response filter (no window applied) and place the result in H. The transition regions are defined with the W array. There must be at least one (lowpass, highpass) and at most two (bandpass, bandstop) transition regions (2 or 4 elements). Filter type is defined with TFilterType. The length of the filter H.Length must be preset. Filters of even length (odd order), must have a stop band next to the nyquist (FS/2) frequency. 20*Log10(Ripple) is also the required attenuation of the stop band in decibel. FS is the sampling frequency. Impulse responses of FIR filters. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector h = new Vector(0); Vector response = new Vector(0); Vector x = new Vector(0); double FS = 2; int n = 41; //lowpass design with cutoff frequency SignalUtils.FirImpulse(h.Size(n), new double[1] {0.5}, TFilterType.ftLowpass,FS); SignalUtils.FrequencyResponse(h, null, response, 8, false, TSignalWindowType.wtRectangular, 0); x = MtxExpr.Ramp(response.Length, TMtxFloatPrecision.mvDouble, 0, 1.0/response.Length); MtxVecTee.DrawIt(x,response,"Lowpass with cutoff at 0.5",false); //lowpass design with transition band SignalUtils.FirImpulse(h.Size(n), new double[2] {0.3,0.5}, TFilterType.ftLowpass,FS); SignalUtils.FrequencyResponse(h, null, response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(x,response,"Lowpass with transition band: 0.3-0.5",false); //Highpass design with cutoff frequency SignalUtils.FirImpulse(h.Size(n), new double[1] {0.5}, TFilterType.ftHighpass,FS); SignalUtils.FrequencyResponse(h, null, response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(x,response,"Highpass with cutoff at 0.5",false); //Highpass design with transition band SignalUtils.FirImpulse(h.Size(n), new double[2] {0.3,0.5}, TFilterType.ftHighpass,FS); SignalUtils.FrequencyResponse(h, null, response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(x,response,"Highpass with transition band: 0.3-0.5",false); //Bandpass design with transition bands SignalUtils.FirImpulse(h.Size(n), new double[4] {0.3,0.4,0.5,0.6}, TFilterType.ftBandpass,FS); SignalUtils.FrequencyResponse(h, null, response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(x,response,"Bandpass with transitoin band: 0.3-0.4, 0.5-0.6",false); //Bandstop design with transition bands SignalUtils.FirImpulse(h.Size(n), new double[4] {0.3,0.4,0.5,0.6}, TFilterType.ftBandstop,FS); SignalUtils.FrequencyResponse(h, null, response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(x,response,"Bandstop with transition bands: 0.3-0.4, 0.5-0.6",false); //Hilbert III SignalUtils.FirImpulse(h.Size(n), new double[1] {0}, TFilterType.ftHilbertIII,FS); SignalUtils.FrequencyResponse(h,null,response,8,false,TSignalWindowType.wtRectangular,0); MtxVecTee.DrawIt(x,response,"Hilbert III",false); //Hilbert IV n++; //n must be even SignalUtils.FirImpulse(h.Size(n), new double[1] {0}, TFilterType.ftHilbertIV,FS); SignalUtils.FrequencyResponse(h, null, response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(x,response,"Hilbert IV", false); //Differentiator III n--; // n must be odd SignalUtils.FirImpulse(h.Size(n), new double[1] {0}, TFilterType.ftDifferentiatorIII,FS); SignalUtils.FrequencyResponse(h, null, response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(x,response, "Differentiator III",false); //Differentiator IV n++; //n must be even SignalUtils.FirImpulse(h.Size(n), new double[1] {0}, TFilterType.ftDifferentiatorIV,FS); SignalUtils.FrequencyResponse(h, null, response, 8, false, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(x,response, "Differentiator IV", false); } *) procedure FirImpulse(const H: TVec; W: array of double; FilterType: TFilterType; FS: double = 2); overload; (*Computes just impulse response to preset length, no windowing*) (*Compute a FIR impulse response filter with user defined Window type. Compute a FIR impulse response filter with user defined Window type applied and place the result in H. Gain defines the filter gain. WindowParam holds the parameter (if required) for the window function. In case of a Kaiser window WindowParam should define the Beta parameter. The H.FloatPrecision determines the precision on Input *) procedure FirImpulse(const H: TVec; W: array of double; WindowParam: double; FilterType: TFilterType; WindowType: TSignalWindowType; Gain: double = 1; FS: double = 2); overload; (*Filter with any window, length must be preset*) (*Design an oversampled FIR filter with rectangular window. Compute a FIR impulse response filter (no window applied) and place the result in H. The transition regions are defined with the W array. There must be at least one (lowpass, highpass) and at most two (bandpass, bandstop) transition regions (2 or 4 elements). Filter type is defined with TFilterType. 20*Log10(Ripple) is also the required attenuation of the stop band in decibel. FS is the sampling frequency. Length specifies the length of the original filter and Step defines the oversampling factor. The actual length of the impulse response vector is computed like this: H.Length := Round(Length/Step) Step and offset must be bigger then 0. If the Offest is 0 and step is 1, the routine returns the same result as FirImpulse. The resulting H vector contains FIR type impulse response, which can be passed to an interpolation routine (linear, cubic, lagrange, etc..). Oversampled FIR filters are used for resampling with an arbitrary sampling frequency and, if Offset > 0 and Step = 1, fractional delay FIR filters can be implemented. If Step = 1 then the resulting impulse response can be passed directly to the FirInit and FirFilter routines. When setting Step bigger then 1, the filter designed must also work as an anti-aliasing filter (low-pass) or aliasing will occur. Filter interpolation. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector h = new Vector(0); Vector x = new Vector(0); double FS = 2; double Step = 1; //interpolate by 1x Step = 1; SignalUtils.FractionalFirImpulse(30,h,new double[2] {0.5,0.6},0,Step,TFilterType.ftLowpass,FS); x = MtxExpr.Ramp(h,0,1.0/h.Length); MtxVecTee.DrawIt(x,h,"Interpolate by 1x",false); Step = 0.5; //interpolate by 2x SignalUtils.FractionalFirImpulse(30,h,new double[2] {0.5,0.6},0,Step,TFilterType.ftLowpass,FS); x = MtxExpr.Ramp(h,0,1.0/h.Length); MtxVecTee.DrawIt(x,h,"Interpolate by 2x",false); Step = 0.25; //interpolate by 4x SignalUtils.FractionalFirImpulse(30,h,new double[2] {0.5,0.6},0,Step,TFilterType.ftLowpass,FS); x = MtxExpr.Ramp(h,0,1.0/h.Length); MtxVecTee.DrawIt(x,h,"Interpolate by 4x",false); Step = 0.125; //interpolate by 8x SignalUtils.FractionalFirImpulse(30,h,new double[2] {0.5,0.6},0,Step,TFilterType.ftLowpass,FS); x = MtxExpr.Ramp(h,0,1.0/h.Length); MtxVecTee.DrawIt(x,h,"Interpolate by 8x",false); } *) procedure FractionalFirImpulse(Length: integer; const H: TVec; const W: array of double; Offset,Step: double; FilterType: TFilterType; FS: double = 2); overload; (*Design a fractional delay allpass FIR filter. The filter properties can only be set to those covered by type. The result is returned in H and optionally a scaling factor or Gain can be specified. The fractional delay of the filter can be specified with FractionalDelay parameter, which must be between 0 and 1. The function returns the total filter delay including the integer delay part. This routine can be called when an allpass, linear phase, fractional FIR filter is desired. The resulting impulse response stored in H can be passed directly to the FirInit and FirFilter routines for filtering. H.FloatPrecision value on input defines the precision (single or double) of the result on output. *) function FractionalImpulse(const H: TVec; FractionalDelay: double; aFractionalImpulse: TFractionalImpulse = falp_60dB; Gain: double = 1): double; overload; (*Design a FIR filter with a Kaiser window. Compute a FIR impulse response filter with kaiser window applied and place the result in H. The transition regions are defined with the W array. There must be at least one (lowpass, highpass) and at most two (bandpass, bandstop) transition regions (2 or 4 elements). Filter type is defined with TFilterType. FS is the sampling frequency. The length of the filter (H.Length) is based on the narrowest transition region in combination with the passband Ripple parameter. 20*Log10(Ripple) is also the required attenuation of the stopband in decibel. Gain defines the filter gain. The resulting H vector contains FIR type impulse response, which can be passed to the FirInit routine. If EnsuredOdd is True, the filter length is guaranteed to have odd length. H.FloatPrecision value on input defines the precision (single or double) of the result on output. Impulse responses of FIR filters windowed with the Kaiser window. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector h = new Vector(0); Vector response = new Vector(0); Vector x; double FS = 2; //sampling frequency double Ripple = 0.001; // 60 dB stopband and 0.001 ripple in the passband // Lowpass design with transition band between 0.3 and 0.5 Hz. SignalUtils.KaiserImpulse(h, new double[2] {0.3,0.5} , Ripple, TFilterType.ftLowpass,1,FS,true); SignalUtils.FrequencyResponse(h, null, response,8,false, TSignalWindowType.wtRectangular,0); x = MtxExpr.Ramp(response.Length, TMtxFloatPrecision.mvDouble, 0,1.0/response.Length); MtxVecTee.DrawIt(x, response,"Lowpass design with transition band between 0.3 and 0.5 Hz",false); // Highpass design with transition band between 0.3 and 0.5 Hz. SignalUtils.KaiserImpulse(h, new double[2] {0.3,0.5} , Ripple,TFilterType.ftHighpass,1,FS,true); SignalUtils.FrequencyResponse(h, null, response,8,false, TSignalWindowType.wtRectangular,0); MtxVecTee.DrawIt(x, response,"Highpass design with transition band between 0.3 and 0.5 Hz",false); // Bandpass design with transition bands between 0.3..0.4 Hz and between 0.5...06 Hz. SignalUtils.KaiserImpulse(h, new double[4] {0.3,0.4,0.5,0.6} , Ripple,TFilterType.ftBandpass,1,FS,true); SignalUtils.FrequencyResponse(h, null, response,8, false,TSignalWindowType.wtRectangular,0); x = MtxExpr.Ramp(response.Length, TMtxFloatPrecision.mvDouble,0,1.0/response.Length); MtxVecTee.DrawIt(x, response,"Bandpass with transition bands 0.3-0.4 Hz and 0.5-0.6 Hz.",false); // Bandstop design with transition bands between 0.3..0.4 Hz and between 0.5...06 Hz. SignalUtils.KaiserImpulse(h, new double[4] {0.3,0.4,0.5,0.6} , Ripple,TFilterType.ftBandstop,1,FS,false); SignalUtils.FrequencyResponse(h, null, response,8, false, TSignalWindowType.wtRectangular,0); x = MtxExpr.Ramp(response.Length, TMtxFloatPrecision.mvDouble,0,1.0/response.Length); MtxVecTee.DrawIt(x, response,"Bandstop with transition bands 0.3-0.4 Hz and 0.5-0.6 Hz.",false); //Hilbert III with transition bands between 0.0-0.1 Hz and 0.9-1.0 Hz. SignalUtils.KaiserImpulse(h, new double[2] {0.9,1} , Ripple,TFilterType.ftHilbertIII,1,FS,false); SignalUtils.FrequencyResponse(h, null, response,8, false, TSignalWindowType.wtRectangular,0); x = MtxExpr.Ramp(response.Length, TMtxFloatPrecision.mvDouble,0,1.0/response.Length); MtxVecTee.DrawIt(x, response,"Hilbert III with transition bands between 0.0-0.1 Hz and 0.9-1.0 Hz.",false); //Hilbert IV with transition band between 0.0-0.1 Hz. SignalUtils.KaiserImpulse(h, new double[2] {0.9,1} , Ripple,TFilterType.ftHilbertIV,1,FS,false); SignalUtils.FrequencyResponse(h, null, response,8, false, TSignalWindowType.wtRectangular,0); x = MtxExpr.Ramp(response.Length, TMtxFloatPrecision.mvDouble,0,1.0/response.Length); MtxVecTee.DrawIt(x, response,"Hilbert IV with transition band between 0.0-0.1 Hz",false); //Differentiator III with transition band between 0.0-0.1 and 0.9-1.0 Hz. SignalUtils.KaiserImpulse(h, new double[2] {0.9,1} , Ripple,TFilterType.ftDifferentiatorIII,1,FS,false); SignalUtils.FrequencyResponse(h, null, response,8, false, TSignalWindowType.wtRectangular,0); x = MtxExpr.Ramp(response.Length, TMtxFloatPrecision.mvDouble,0,1.0/response.Length); MtxVecTee.DrawIt(x, response,"Differentiator III with transitions between 0.0-0.1 and 0.9-1.0 Hz",false); //Differentiator IV with transition band between 0.0 and 0.1 Hz. SignalUtils.KaiserImpulse(h, new double[2] {0.9,1} , Ripple,TFilterType.ftDifferentiatorIV,1,FS,false); SignalUtils.FrequencyResponse(h, null, response,8, false, TSignalWindowType.wtRectangular,0); x = MtxExpr.Ramp(response.Length, TMtxFloatPrecision.mvDouble,0,1.0/response.Length); MtxVecTee.DrawIt(x, response,"Differentiator IV with transitions between 0.0-0.1 Hz",false); } *) procedure KaiserImpulse(const H: TVec; W: array of double; Ripple: double; FilterType: TFilterType; Gain: double = 1; FS: double = 2; EnsuredOdd: boolean = false); overload; (*Design an oversampled FIR filter with a Kaiser window. Compute a FIR impulse response filter with kaiser window applied and place the result in H. The transition regions are defined with the W array. There must be at least one and at most two transition regions (2 or 4 elements). Filter type is defined with TFilterType. FS is the sampling frequency. The length of the filter (H.Length) is based on the narrowest transition region in combination with the passband Ripple parameter. 20*Log10(Ripple) is also the required attenuation of the stop band in decibel. Gain defines the filter gain and Step defines the step used to compute the oversampling factor: N = Trunc(1/Step). Step and Offset must be between 0 and 1. When offset is 0 and Step is 1, the routine returns the same as KaiserImpulse routine. The resulting H vector contains FIR type impulse response, which can be passed to an interpolation routine. Oversampled FIR filters are used for implementation of fractional delay filters and resampling with an arbitrary sampling frequency. If Step = 1 then the resulting impulse response can be passed directly to the FirInit and FirFilter routines. If ExtraSample is True an additional sample of the impulse response is be added to the right part of the impulse response. In this case, the filter is first designed with impulse response length of Length+2 and then truncated by 1 on the left side. The function returns the length of the non-interpolated impulse response. H.FloatPrecision value on input defines the precision (single or double) of the result on output. *) function FractionalKaiserImpulse(const H: TVec; W: array of double; Ripple, Offset,Step: double; FilterType: TFilterType; ExtraSample: boolean = True; Gain: double = 1; FS: double = 2): integer; overload; (*Design a Savitzky-Golay polynomial smoothing filter. Compute a Savitzky-Golay polynomial smoothing filter. The resulting filter is placed in matrix H. Only the center row of H is used for filtering, the upper and lower part of the H matrix are applied to the transition region when the signal starts and stops. Diff contains differentiation filters. Weights contain weights for the least square minization. Order must be less then FrameSize and FrameSize must be Odd. On input, the H.FloatPrecision and Weights.FloatPrecision (if present) need to match. *) procedure SavGolayImpulse(const H, Diff: TMtx; FrameSize, Order: integer; const Weights: TVec = nil); overload; (*The resulting H vector contains FIR type impulse response, which can be passed to the FirInit routine. Streamed filtering. A vector with a sine signal is broken down in to smaller pieces and they are filtered one by one. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector h = new Vector(0); Vector b = MtxExpr.Ramp(300, TMtxFloatPrecision.mvDouble, 0, 0.1); Vector c = new Vector(b.Length); TFirState state = new TFirState(); int n = 10; int i; SignalUtils.SavGolayImpulse(h,15,7,null); SignalUtils.FirInit(h,ref state,1,0,1,0); try { c.Size(b); n = 10; int bLength = b.Length; //must be outside of the "for" to prevent reevaluation for (i = 0; i < (bLength/n); i++) { b.SetSubRange(i * n, n); //select only a small subvector of the vector c.SetSubRange(i * n, n); SignalUtils.FirFilter(b,c,ref state); } MtxVecTee.DrawIt(new TVec[2] { b, c }, new string[2] { "Filtered data", "Original data" }, "Savitzky Golay", false); } finally { SignalUtils.FirFree(ref state); } } *) procedure SavGolayImpulse(const H: TVec; FrameSize, Order: integer; const Weights: TVec = nil); overload; (*Savitzky-Golay FIR smothing filter. Apply Savitzky-Golay FIR smothing filter designed by the SavGolayImpulse routine and stored in the H matrix to the Data. For streamed filtering use the routine. *) procedure SavGolayFilter(const Data: TVec; const H: TMtx); (*Fast envelope detector. A simple and fast envelope detector using moving average filter. FrameSize defines the length of the moving average (low pass filter) and the downsampling factor. The result is placed in Dst. Dst.Length = Src.Length div FrameSize. Src.Length must be divisable by FrameSize. This routine is 10 to 100x times faster then a decimator based envelope detector. Its drawback is higher noise due to some aliasing. Envelope detection is used to find the frequency of events with "long" periods. For example: sampling frequency is 11kHz. The audio card is recording hammer impacts which occur once every five seconds. Because the duration of the hammer impact is very short, the 0.2 Hz frequency will not show up in the frequency spectrum of the signal, regardless of the frequency resolution, especially because the audio card filters out everything below 20 Hz. By selecting FrameSize = 2000 the sampling frequency will be reduced by 2000x, by averaging together groups of rectified samples. The frequency spectrum of the filtered signal will show a clear peak at 0.2 Hz. A simple test of the function: using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector Response = new Vector(0); Vector x; double FS = 2; Vector b = MtxExpr.Sin(MtxExpr.Ramp(3000, TMtxFloatPrecision.mvDouble, 0,2*Math.PI*0.02/FS))* MtxExpr.Sin(MtxExpr.Ramp(3000, TMtxFloatPrecision.mvDouble, 0,2*Math.PI*0.2/FS)); Vector c = new Vector(b.Length); SignalUtils.EnvelopeDetector(b,c,10); //reduce sampling frequency by 10x SignalUtils.FrequencyResponse(b, null, Response, 8, true, TSignalWindowType.wtHanning, 0); x = MtxExpr.Ramp(Response.Length, TMtxFloatPrecision.mvDouble, 0,1.0/Response.Length); MtxVecTee.DrawIt(x, Response, "Original frequency spectrum", false); SignalUtils.FrequencyResponse(c,null,Response,8,true, TSignalWindowType.wtHanning, 0); x = MtxExpr.Ramp(Response.Length, TMtxFloatPrecision.mvDouble, 0,1.0/Response.Length); MtxVecTee.DrawIt(x, Response, "Envelope frequency spectrum", false); } *) procedure EnvelopeDetector(const Src, Dst: TVec; FrameSize: integer); (*Flip the frequency band. Flip the freqencies of the time domain signal stored in X, so that the DC becomes the Nyquist frequency and the Nyquist frequency becomes the DC. (mirror all frequencies around FS/4) The routine is very fast and can also be used for streaming. X.length must be even. The sampling frequency is 256 Hz. A tone has a frequency 6Hz. After flipping the frequencies, the tone has a frequency of: FS/2 - 6 = 122Hz. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = MtxExpr.Sin(MtxExpr.Ramp(256, TMtxFloatPrecision.mvDouble,0,2*Math.PI*6/256)); Vector c = new Vector(b.Length); Vector Response1 = new Vector(0); Vector Response2 = new Vector(0); c.Copy(b); SignalUtils.BandFlip(b); MtxVecTee.DrawIt(new TVec[2] {c,b}, new string[2] {"Original signal","Flipped signal"},"Band flipped time signal", false); SignalUtils.FrequencyResponse(c, null, Response1, 1, true, TSignalWindowType.wtRectangular, 0); SignalUtils.FrequencyResponse(b, null, Response2, 1, true, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(new TVec[2] {Response1,Response2}, new string[2] {"Spectrum: original signal","Spectrum: flipped signal"}, "Band flipped frequency spectrum", false); } *) procedure BandFlip(const X: TVec); overload; (*Remove the DC component. Subtract the mean value of Data from Data. Works for stationary signals. Use a highpass FIR filter for signals with varying mean value or call the DcFilter routine, for an IIR version of the DC filter. Removing the DC component without applying a true FIR or IIR filter is often convinient prior to frequency analysis, because there is no run-in or run-out and no filter delay. The sampling frequency is 256 Hz. A tone has a frequency 6Hz, amplitude 1 and DC offset is 4. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); Vector Response1 = new Vector(0); Vector Response2 = new Vector(0); SignalUtils.Tone(b, 256, 6.0 / 256, 0, 1, false); b = b + 4; c.Copy(b); SignalUtils.RemoveDC(b); MtxVecTee.DrawIt(new TVec[2] {c,b}, new string[2] {"Original signal","Signal without DC"},"Time signals", false); SignalUtils.FrequencyResponse(c, null, Response1, 1, true, TSignalWindowType.wtRectangular, 0); SignalUtils.FrequencyResponse(b, null, Response2, 1, true, TSignalWindowType.wtRectangular, 0); MtxVecTee.DrawIt(new TVec[2] {Response1,Response2}, new string[2] {"Spectrum: original signal","Spectrum: signal without DC"}, "Frequency spectrum", false); *) function RemoveDC(const SrcDst: TVec; Index: integer; Len: integer = MtxVecEOA): TVec; overload; (*In-place version of the RemoveDC method.*) function RemoveDC(const SrcDst: TVec): TVec; overload; (*Not-In-place version of the RemoveDC method.*) procedure RemoveDC(const Src, Dst: TVec); overload; (*Not-in-place version of the RemoveDC method with source and destination indexes. The Src data is preserved, the destination will hold the result. If Len is not specified, the maximum possible value will be used. *) procedure RemoveDC(const Src, Dst: TVec; SrcIndex, DstIndex: integer; Len: integer = MtxVecEOA); overload; (*Initialize an IIR filter. Initialize an IIR filter by initializing the IirState variable. Set ComplexData to True, if the data stream to be filtered will be complex. First half of the taps contains the numerator and the second half the denominator coefficients of the transfer function. The length of the taps vector is equal to 2*(IIrOrder + 1), where IirOrder is the order of the Iir filter. Transfer function: Sum( t[k] *z^(-k) ) H(z) = --------------------- , from k = 0 to N-1. Sum( t[N+k]*z^(-k) ) N .. length of polynomial, N = Taps.Length/2. N-1 .. order of the polynomial t ... taps vector *) procedure IirInit(const Taps: TVec; var IirState: TIirState; ComplexData: boolean); overload; (*Initialize an IIR filter with second order sections. Initialize an IIR filter by initializing the IirState variable. Set ComplexData to True, if the data stream to be filtered will be complex. The Sos (real) vector variable contains second order sections as produced by the ZeroPoleToSOS function: (B00, B10, B20, A00, A10, A20), (B01, B11, B21, A01, A11, A21), (.... ),... B - numerator sections array A - denumarator sections array Using second order sections to compute an IIR filter results in higher numerical accuracy and greater filter stability at a slightly higher cost on performance. Lowpass filter a sine signal with Chebyshev type I filter. Sampling frequency is 2Hz, cutoff frequency is 0.6 Hz. Passband ripple is 0.2 dB and filter order is 6. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); Vector sos = new Vector(0); Vector Response1 = new Vector(0); Vector Response2 = new Vector(0); TIirState state = new TIirState(); int n; int i; SignalUtils.Tone(b,300,6.0/300,0,1,false); // Alternative: try gaussian noise // b = MtxExpr.RandGauss(300); IIRFilters.ChebyshevIFilter(6,0.2,new double[1] {0.6}, TFilterType.ftLowpass,false,sos,TIirFrequencyTransform.ftStateSpaceAnalog); SignalUtils.IirInit(sos,ref state,false); try { c.Copy(b); //backup data SignalUtils.IirFilter(c,state); //apply filter to the data } finally { SignalUtils.IirFree(ref state); //free structure, if you dont require this IIR filter anymore } MtxVecTee.DrawIt(new TVec[2] {b,c}, new string[2] {"Original signal","Filtered signal"},"Time signals", false); SignalUtils.FrequencyResponse(c, null, Response1, 1, true, TSignalWindowType.wtHanning, 0); SignalUtils.FrequencyResponse(b, null, Response2, 1, true, TSignalWindowType.wtHanning, 0); MtxVecTee.DrawIt(new TVec[2] {Response1,Response2}, new string[2] {"Spectrum: original signal","Spectrum: filtered signal"}, "Frequency spectrum", false); } *) procedure IirInitBQ(const Sos: TVec; var IirState: TIirState; ComplexData: boolean); (*Initialize an IIR filter. Num contains the numerator and Den the denominator coefficients of the transfer function. The length of the Num and Den must be equal. If the numerator is of lower order that the denominator, then the numerator should be padded with zeros from the left. Transfer function: Sum( b[k]*z^(-k) ) H(z) = ----------------------, from k = 0 to N-1. Sum( a[k]*z^(-k) ) N = Num.Length = Den.Length. b... Num vector a... Den vector H(z) is also written as: Sum( b[k]*z^(-k) ) H(z) = ----------------------, k = 0.. N-1, m = 1..N-1 1 - Sum( a[m]*z^(-m) ) a[0] must always be 1. IIR filters evaluate the difference equation: y[i] = b[0]*x[i] + b[1]*x[i-1] + ... + b[N]*x[i-N] + a[1]*y[i-1] + ... + a[N]*y[i-N] x[i] ... input sample y[i] ... output (filtered) sample References: [1]Understanding digital signal processing, Richard G. Lyons, Prentice Hall, 2001. *) procedure IirInit(const Num, Den: TVec; var IirState: TIirState; ComplexData: boolean = false); overload; (*Filter data with an IIR filter. Filter data in Src and place the result in Dst. IirState must be initialized with a call to IirInit. Note Use this routine for filtering of streaming data. *) procedure IirFilter(const Src, Dst: TVec; var IirState: TIirState); overload; (*Filter data with an IIR filter. Filter Data and place the result back in Data. Use this routine for filtering of non-consecutive blocks of data. Set LinearPhase to True to double filter attenuation and ensure no phase distortion and no phase delay. *) procedure IirFilter(const Data: TVec; var IirState: TIirState; LinearPhase: boolean = False); overload; (*Filter Data and place the result back in Data. The Iir filter is defined with a transfer function. Num is numerator and Den is denominator. Set LinearPhase to True to double filter attenuation and ensure no phase distortion and no phase delay (except for the initial transition regions). Lowpass filter a sine signal with Chebyshev type I filter. Sampling frequency is 2Hz, cutoff frequency is 0.6 Hz. Passband ripple is 0.2 dB and filter order is 6. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); Vector Response1 = new Vector(0); Vector Response2 = new Vector(0); TIirState state = new TIirState(); int n; int i; SignalUtils.Tone(b,300,6.0/300,0,1,false); // Alternative: try gaussian noise // b = MtxExpr.RandGauss(300); c.Size(b); IIRFilters.ChebyshevIFilter(6,0.2,new double[1] {0.6}, TFilterType.ftLowpass,false,num,den,TIirFrequencyTransform.ftStateSpaceAnalog); SignalUtils.IirInit(num,den,ref state,false); try { //Alternative 1, IIR streaming n = 10; int bLength = b.Length; //to prevente reevaluaton inside "for" for (i = 0; i < (bLength/n); i++) { b.SetSubRange(i*n,n); c.SetSubRange(i*n,n); SignalUtils.IirFilter(b,c,ref state); } } finally { SignalUtils.IirFree(ref state); } //Alternative 2 single block filter (does not require TIirState) // c.Copy(b); // SignalUtils.IirFilter(c,num,den,true); //Alternative 3 single block // c.Copy(b); // SignalUtils.IirFilter(c,state); c.SetFullRange(); b.SetFullRange(); MtxVecTee.DrawIt(new TVec[2] {b,c}, new string[2] {"Original signal","Filtered signal"},"Time signals", false); SignalUtils.FrequencyResponse(c, null, Response1, 1, true, TSignalWindowType.wtHanning, 0); SignalUtils.FrequencyResponse(b, null, Response2, 1, true, TSignalWindowType.wtHanning, 0); MtxVecTee.DrawIt(new TVec[2] {Response1,Response2}, new string[2] {"Spectrum: original signal","Spectrum: filtered signal"}, "Frequency spectrum", false); } *) procedure IirFilter(const Data, Num, Den: TVec; LinearPhase: boolean = False); overload; (*Filter data with an IIR filter. Filters sample Src and places real valued output of the filter in Dst. *) procedure IirFilter(Src: double; out Dst: double; var IirState: TIirState); overload; procedure IirFilter(Src: single; out Dst: single; var IirState: TIirState); overload; (*Filter data with an IIR filter. Filters sample Src and places complex output of the filter in Dst. *) procedure IirFilter(const Src: TCplx; out Dst: TCplx; var IirState: TIirState); overload; procedure IirFilter(const Src: TSCplx; out Dst: TSCplx; var IirState: TIirState); overload; (*Free an IIR filter. Free any memory associated with the IIR filter initialized with the IirInit routine. The method can be called more than once. Nothing will be freed, if the filter is not initialized, but IirState must be initialized either with zeros or a call to IirInit before passing it to this routine. *) procedure IirFree(var IirState: TIirState); overload; (*Initialize a FIR filter. Initialize the FIR filter by initializing the FirState variable. FirTaps must hold the filter impulse. FirState variable must be initialized with zeros. UpSample and DownSample define the sampling frequency change by an integer factor. UpDelay defines the initial FIR filter delay when upsampling and DownDelay defines the initial FIR filter delay when downsampling. IF Upsample or DownSample are different from 1, a multirate FIR filter is initialized. Multirate FIR filters avoid computing samples which will be discarded, because of the sampling rate change. The multirate filter first performs the upsampling and then the downsampling. Fir filters convolve an impulse response with the signal [1] p. 165: M-1 y[i] = Sum( h[k]*x[i-k] ) k=0 x.. input signal. h.. impulse response y.. output signal Convolution can also be written as: y[i] = h[k] ( * ) x[i] In frequency domain, convolution becomes multiplication: Y[I] = X[I]*H[I] Y.. frequency spectrum of the output signal. X.. frequency spectrum of the input signal. H.. frequency spectrum of the impulse response Impulse response for typical filter types can be designed with , , and . References: [1] Understanding digital signal processing, Richard G. Lyons, Prentice Hall, 2001. *) procedure FirInit(const FirTaps: TVec; var FirState: TFirState; UpSample: integer = 1; UpDelay: integer = 0; DownSample: integer = 1; DownDelay: integer = 0); overload; (*Filter data with a FIR filter. Uses comparatively 3x less memory from FirInit, but the function supports the initialization of FIR filter only when the taps and the signal are real (not complex). *) procedure FirInitReal(const FirTaps: TVec; var FirState: TFirState; UpSample: integer = 1; UpDelay: integer = 0; DownSample: integer = 1; DownDelay: integer = 0); (*Filter data with a FIR filter. Filter data in Src and place the result in Dst. FirState must be initialized with a call to FirInit. This version of FirFilter routine can filter streaming blocks of data. *) procedure FirFilter(const Src, Dst: TVec; var FirState: TFirState); overload; (*Filter data with a FIR filter. Filter real Src sample and place the filtered result in Dst sample. *) procedure FirFilter(const Src: double; out Dst: double; var FirState: TFirState); overload; (*Filter data with a FIR filter. Filter real TCplx sample and place the filtered result in Dst sample. *) procedure FirFilter(const Src: TCplx; out Dst: TCplx; var FirState: TFirState); overload; (*Filter Data with a FIR filter and place the result back in the Data. This version of FirFilter can not be used to filter streaming data. The routine compensates for group delay and returns filtered data delayed by 0 (odd FIR length) or 0.5 samples (even FIR length). Lowpass filter a signal with a FIR filter. Sampling frequency is 2Hz, cutoff frequency is 0.6 Hz. Stopand passband ripple is 0.001. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); Vector h = new Vector(0); Vector Response1 = new Vector(0); Vector Response2 = new Vector(0); TFirState state = new TFirState(); int n; int i; double FS = 2; SignalUtils.Tone(b,300,6.0/300,0,1,false); // Alternative: try gaussian noise // b = MtxExpr.RandGauss(300); c.Size(b); OptimalFir.RemezImpulse(h, new double[2] { 0.5, 0.7 }, 0.001, TFilterType.ftLowpass, 1, FS,false); SignalUtils.FirInit(h,ref state,1,0,1,0); try { //Alternative 1, FIR streaming n = 10; int bLength = b.Length; //to prevente reevaluaton inside "for" for (i = 0; i < (bLength/n); i++) { b.SetSubRange(i*n,n); c.SetSubRange(i*n,n); SignalUtils.FirFilter(b,c,ref state); } } finally { c.SetFullRange(); b.SetFullRange(); SignalUtils.FirFree(ref state); } //Alternative 2 single block filter (does not require TFirState) // c.Copy(b); // SignalUtils.FirFilter(c,h,1,1); //Alternative 3 single block // c.Copy(b); // SignalUtils.FirFilter(b,c,state); MtxVecTee.DrawIt(new TVec[2] {b,c}, new string[2] {"Original signal","Filtered signal"},"Time signals", false); SignalUtils.FrequencyResponse(b, null, Response1, 1, true, TSignalWindowType.wtHanning, 0); SignalUtils.FrequencyResponse(c, null, Response2, 1, true, TSignalWindowType.wtHanning, 0); MtxVecTee.DrawIt(new TVec[2] {Response1,Response2}, new string[2] {"Spectrum: original signal","Spectrum: filtered signal"}, "Frequency spectrum", false); } *) procedure FirFilter(const Data, FirTaps: TVec; UpSample: integer = 1; DownSample: integer = 1); overload; (*Free a FIR filter. Free any memory associated with the FIR filter initialized with the FirInit routine. The method can be called more than once. Nothing will be freed, if the filter is not initialized, but FirState must be initialized either with zeros or a call to FirInit before passing it to this routine. *) procedure FirFree(var FirState: TFirState); overload; (*Initialize a median filter. Initialize the median filter by initializing the State variable. The MaskSize defines the size of the maks of the median filter. *) procedure MedianInit(MaskSize: integer; var State: TMedianState; const FloatPrecision: TMtxFloatPrecision); overload; (*Filter data with a median filter. Filter data in Src with a median filter configured with a call to MedianInit routine and place the result in the Dst. The length of the Dst will be set to match the Length of the Src. This MedianFilter routine can be used to filter streaming data. *) procedure MedianFilter(const Src, Dst: TVec; const State: TMedianState); overload; (*Filter data in Src from SrcIndex to SrcIndex+Len with a median filter and place the result in Dst from DstIndex position on. MaskSize defines the size of the mask for the filter. This MedianFilter routine can be used to filter a block of data. The size of Dst is not adjusted. *) procedure MedianFilter(const Src,Dst: TVec; MaskSize: integer; SrcIndex: integer; DstIndex: integer; Len: integer = -1); overload; (*Filter data in Src with a median filter and place the result in Dst. MaskSize defines the size of the mask for the filter. This MedianFilter routine can be used to filter a block of data. The size of Dst is atomatically adjusted. *) procedure MedianFilter(const Src,Dst: TVec; MaskSize: integer); overload; (*Filter data in Data with a median filter from DataIndex to DataIndex+Len and place the result back in the Data. Set the mask size of the median filter to MaskSize. If Len is MtxVecEOA, the maximum length of the Data vector will be used. Median filter applied to a single block of data. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); b.Size(300); b.SetSubRange(0,150); b.Ramp(0,1); b.SetSubRange(150,150); b.Ramp(150,-1); //creates a triangle b.SetFullRange(); SignalUtils.MedianFilter(b,c,9); SignalUtils.MedianFilter(b,9,0,Math387.MtxVecEOA); //Alternative (in-place) if (!(b.Equal(c,0))) throw new Exception("Not equal"); } *) procedure MedianFilter(Data: TVec; MaskSize: integer; DataIndex: integer = 0; Len: integer = -1); overload; (*Free a median filter. Deallocate any memory allocated by the median filter initialized with the MedianInit routine. *) procedure MedianFree(var State: TMedianState); overload; (*Initialize an integer delay filter. Initialize the delay filter by initializing the State variable. The Delay parameter defines the delay in number of sampels. *) procedure DelayInit(Delay: integer; var State: TDelayFilterState; const FloatPrecision: TMtxFloatPrecision); overload; (*Free a delay filter. Deallocate any memory allocated by the delay filter. *) procedure DelayFree(var State: TDelayFilterState); overload; (*Filter data with an integer delay filter. Apply a delay to Src and place the result in Dst. The state parameter must be initialized with a call to DelayInit routine. *) procedure DelayFilter(const Src, Dst: TVec; const State: TDelayFilterState); overload; (*Design an impulse response of a moving average filter. Design moving average filter by initializing the Taps variable. Apply the moving average filter to your data, by passing the initialized Taps variable to the FirInit routine and then pass the FirState variable to the FirFilter routine. MaskSize defines the length of the filter in samples. The precision of the Taps is specified on input with Taps.FloatPrecision *) procedure MovingAverageImpulse(const Taps: TVec; MaskSize: integer); overload; (*Filter data with a moving average filter. Applies moving average filter to Data from DataIndex to DataIndex+Len. Set Len to -1 to use the full Data length. MaskSize defines the length of the filter in samples. This MovingAverageFilter routine can be used to filter a block of data (not for streaming). Moving average filter applied to a sine signal. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); b.Size(300); SignalUtils.Tone(b, 300, 5.0 / 300, 0, 1, false); c.Copy(b); SignalUtils.MovingAverageFilter(b, 20, 0, Math387.MtxVecEOA); //from Index 0 to EndOfArray MtxVecTee.DrawIt(new TVec[2] { c, b }, new string[2] { "Unfiltered", "Filtered" }, "Moving average filter", false); } *) procedure MovingAverageFilter(Data: TVec; MaskSize: integer; DataIndex: integer = 0; Len: integer = -1); overload; (*Filter data with an exponential average filter. Exponential average filter without the TIirState structure. The function can be called to filter sample by sample. Initialize the State to 0 on the first call. Decay defines the decay from sample to sample and is initialized to 50% by default. With each new sample, the filter will take average from the 50% of the new sample and 50% of the previous average. The exponential average filter implements the following difference equation: y[i] = 1/d * x[i] + (d-1)/d * y[i-1] x.. input signal y.. output signal d.. Decay factor In terms of percentage: y[i] = a * x[i] + b * y[i-1] , a + b = 1 a*100 ... percent of the new data used. b*100 ... percent of the old average used to compute the new average. *) procedure ExpAverageFilter(Data: TVec; var State: double; Decay: double = 50); overload; (*Filter data with an exponential averaging filter. Single sample non-vectorized variant of the exponential averaging. *) function ExpAverageFilter(const Data: double; var State: double; Decay: double = 50): double; overload; (*Filter data with an exponential average filter. Exponential average filter without the TIirState structure for complex data. *) procedure ExpAverageFilter(Data: TVec; var State: TCplx; Decay: double = 50); overload; (*Design an exponential filter with Decay parameter and place the transfer function in Num (numerator) and Den (denominator). Transfer function can be used to initialize an IIR filter by passing num and den to the IirInit routine. Exponential average filter. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b; Vector c = new Vector(0); Vector num = new Vector(0); Vector den = new Vector(0); int n; int i; TIirState IirState = new TIirState(); double state; //Alternative 1: Tone with 5 periods // b := Sin(Ramp(300, mvDouble, 0,2*Pi*5/300)); //Alternative 2: Gaussian noise b = MtxExpr.RandGauss(300,false); c.Copy(b); n = 10; state = 0; int bLength = b.Length; for (i = 0; i < (bLength / n); i ++) //streaming test 1 { b.SetSubRange(i*n,n); SignalUtils.ExpAverageFilter(b,ref state, 10); //set to 10% } b.SetFullRange(); MtxVecTee.DrawIt(new TVec[2] {c,b}, new string[2] {"Unfiltered","Filtered"},"Exponential averaging",false); b.Copy(c); SignalUtils.ExpAverageFilter(10,num,den); //set to 10x, (1/10 = 0.1, => 10%) SignalUtils.IirInit(num,den,ref IirState,false); bLength = b.Length; for (i = 0; i < (bLength / n); i ++) //streaming test 2 { b.SetSubRange(i*n,n); c.SetSubRange(i*n,n); SignalUtils.IirFilter(b,c,ref IirState); } SignalUtils.IirFree(ref IirState); b.SetFullRange(); c.SetFullRange(); MtxVecTee.DrawIt(new TVec[2] { b, c }, new string[2] { "Unfiltered", "Filtered" }, "Exponential averaging with IirFilter", false); } *) procedure ExpAverageFilter(Decay: double; Num, Den: TVec); overload; (*Design a DC filter. Design a DC filter with TransitionBandwidth and place the transfer function in Num (numerator) and Den (denominator). You can then use this transfer function to initialize an IIR filter with a call to IirInit. A DC filtered signal will be centered around zero. This DC filter is a simple differentiator/integrator pair. Transition bandwidth is the width of the frequency band where the amplitude is not yet completely attenuated. With DC filters, the transition band starts at 0 Hz. Narrow transition band (TransitionBandwidth/FS ratio is small) will result in filters with longer delays. FS is the sampling frequency. The filter implements the following difference equation: y[i] = x[i] - x[i-1] + alpha*y[i-1] x.. input signal y.. output signal alpha.. parameter Alpha paremeter can control the 3dB frequency of the transition bandwidth: alpha := 1-(TransitionBandwidth/FS)*Pi; FS.. sampling frequency TransitionBandwidth.. frequency up to which will the filter have more then 3dB attenuation. Must be less then FS/2. *) procedure DcFilter(TransitionBandwidth, FS: double; num,den: TVec); overload; (*:Design a DC filter with alpha parameter and place the transfer function in Num (numerator) and Den (denominator). This transfer function can be used to initialize an IIR filter with a call to IirInit. A DC filtered signal will be centered around zero. The DC filter is a simple differentiator/integrator pair. alpha is typically between 0.99 and 0.9999 and must be < 1. A bigger alpha will cause longer filter delay. *) procedure DcFilter(alpha: double; num,den: TVec); overload; (*State parameter holds the filter state. NewValue is the next sample and alpha is typically between 0.99 and 0.9999 and must be < 1. Big alpha will cause longer filter delay and more ringing. State should be initialized to zero before the routine is called for the first time. DC filtering. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); Vector num = new Vector(0); Vector x = new Vector(0); Vector den = new Vector(0); Vector Response = new Vector(0); int n; int i; TIirState IirState = new TIirState(); TCplx DCState; SignalUtils.Tone(b,300,5.0/300,0,1,false); //generate sine with 5 periods in 300 samples // Alternative: // b.RandGauss; b = b + 2; c.Copy(b); n = 10; SignalUtils.DcFilter(0.95,num,den); SignalUtils.IirInit(num,den,ref IirState,false); int bLength = b.Length; for (i = 0; i < (bLength/n); i++) //only to test the streaming { b.SetSubRange(i*n,n); c.SetSubRange(i*n,n); SignalUtils.IirFilter(b,c,ref IirState); } b.SetFullRange(); c.SetFullRange(); MtxVecTee.DrawIt(new TVec[2] { b, c }, new string[2] { "Unfiltered", "Filtered" }, "DC IirFilter", false); SignalUtils.FrequencyResponse(num,den,Response,64,false,TSignalWindowType.wtRectangular,0); MtxVecTee.DrawIt(Response,"Frequency response",false); SignalUtils.DcFilter(0.05,2,num,den); SignalUtils.IirInit(num,den,ref IirState,false); bLength = b.Length; for (i = 0; i < (bLength/n); i++) //only to test the streaming { b.SetSubRange(i*n,n); c.SetSubRange(i*n,n); SignalUtils.IirFilter(b,c,ref IirState); } b.SetFullRange(); c.SetFullRange(); MtxVecTee.DrawIt(new TVec[2] { b, c }, new string[2] { "Unfiltered", "Filtered" }, "DC IirFilter", false); SignalUtils.FrequencyResponse(num,den,Response,64,false,TSignalWindowType.wtRectangular,0); MtxVecTee.DrawIt(Response,"Frequency response",false); DCState = Math387.C_ZERO; for (i = 0; i < (b.Length); i++) //only to test the streaming { c.Values[i] = SignalUtils.DcFilter(b.Values[i],ref DCState,0.95); } MtxVecTee.DrawIt(new TVec[2] { b, c }, new string[2] { "Unfiltered", "Filtered" }, "DC IirFilter", false); } *) function DcFilter(NewValue: double; var State: TCplx; alpha: double = 0.99): double; overload; (*Design a notch filer. Design a notch filter and place the transfer function in Num (numerator) and Den (denominator). You can then use this transfer function to initialize an IIR filter with a call to IirInit. A notch filter will filter out the notch frequency. and r defines the attenuation and width of the transition band. Typical values for r are from 0.99 to 0.9999. *) procedure NotchFilter(NotchFrequency, r: double; num,den: TVec; FS: double = 2); overload; (*Computes the group delay of IIR filters. Num holds the numerator and Den the denominator of the transfer function for which the group delay should be computed. Den can be nil. Zeropadding defines the amount of zero padding applied with FFT. The result is placed in GrpDelay. Group delay is the first derivate of continuous phase: g = (d/dw)Phase, where w is the frequency ([1] p. 201). References: [1] Understanding digital signal processing, Richard G. Lyons, Prentice-Hall, 2001. Advanced sampling techniques *) procedure GroupDelay(GrpDelay, Num: TVec; Den: TVec = nil; ZeroPadding: integer = 1); procedure GroupDelay2(GrpDelay, Num: TVec; den: TVec = nil; ZeroPadding: integer = 1); (*Filter data with a sample-and-decay filter. Filter Data with a non-linear sample-and-decay filter. This filter is similar to Sample-And-Hold, except that the held value slowly decays until a bigger is found. The filter features streaming support via State variable. Data can be real or complex. The decay parameter defines how much will the value decay from sample to sample. The State variable has to be initialized with zeros before it is passed to the routine for the first time. Sample and delay filter example on a sine. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); Vector num = new Vector(0); int n = 10; int i; TSampleAndHoldState State = new TSampleAndHoldState(); SignalUtils.Tone(b,300,5.0/300,0,1,false); c.Copy(b); int bLength = b.Length; for (i = 0; i < (bLength/n); i++) { b.SetSubRange(i*n,n); SignalUtils.SampleAndDecayFilter(b,ref State,0.95); } b.SetFullRange(); MtxVecTee.DrawIt(new TVec[2] {c,b}, new string[2] {"Unfiltered","Filtered"},"SampleAndDecay",false); } *) procedure SampleAndDecayFilter(Data: TVec; var State: TSampleAndHoldState; Decay: double = 0.99); overload; (*Filter data with a sample-and-hold filter. Filter Data with a non-linear sample-and-hold filter. The current value is held until a bigger is found or time-out occurs. Time out is defined in samples with Hold parameter. The filter features streaming support via the State variable. Data can be real or complex. The State variable has to be initialized with zeros before it is passed to the routine for the first time. SampleAndHoldFilter example on a sine. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); Vector num = new Vector(0); int n = 10; int i; TSampleAndHoldState State = new TSampleAndHoldState(); SignalUtils.Tone(b,300,5.0/300,0,1,false); c.Copy(b); int bLength = b.Length; for (i = 0; i < (bLength/n); i++) { b.SetSubRange(i*n,n); SignalUtils.SampleAndHoldFilter(b,ref State, 7); } b.SetFullRange(); MtxVecTee.DrawIt(new TVec[2] {c,b}, new string[2] {"Unfiltered","Filtered"},"SampleAndDecay",false); } *) procedure SampleAndHoldFilter(Data: TVec; var State: TSampleAndHoldState; Hold: integer); (*Initialize a "consistent parameter" filter. Initialize TConsistentParam state variable. Param is the inital value of the parameter. *) procedure ConsistentParameterInit(Param: double; var State: TConsistentParam); overload; (*Filter data with a "consistent parameter" filter. Consistent parameter filter is a non-linear filter, that will return false once and reset the timer, if the Param value changes before TimeSpanSeconds has passed. *) function ConsistentParameterFilter(Param: double; TimeSpanSeconds: double; var State: TConsistentParam): boolean; overload; (*Param can be boolean. *) function ConsistentParameterFilter(Param: boolean; TimeSpanSeconds: double; var State: TConsistentParam): boolean; overload; (*Conjugate sort complex values. Sort complex values in Vec with tolerance Tol. Conjugate sort will group conjugated complex numbers together. This routine is usefull for processing complex zeros/poles. *) procedure SortConj(const Vec: TVec; Tol: double = 1E-4); overload; (*Conjugate sort complex Vec values from Index to Index+Len.*) procedure SortConj(const Vec: TVec; Index, Len: integer; Tol: double = 1E-4); overload; (*Copy data to a circular buffer. Shifts the Buffer data to the left, to make room for new Source data and copies Source to the Buffer. BufferSize defines the maximum size up to which the Buffer will increase and before the shifting begins. When BufferSizse is exceeded the oldest data is dropped to make space for the new. Note This function is not very efficient for very big buffers, but it is very simple to use. *) procedure CircularBuffer(Buffer, Source: TVec; BuffSize: integer); overload; (*Initialize a circular buffer. Initializes circular buffer State variable. BufferSize is the new buffer size. Buffer holds the actual data and its size is adjusted to match BufferSize. IncrementStep defines the step by how much will the buffer increase when auto-resizing. NewBufferLength = OldBufferLength*IncrementStep; Auto-resizing is performed by routine. The incrementStep parameter will be forced in to interval between 1 and 10. *) procedure InitCircularBuffer(BufferSize: integer; IncrementStep: double; var State: TCircularBufferState); overload; (*Reset circular buffer.*) procedure ResetCircularBuffer(var State: TCircularBufferState); overload; (*Advance the read position of circular buffer. Advance the read position of the circular buffer by SamplesToSkip. *) procedure AdvanceCircularBuffer(SamplesToSkip: integer; var State: TCircularBufferState); overload; (*Copy data to circular buffer. Copies Src.Length samples from Src to Buffer. If there is not enough space available and the data that has not yet been read will be overwritten, the BufferOverflow flag will be set in the State variable. The routine advances WritePosition of the buffer to the position where the next data block is to be written to. If AutoResize is True the buffer will be resized before the buffer is underrun or overrun. The size of the buffer will increased by IncrementStep parameter passed to the . *) procedure WriteToCircularBuffer(Buffer, Src: TVec; var State: TCircularBufferState; AutoResize: boolean = false); overload; (*Copy data from circular buffer. Copies Dst.Length samples from Buffer to Dst. If there is not Dst.Length samples available, the BufferUnderflow flag will be set in the State variable. The routine advances ReadPosition of the buffer to the position where the next data is to be read from. ForwardStep defines the number of samples to advance the read cursor. If the ForwardStep is -1, the read cursor is advanced Dst.Length samples. Circular buffer test. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b = new Vector(0); Vector c = new Vector(0); Vector h = new Vector(0); int n = 6; int i; TCircularBufferState State = new TCircularBufferState(); Vector h1 = MtxExpr.Ramp(30, TMtxFloatPrecision.mvDouble, 0,1); c.Size(h1); c.SetZero(); h.Size(h1); h.SetZero(); SignalUtils.InitCircularBuffer(11,2,ref State); int h1Length = h1.Length/n; for (i = 0; i < h1Length; i++) { h1.SetSubRange(i * n, n); h.SetSubRange(i * n, n); c.SetSubRange(i * n, n); SignalUtils.WriteToCircularBuffer(b, h1, ref State,false); n = SignalUtils.PeekCircularBuffer(State); SignalUtils.MonitorCircularBuffer(b, h, ref State); SignalUtils.ReadFromCircularBuffer(b, c, ref State, -1); } h1.SetFullRange(); h.SetFullRange(); c.SetFullRange(); MtxVecTee.DrawIt(new TVec[3] {h1,h,c}, new string[3] {"Original","Monitored","Read"},"Circular buffering",false); } *) procedure ReadFromCircularBuffer(Buffer, Dst: TVec; var State: TCircularBufferState; ForwardStep: Integer = -1); overload; (*Get the number of available samples within the circular buffer. Returns number of available samples, that can be read by ReadFromCircularBuffer. This function allows you to guard against buffer underflow. (reading data that is not there yet) *) function PeekCircularBuffer(const State: TCircularBufferState): integer; overload; (*Get the index in the buffer where the next block of data will be written. Get the index in the buffer where the next block of data will be written. *) function WritePositionCircularBuffer(const State: TCircularBufferState): integer; overload; (*Resize the circular buffer. Resize the Buffer to BufferSize. The procedure also resets BufferUnderflow and BufferOverflow flags. *) procedure ResizeCircularBuffer(BufferSize: integer; var State: TCircularBufferState; Buffer: TVec); overload; (*Monitor the circular buffer. Copies the most recent Dst.Length samples from the circular Buffer to Dst and does not advance the read cursor. *) procedure MonitorCircularBuffer(Buffer, Dst: TVec; var State: TCircularBufferState; MonitorMode: TMonitorMode); overload; (*Compute running average. Compute running average of Data and place the result in Averaged. Count defines the Data block count already averaged and Decay defines exponential decay factor. If Decay is zero, the method will compute linear average. This can be usefull to save memory, if a very large amount of large Data blocks has to be averaged. If Decay is bigger then zero, then this routine is the vectorized version of the ExpAverageFilter routine. *) procedure RunningAverage(const Averaged, Data: TVec; Count: integer; Decay: integer = 0); overload; (*Compute running average on TMtx data. *) procedure RunningAverage(const Averaged, Data: TMtx; Count: integer; Decay: integer = 0); overload; (*Compute a part of the bispectrum. Compute a single horizontal bispectral line (triple product) from Spectrum and place the result in Bispectrum. The index parameter defines for which frequency you want to compute the triple product. The triple product will computed for one frequency against all others. When used together with routine, bicoherence can be estimated. Bicoherence has 1 where a frequency pair is synchronized in phase. Frequency pairs which are unrelated in phase have a zero in the bicoherence matrix. *) procedure BiSpectrum(const BiSpectrum, Spectrum: TVec; Index: integer; Zeroed: boolean); overload; (*Compute a part of the bispectrum. Compute a single vertical bispectral line (triple product) from Spectrum and place the result in BiSpectrum. The index parameter defines for which frequency you want to compute the triple product. The triple product will computed for one frequency against all others. When used together with RunningAverage method, you can compute the bicoherence. Bicoherence has 1 where a frequency pair is synchronized in phase. Frequency pairs which are unrelated in phase have a zero in the bicoherence matrix. *) procedure BiSpectrumVert(const BiSpectrum, Spectrum: TVec; Index: integer; MtxPacked: boolean = False); overload; (*Conjugate extend a frequency spectrum. Assuming that Src contains the result of a real FFT, the Src will be conjugated, flipped and appended to itself. *) function ConjExtend(const Src: TVec): TVec; overload; (*The result will be placed in Dst. *) procedure ConjExtend(const Src, Dst: TVec); overload; (*Yule-Walker method for autoregressive parameter estimation. Src contains the data on which the autoregressive parameter estimation (placed in A) should be based. Order defines the Order of the autoregressive process. *) procedure ArYuleWalker(const Src, A: TVec; Order: integer); (*Computes a frequency spectrum with the autoregressive Yule-Walker method. Computes a frequency spectrum from Data and places the result in aResult. ArOrder is the autoregressive order used by the Yule-Walker method and zero padding factor for the FFT is defined with the ZeroPadding parameter. Estimate the frequency spectrum of sine signal with the Yule-Walker method. The assumed order is 4 and zero padding is set to 16. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector b, X; Vector Response = new Vector(0); b = MtxExpr.Sin(MtxExpr.Ramp(300, TMtxFloatPrecision.mvDouble, 0, 2 * Math.PI * 50 / 300)); SignalUtils.ArYuleWalkerSpectrum(b, Response, 4, 32); X = MtxExpr.Ramp(Response.Length, TMtxFloatPrecision.mvDouble, 0, 1.0 / Response.Length); MtxVecTee.DrawIt(X, 20 * MtxExpr.Log10(MtxExpr.Abs(Response)),"",false); } *) procedure ArYuleWalkerSpectrum(const Data, aResult: TVec; ArOrder: integer = 40; ZeroPadding: integer = 8); (*Computes a frequency spectrum with the autoregressive Burg method. Computes a frequency spectrum from Data and places the result in aResult. ArOrder is the autoregressive order used by the Burg method and zero padding factor for the FFT is defined with the ZeroPadding parameter. *) procedure ArBurgSpectrum(const Data, aResult: TVec; ArOrder: integer = 40; ZeroPadding: integer = 8); (*Computes a frequency spectrum with the autoregressive "modified covariance" method. Computes a frequency spectrum from Data and places the result in aResult. ArOrder is the autoregressive order used by the modified covariance method and zero padding factor for the FFT is defined with the ZeroPadding parameter. *) procedure ArMCovarianceSpectrum(const Data, aResult: TVec; ArOrder: integer = 40; ZeroPadding: integer = 8); (*Computes a frequency spectrum with the autoregressive "covariance" method. Computes a frequency spectrum from Data and places the result in aResult. ArOrder is the autoregressive order used by the covariance method and zero padding factor for the FFT is defined with the ZeroPadding parameter. *) procedure ArCovarianceSpectrum(const Data, aResult: TVec; ArOrder: integer = 40; ZeroPadding: integer = 8); (*Modified covariance method for autoregressive parameter estimation. The AR parameter estimation is based on forward and backward prediction errors, and on direct estimation of of the reflection coefficients. Src contains the data on which the autoregressive parameter estimation (placed in A) should be based. Order defines the Order of the autoregressive process and K are the reflection coefficients. E is the prediction error. References: [1] Introduction To Spectral Analysis, Petre Stoica and Randolph Moses, Prentice-Hall, 1997, Page 120. *) procedure ArBurg(const Src: TVec; Order: integer; const A,K: TVec; out E: double); overload; (*Modified covariance method for autoregressive parameter estimation. The AR parameters are estimated by minimizing the average of the estimated forward and backward prediction error powers. Src contains the data on which the autoregressive parameter estimation (placed in A) should be based. Order defines the Order of the autoregressive process. E is the prediction error. References: [1] Modern spectral estimation, Steven M. Kay, Prentice-Hall, Page 225 *) procedure ArMCovariance(const Src: TVec; Order: integer; const A: TVec; out E: double); overload; (*Covariance method for autoregressive parameter estimation. The AR parameters are estimated by minimizing an estimate of the prediction error power, but uses less data points then Yull-Walker (autocorrelation method) estimator. The covariance method can accurately extract frequencies of pure sinusoids. Src contains the data on which the autoregressive parameter estimation (placed in A) should be based. Order defines the Order of the autoregressive process. E is the prediction error. References: [1] Modern spectral estimation, Steven M. Kay, Prentice-Hall, Page 221 *) procedure ArCovariance(const Src: TVec; Order: integer; const a: TVec; out E: double); overload; (*Compute the chirp z-transform. Compute the chirp z transform of Src and place it in aResult. Chirp z transform transforms the time domain signal Src in to frequency domain. (as does FFT). FStart is the starting frequency and FStop is the stop frequency. k defines the number of steps within that frequency band. FS is the sampling frequency. RStart is the starting radius of the circle in the z-domain and RStop is the final radius of the circle in the z-domain. CZT does the same as the FFT algorithm, if k = Src.Length*0.5, FStart = 0 and FStop = FS/2. and Src.Length is power of two. CZT is slower, but more versatile then FFT. It can be faster then FFT, for large zero padding factors. Chirp transform algorithm was first proposed by Bluestein. Generalization of the algorithm called Chirp-Z transform obtains samples of the z-transform spaced equally in angle on a spiral contour in z-plane. More on this subject can be found in [1] (p. 629) and [2] (p. 393). Rabiner [2] proposed the CZT algorithm. References: [1] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989. [2] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975. *) procedure CZT(const Src: TVec; k: integer; FStart, FStop: double; const aResult: TVec; FS: double = 2; RStart: double = 1; RStop: double = 1); overload; (*Initializes the chirp z-transform. If the parameters to CZT function do not change between calls, some variables can be pre-computed and stored in to a state variable. The length (and precision) of the source data is expected to remain fixed. This overload is about 2.5x faster than the variant without the state variable. The State variable does not need to be freed, if parameters change. It is also safe to call CztInit, if parameters did not change. Initialization will be simply skipped in this case. Chirp z transform transforms the time domain signal Src in to frequency domain (as do DFT and FFT). FStart is the starting frequency and FStop is the stop frequency. k defines the number of steps within that frequency band. FS is the sampling frequency. RStart is the starting radius of the circle in the z-domain and RStop is the final radius of the circle in the z-domain. CZT does the same as the FFT algorithm, if k = Src.Length*0.5, FStart = 0 and FStop = FS/2. and Src.Length is power of two. CZT is slower, but more versatile then FFT. It can be faster then FFT, for large zero padding factors. Chirp transform algorithm was first proposed by Bluestein. Generalization of the algorithm called Chirp-Z transform obtains samples of the z-transform spaced equally in angle on a spiral contour in z-plane. More on this subject can be found in [1] (p. 629) and [2] (p. 393). Rabiner [2] proposed the CZT algorithm. References: [1] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989. [2] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975. *) procedure CZTInit(const State: TCztState; SrcLen: integer; SrcFloatPrecision: TMtxFloatPrecision; k: integer; FStart, FStop: double; FS: double = 2; RStart: double = 1; RStop: double = 1); overload; (*Computes the chirp z-transform. If the parameters to CZT function do not change between calls, some variables can be pre-computed and stored in to a state variable. The result is placed in to the Dst variable. The State variable needs to be initialized with a call to CztInit method. *) procedure CZT(const Src, Dst: TVec; const State: TCztState); overload; (*Compute the chirp z-transform. Compute the chirp z-transform of Src and place it in aResult. The starting frequency is zero and the stop frequency and FStop is at FS/2. k defines the number of steps within that band. RStart is the starting radius of the circle in the z-domain and RStop is the final radius of the circle in the z-domain. *) procedure CZT(const Src: TVec; k: integer; const aResult: TVec; RStart: double = 1; RStop: double = 1); overload; (*Compute the chirp z-transform. Compute the chirp z-transform of Src and place it in aResult. The starting frequency is defined with Offset and frequency step is defined with Step. The final frequency is at Offset + k*Step. k defines the number of frequency bins at which to estimated the amplitude and phase of the frequency. Example for computing the Step and Offset: Step := Expj(-(FStop - FStart)*2*Pi/(k*FS)); Offset := Expj(2*Pi*FStart/FS); *) procedure CZT(const Src: TVec; k: integer; const Step, Offset: TCplx; const aResult: TVec); overload; (*Compute the frequency response. Computes complex frequency response of the transfer function with Num in the numerator and Den in the denominator. The result is placed in the Response. WindowType specifies the window to be applied and WindowParam is window parameter required by some window types. If denominator is 1 pass nil for Den. The function returns the actual zero padding factor. Actual zero padding will be different from ZeroPadding, if Num and/or Den do not have power of two length. If Normalize is True the, frequency spectrum will be normalized to reflect the true amplitude. *) function FrequencyResponse(const Num,Den,Response: TVec; ZeroPadding: integer = 2; Normalize: boolean = false; WindowType: TSignalWindowType = wtRectangular; WindowParam: double = 40): double; overload; (*Computes s-domain frequency response (Laplace transform). Computes complex frequency response of the transfer function with Num in the numerator and Den in the denominator. The result is placed in the Response. Frequencies vector holds the frequencies [rad/sec] at which the frequency response should be evaluated. The requested frequencies should be logarithmically spaced, but this is not mandatory. More points can be specified in the areas where the frequency response changes rapidly. Optionally you can also specify the alpha parameter as in: s = alpha + j*Frequencies; The default value for alpha is zero. *) procedure FrequencyResponseS(const Num, Den, Frequencies, Response: TVec; alpha: double = 0); overload; (*Compute a logarithmic ramp. Fills RampVec vector with (base 10) logarithmic scale values from StartPower to StopPower. The number of values returned is defined with RampVec.Length. Compute 10 values of a logarithmic scale between powers of -1 and +1. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector RampVec = new Vector(10); SignalUtils.LogRamp(RampVec,-1,1); // RampVec = [0.1 0.1668 0.2782 0.4641 0.7742 // 1.2915 2.1544 3.5938 5.9948 10] MtxVecEdit.ViewValues(RampVec,"Logarithmic scale"); } *) procedure LogRamp(const RampVec: TVec; StartPower,StopPower: double); (*Compute Crest factor. Returns crest factor, if the zero mean signal had StdDev for standard deviation and Peak was the maximum deviation from the mean. Note Crest factor is usefull when monitoring ball bearings. *) function Crest(aStdDev, aPeak: double): double; overload; (*Returns crest factor, for complex data. *) function Crest(aStdDev, aPeak: TCplx): double; overload; (*Find the maximum deviation from the mean. Compute maximum deviation from the mean, if the Min is the minimum value found, max was the maximum value found and Mean is the average of the signal. *) function Peak(aMax, aMin, aMean: double): double; overload; (*Find the maximum deviation from the aMean for complex data.*) function Peak(aMax, aMin, aMean: TCplx): double; overload; (*Compute RMS of a frequency band from the frequency spectrum. Compute RMS value from the frequency spectrum instead from the time signal. Amplt contains the amplitude spectrum and SpectrumType defines the type of the amplitude spectrum. The RMS is computed from values from Index to Index+Len of the Amplt vector. This function allows computing an estimate of the RMS within a specified frequency band. RMS estimates made from frequency spectrum are not very accurate. The main reason for this is spectral leakage. While according to the Parsevals theorem, the RMS of the time signal is equal to the RMS of a frequency spectrum, this relation does not hold as soon as ones tries to compute the RMS of a subband. But this value, although not exactly a true RMS, may still serve its purpose to detect changes within specific frequency bands. The routine does not take in to account the amount of zero padding applied. If SpcTypeAdjust is True then the value of the RMS will be adjusted to compensate for the SpectrumType other then spAmplt. *) function RmsOfSpectrum(const Amplt: TVec; SpectrumType: TSpectrumType; SpcTypeAdjust: boolean = True; Index: integer = 0; Len: integer = -1): double; overload; (*Return the index of the nearest value. Return the index of the value within data where the expression abs(Data[Index]- ASample) is smallest. *) function FindNearest(const Data: TVec; ASample: double): integer; overload; (*Find the nearest peak. Converge to the nearest maximum in Data, if the start position is at Data[StartIndex]. Returns the position of the found maximum: Data[i-1] < Data[i] > Data[i+1]. *) function NearestMaximum(const Data: TVec; const StartIndex: integer): integer; overload; (*Find the nearest peak. Converge to the nearest peak of Data, if the start position is at Data[PeakIndexes[i]]. Returns the index position of the found peaks in PeakIndexes. *) procedure NearestMaximum(const Data: TVec; const PeakIndexes: TVecInt); overload; function NearestMean(const Data: TVec; var Index: integer; window: integer; var Mean : double): boolean; overload; (*Find the nearest minumum. Converge to the nearest minimum in Data, if the start position is at Data[StartIndex]. Returns the position of the found minimum: Data[i-1] > Data[i] < Data[i+1]. *) function NearestMinimum(const Data: TVec; const StartIndex: integer): integer; overload; (*Find the nearest zero crossing. Converge to the nearest zero crossing of Data, if the start position is at Data[Pos]. Return the position of the found zero crossing in Pos and amplitude in Y. Use linear interpolation, if LinInterpolate is True, to determine the position of the actual intersection of the Data time series with zero. Use Y to determine how close the interpolation came . Y will be zero in case of linear interpolation. *) procedure NearestZeroCrossing(const Data: TVec; var Pos: double; out Y: double; LinInterpolate: boolean = True); overload; (*Find the nearest DC crossing. Converge to the nearest DC crossing of Data, if the start position is at Data[Pos]. Return the position of the found DC crossing in Index and amplitude in Y. Use linear interpolation, if LinInterpolate is True, to determine the position of the actual intersection of the Data time series with zero. Use Y to determine how close the interpolation came (Y should be DC). *) procedure NearestDCCrossing(const Data: TVec; var Pos: double; out Y: double; DC: double; LinInterpolate: boolean = True); overload; (*Locate the next maximum. Return the next Maximum found in Src looking from offset to the right. Offset is modified to reflect the position of the found maximum. If a true maximum is not found the function returns false. *) function NextMaximum(const Src: TVec; var Offset: integer): boolean; overload; (*Locate the next minimum. Return the next Minimum found in Src looking from Offset to the right. Offset is modified to reflect the position of the found maximum. If a true minimum is not found the function returns false. *) function NextMinimum(const Src: TVec; var Offset: integer): boolean; overload; (*Locate the next zero crossing. Return the next zero crossing found in Src looking from Offset to the right. Offset is modified to reflect the position of the found zero crossing. *) procedure NextZeroCrossing(const Src: TVec; var Offset: integer); overload; (*Locate the next DC crossing. Return the next DC crossing found in Src looking from Offset to the right. Offset is modified to reflect the position of the found DC crossing. *) procedure NextDCCrossing(const Src: TVec; var Offset: integer; DC: double); overload; (*Return the number of peaks. Return the total count of peaks within Src starting at index to Index+Len. If Len = -1 end the search at Src.Length. *) function MaximumCount(const Src: TVec; Index: integer = 0; Len: integer = -1): integer; overload; (*Return the number of minimums. Return the total count of minimums within Src starting at index to Index+Len. If Len = -1 end the search at Src.Length. *) function MinimumCount(const Src: TVec; Index: integer = 0; Len: integer = -1): integer; overload; (*Return the number of zero crossings. Return the total count of zero crossings within Src starting at index to Index+Len. If Len = -1 end the search at Src.Length. *) function ZeroCrossingCount(const Src: TVec; Index: integer = 0; Len: integer = -1): integer; overload; (*Return the number of DC crossings. Return the total count of DC crossings within Src starting at index to Index+Len. If Len = -1 end the search at Src.Length. *) function DCCrossingCount(const Src: TVec; DC: double; Index: integer = 0; Len: integer = -1): integer; overload; (*Return the total count of zero crossings within Src starting at index to Index+Len. If Len = -1 end the search at Src.Length. Count DC crossings only in one way, from - to + (Negative = True) or from + to - (Negative = False). *) function DCCrossingCount(const Src: TVec; DC: double; Negative: boolean; Index: integer = 0; Len: integer = -1): integer; overload; (*Adjust Phase in radians to range between 0 and 2*Pi. Adjust Phase in radians to fall in range between 0 and 2*Pi. *) function CircularPhase(Phase: double): double; overload; (*Adjust frequency in radians to fall in range between 0 and 0.5. Convert Frequency in radians to range between 0 and 0.5 for real signals and between -0.5 and +0.5 for Complex signals. *) function CircularFrequency(Freq: double; Complex: boolean = false): double; overload; (*Unwrap the phase of the phase spectrum. The phase spectrum in Src is unwrapped and the result is placed in Dst. RunningPhase defines how the unwrapped phased will be further processed. Tolerance defines the maximum change allowed between consecutive values, before the 360 (or 2*Pi) degrees is added (or subtracted). Tolerance must always be specified in degrees. If radians is True, the Src should contain the phase spectrum in radians, otherwise in degrees. If the time signal was padded with zeros, the ActualZeroPadding should hold the ratio: LengthAfterZeroPadding/LengthBeforeZeroPadding. ActualZeroPadding factor is needed to accurately determine the total phase delay. If phase delay is not an issue the ActualZeroPadding factor can be set to 1. The Src may not have central spectrum element as its last value. If the phase spectrum was obtained from FFT on a real data, the vector must be subranged before passed to the phase unwrapping routine. Phase unwrapping is extensively discussed in [1] (p. 790), especially because of its application in complex cepstrum estimation. References: [1] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989. *) function PhaseUnwrap(const Src, Dst: TVec; RunningPhase: TRunningPhase; ActualZeroPadding: double = 1; Radians: boolean = false; Tolerance: double = 180): double; (*Add phase lag to the unwrapped phase. Phase lag, as returned by the PhaseUnwrap routine, will be added to Src and the result will be placed in Dst. If Radians is True, the phaseLag was computed in radians. *) procedure PhaseWrap(const Src, Dst: TVec; PhaseLag: double; Radians: boolean = false); (*Compute real cepstrum. Compute real cepstrum of Src and place the result in Dst. If DCDump is True, the DC component will be removed prior to computing FFT. WindowType specifies the window type used for the forward FFT. Real cepstrum can be used to find periodicites in the frequency spectrum itself. It is namely defined as an inverse FFT of the logarithm of the magnitude spectrum [1] (p. 770, eq 12.8): rc = IFFT(log(abs(FFT(x))) x.. time series Sometimes we also find the following definition: rc = FFT(log(abs(FFT(x))) A good choice for the window function is the Hanning window. References: [1] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989. *) procedure RealCepstrum(const Src, Dst: TVec; DumpDC: boolean = True; WindowType: TSignalWindowType = wtHanning; Magnitude: boolean = false); (*Compute complex cepstrum. Compute complex cepstrum of Src and place the result in Dst. ZeroPadding defines the amount of zero padding used. Large zero padding factors will give better phase unwrapping results. The function returns integer phase lag subtracted from the unwrapped phase. This parameter can then be used for inverse complex cepstrum. Different methods to compute complex cepstrum and its applications in homomorphic filtering can be found in [1] (Chapter 12) and [2] (p. 687). It is defined as: rc = IFFT(log(FFT(x))) x.. time series Complex cepstrum can be used (among other things) to detect and measure echo delays in the signal and for filtering applications. The user should considering windowing the time time signal prior to passing it to this routine. The main problem of efficient complex cepstrum estimation is the estimation of the complex logarithm. As mentioned, the function returns integer phase lag subtracted from the unwrapped phase. This lag is subtracted because the unwrapped phase otherwise formes a step function for the FFT which follows. By pushing the begining and the end of the unwrapped phase towards zero, the FFT which follows will be more effective and will give a more clear picture, because there will be no circular discontinuity in the unwrapped phase. Similar effect is achieved with window functions and when removing a trend. For example, if a time series is superimposed on a linear regression line, it makes sense first to subtract linear regression line (de-trend) and to form a zero mean signal before performing an FFT, because otherwise the signal does not start close to where it ends. (FFT sees the signal as a circular infinite signal.) References: [1] Discrete-time signal processing, Oppenheim and Schafer, Prentice-Hall, 1989 [2] Theory and application of digital signal processing, Lawrence R. Rabiner and Bernard Gold. Prentice-Hall, 1975 *) function CplxCepstrum(const Src, Dst: TVec; ZeroPadding: integer = 16): double; (*Compute the inverse complex cepstrum. Compute inverse complex cepstrum of Src and place the result in Dst. Phase lag is the value returned by CplxCepstrum. TargetLength is the length of the original (not zero padded) signal passed to the CplxCepstrum routine. Forward and inverse complex cepstrum. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector a = new Vector(0); Vector b = new Vector(0); double k; a.SetIt(false,new double[8] {1,2,3,4,3,2,-1,1}); k = SignalUtils.CplxCepstrum(a,b,64); SignalUtils.CplxCepstrumInv(b,a,k,a.Length); // "a" again becomes the same as on the input a = [1,2,3,4,3,2,-1,1] MtxVecTee.DrawIt(a,"Original time signal",false); } *) procedure CplxCepstrumInv(const Src, Dst: TVec; PhaseLag: double; TargetLength: integer); (*Initialize the generation of a sine function. Initialize a sine signal ToneState with frequency Freq, initial Phase and amplitude Mag. Set Complex to true to generate a complex signal. ToneState is to be passed to the Tone routine to generate the actuall time series. *) procedure ToneInit(Freq, Phase, Mag: double; var ToneState: TToneState; Complex: boolean = false); (*Generate a sine function. Place a tone defined with a call to ToneInit in Dst. The routine is stream capable. *) procedure Tone(const Dst: TVec; var ToneState: TToneState); overload; (*Generate a sine function. Generate a single tone wave signal with frequency Freq, initial Phase and amplitude Mag. Set Complex to true to generate a complex signal. The routine is not stream capable. *) procedure Tone(const Dst: TVec; SampleCount: integer; Freq, Phase, Mag: double; Complex: boolean = false); overload; (*Initialize the generation of a square tone. Initialize a square signal ToneState with frequency Freq, initial Phase and amplitude Mag. Set Complex to true to generate a complex signal. State is to be passed to the SquareTone routine to generate the actuall time series. The time series is generated by Fourier Series expansion. For very low frequencies, for example 1Hz, and sampling frequency of 192kHz, this would result in 48000 distinct sine tones to be generated and would require proportional computational power. The maxHarmonics parameter defines the maximum number of harmonics (including the fundamental) to be generated for the approximation of the square signal. This can help limit the CPU requirements in case of very low frequencies. About 200 Harmonics would already give a very good shape of "square". *) procedure SquareToneInit(Freq, Phase, Mag: double; var State: TSquareToneState; Complex: boolean = false; const MaxHarmonics: integer = -1); (*Generate a square wave function. Place a tone defined with a call to ToneInit in Dst. The routine is stream capable. Dst.IsDouble will be used to determine floating point precision. *) procedure SquareTone(const Dst: TVec; var State: TSquareToneState); overload; (*Generate a square wave function. Generate a square wave signal with frequency Freq, initial Phase and amplitude Mag. Set Complex to true to generate a complex signal. The routine is not stream capable. Dst.IsDouble will be used to determine floating point precision. *) procedure SquareTone(const Dst: TVec; SampleCount: integer; Freq, Phase, Mag: double; Complex: boolean = false; const MaxHarmonics: integer = -1); overload; (*Initialize the generation of a triangle function. Initialize a triangular signal ToneState with frequency Freq, initial Phase, Asym assymetry and amplitude Mag. Set Complex to true to generate a complex signal. ToneState is to be passed to the TriangleTone routine to actually generate the time series. *) procedure TriangleToneInit(Freq, Phase, Mag, Asym: double; var ToneState: TTriangleState; Complex: boolean = false); (*Generate a triangle function. Place a tone defined with a call to TriangleToneInit in Dst. The routine is stream capable. *) procedure TriangleTone(const Dst: TVec; var ToneState: TTriangleState); (*Compute the probability that given probability distributions are (not) the same. The function integrates the envelope of a set of gaussian PDF functions defined with Means (holding mean values) and StdDevs (holding standard deviations) arrays. The start of the integration interval is determined by the PDF with smallest value of expression: Mean-3*StdDev. The end of the integration interval is determined by the PDF with largest value of expression: Mean+3*StdDev. If all PDF functions are distinctly a part, the value of the integral will be equal to the number of PDF functions. If all PDF functions exactly overlapp each other, the value of the integral will be 1. This function is therefore a measure of overlapping of a set of gaussian probability density functions. The result will return this measure in percent. 0% for full overlapping and 100% for no overlapping. Precision defines integration precision by defining the step: h = (Smallest StdDev)*3/Precision. The drawback of this approach is that CPU usage grows with precision, distance between the mean values and smaller standard deviations. If the precision is too small, the function will degenerate in to histogram calculation. If you are integrating only over two distributions, that is not a serious problem and the result will remain meaningfull, but not very accurate. *) function DistinctCDF(const Means,StdDevs: array of double; Precision: integer = 100; MaxSteps: integer = 10000): double; overload; (*Returns a measure for the probability that x belongs to the normal distribution with Mean and StdDev. The function returns the 1- (integral of PDF on the intervals [-Inf,mean-abs(mean-x)],[mean+abs(mean-x),+Inf]). The function returns near 100, if x is further then 4*StdDev away from mean and returns 0, if x = mean. *) function DistinctCDF(x, Mean, StdDev: double): double; overload; (*Convert a TSpectrumType type to a string. The function returns a description of a TSpectrumType type specified by the SpectrumType parameter. *) function SpectrumTypeToString(SpectrumType: TSpectrumType): string; overload; (*Align string by inserting space. AString is padded with spaces to match ColumnWidth according to the value of Align parameter. *) function AlignString(const AString: string; ColumnWidth: integer; Align: TFixedTextAlign = ftaLeftAlign): string; (*Compute standard deviation. Returns standard deviation according to the formula: n*Sum(x^2) - Sum(x)^2 StdDev = ( ------------------------- )^0.5 n*(n-1) n... number of averages taken Sum(x^2)... sum of squares of samples Sum(x)... sum of samples *) function StdDev(SumOfSquares, Sum: double; Averages: integer): double; overload; (*Return standard deviation in aResult.*) procedure StdDev(const SumOfSquares, Sum: TVec; Averages: integer; const aResult: TVec); overload; (*Return standard deviation in aResult.*) procedure StdDev(const SumOfSquares, Sum: TMtx; Averages: integer; const aResult: TMtx); overload; (*Convert amplitude to decibels. Converts Data (amplitude) to decibels. Base is the base of the logarithm and should be 10, if decibels are desired. The logarithm is then scaled by 20 according to the formula LogN(Base,Value)*20. Span defines the dynamic range in dB on the left axis. The values below -span will be clipped. If Normalized is True, the maximum will be set to zero and minimum to -Span. The function returns -Span. *) function AmpltToDb(const Data: TDenseMtxVec; Base, Span, aMax: double; Normalized: boolean): double; (*Convert amplitude to logarithmic scale. Applies logarithm to Data. Base is the base of the logarithm and should be 10, if decibels are desired. The logarithm is then scaled by aScale according to the formula LogN(Base,Value)*aScale. Span defines the dynamic range on the left axis. If aScale is 20 and base is 10, the Span is in dB. The values below -span will be clipped. If Normalized is True, the maximum will be set to zero and minimum to -Span. The function returns -Span. *) function AmpltToLog(const Data: TDenseMtxVec; Base, Span, aMax, aScale: double; Normalized: boolean): double; (*Returns description of the signal. Returns description of the signal with given data. To ommit NumberOfrecords just enter a zero. *) function SignalFormatDescription(FS: double; Precision: TPrecision; Ch: integer; NumberOfRecords: integer = 0): string; (*The Discrete Fourier transformation (DFT) for a given frequency. Calculates the Discrete Fourier transformation (DFT) for a given frequency. Goertz returns the DFT at the Frequency. Each value of the DFT computed by the Goertzel algorithm takes 2N+2 real multiplications and 4N real additions. FFT computes the DFT with N*log2(N) of real multiplications and additions. The Goertz method is faster, than direct DFT, if we need values at consecutively different frequencies. At every call the Freq parameter is different. If the frequencies, at which we need to evalute the spectrum are fixed, then the direct DFT method is faster than Goertz. The function applies Bonzanigo's phase correction. This means, that phase information is reliable and correct also when computed at an arbitrary non-integer frequency. An "integer frequency" is such that an (exact) integer number of periods fits in to the data (signal) of the specific length. DFT of a single frequency. using Dew.Math; using Dew.Math.Editors; using Dew.Math.Units; using Dew.Signal; using Dew.Signal.Units; using Dew.Math.Tee; using Dew.Signal.Tee; private void button1_Click(object sender, EventArgs e) { Vector a = new Vector(0); SignalUtils.Tone(a,256,5.5/256,0,12.53,false); //generate a tone //And now detect the amplitude at non-integer frequency: MessageBox.Show("Amplitude = " + Math387.SampleToStr(Math387.CAbs(SignalUtils.Goertz(a,5.5/256))/128,0,15)); } *) function Goertz(const Src: TVec; Freq: double; PhaseCorrection: TGoertzPhaseCorrection = gpcBonzanigo): TCplx; overload; (*The Discrete Fourier transformation (DFT) for a given frequency. SrcIndex and srcLen define the sub-range of Src on which to compute the function. *) function Goertz(const Src: TVec; Freq: double; const srcIndex: integer; srcLen: integer = -1; PhaseCorrection: TGoertzPhaseCorrection = gpcBonzanigo): TCplx; overload; (*The Discrete Fourier transformation (DFT) for given two frequencies. Calculates the Discrete Fourier transformation (DFT) for two given frequencies. Goertz returns the DFT at the two frequencies pass as a parameter. Each value of the DFT computed by the Goertzel algorithm takes 2N+2 real multiplications and 4N real additions. FFT computes the DFT with N*log2(N) of real multiplications and additions. The Goertz method is faster, than direct DFT, if we need values at consecutively different frequencies. if at every call the Freq0 and Freq1 parameters are different. If the frequencies, at which we need to evalute the spectrum are fixed, then the direct DFT method is faster. *) procedure GoertzTwo(const Src: TVec; Freq0, Freq1: double; var Result0, Result1: TCplx; PhaseCorrection: TGoertzPhaseCorrection = gpcBonzanigo); overload; (*The Discrete Fourier transformation (DFT) for given two frequencies. SrcIndex and srcLen define the sub-range of Src on which to compute the function. *) procedure GoertzTwo(const Src: TVec; Freq0, Freq1: double; var Result0, Result1: TCplx; const srcIndex: integer; srcLen: integer; PhaseCorrection: TGoertzPhaseCorrection = gpcBonzanigo); overload; (*The Discrete Fourier transformation (DFT) for vector of frequencies. Calculates the Discrete Fourier transformation (DFT) for an array of frequencies defined in the Freq parameter. Goertz returns the DFT of the frequencies pass as a parameter in the Dst variable. Each value of the DFT computed by the Goertzel algorithm takes 2N+2 real multiplications and 4N real additions. FFT computes the DFT with N*log2(N) of real multiplications and additions. The Goertz method is faster, than direct DFT, if we need values at consecutively different frequencies. If at every call the values in Freq parameter are different. If the frequencies, at which we need to evalute the spectrum are fixed between calls, then the direct DFT method is faster. *) procedure Goertz(const Src, Freq, Dst: TVec; PhaseCorrection: TGoertzPhaseCorrection = gpcBonzanigo); overload; (*The Discrete Fourier transformation (DFT) for vector of frequencies. SrcIndex and srcLen define the sub-range of Src on which to compute the function. *) procedure Goertz(const Src, Freq, Dst: TVec; const srcIndex: integer; srcLen: integer; PhaseCorrection: TGoertzPhaseCorrection = gpcBonzanigo); overload; (*Computes FIR filter from specified frequency spectrum. Amplt defines desired frequency spectrum (linear scale) and Freq defines the frequency at which the spectrum was sampled. Frequencies must be between 0 and 1 and have to include 0 and 1. H contains the impulse response upon completion. H.Length specifies the length of impulse response on input. The accuracy of filter matching the desired response greatly depends upon the length of the filter especially, when the desired response contains sharp or steep transitions. The function internally performs linear interpolation between points specified by Amplt and Freq vectors. The desired precision will be picked from Amplt.FloatPrecision. *) procedure InverseFilter(const Amplt, Freq, H: TVec; WindowType: TSignalWindowType = wtBlackman; WindowParam: double = 0); (*Computes FIR filter with desired noise/signal coloring response. Parameter dBPerDecade specifies the filters response in terms of decibels per decade roll-off or roll-up. The parameter can be positive or negative. When euqal to 10, the FIR filter generated will have pink (noise) response. When equal to 20 it will generate brown(ian) (noise) response. Paramter FS specifies the sampling frequency and StartBW the location of the "knee" of the filter at which the linear dB/decade response begins. StartBW may not be too close to 0. The closer the value is to zero the longer will be the filter. Longer filter means longer delay and slower computation times. To produce for example pink noise first generate random noise and then filter the noise with the filter response H: x.RandGauss(); FirFilter(x, H); The desired precision will be picked from H.FloatPrecision *) procedure ColoredNoiseFIR(const H: TVec; const dBPerDecade, StartBW, FS: double); (*Returns string representation of TMusicalNotePitch. Returns string representation of TMusicalNotePitch including the Octave. Example: C#2 *) function MusicalNotePitchToStr(const Src: TMusicalNotePitch; const Octave: integer): string; overload; (*Initializes MusicalScale according to Pitch standard and required octave count. The MusicalScale parameter is initialized according to parameters. The MusicalScale is required by the MusicalNotePitch function. Default pitch standard is 440Hz and default Ocatave count is 11. This should suffice for most application. The pitch standard of 440Hz applies to note A in 4th octave. There are however many variations around the world. Cuba uses 436, Hungary 438, Berlin orchestra 443, etc.. References: [1] https://www.thebeliever.net/pitch-battles/ *) procedure MusicalScaleInit(const MusicalScale: TVec; PitchStandard: double = 440; OctaveCount: integer = 11); (*Returns musical note pitch, the octave and pitch error in Hz. The frequency in Hz to convert to musical note pitch. The difference between the nearest musical note pitch and Freq in Hz. Returns the octave number on output. The musical scale as returned by the MusicalScaleInit method. Returns musical note pitch as the result, the octave number and pitch error in Hz in the parameters. *) function MusicalNotePitch(const Freq: double; out pitchError: double; out freqOctave: integer; const aMusicalScale: TVec): TMusicalNotePitch; overload; (*Returns musical note pitch, the octave and pitch error in Hz. The array of frequencies in Hz to convert to musical note pitch. The difference between the nearest musical note pitch and Freq in Hz. To obtain error in % compute: pitchError[i]/Freq[i]*100% Contains the octave numbers on output. The musical scale as returned by the MusicalScaleInit method. aMusicalScale.FloatPrecision will be used as the input parameter to indicate the desired precision (single or double). Contains musical note pitch on the output. The values in MusicalNotePitch need to by typcasted to an enumerated type: MusicalNotePitchToStr(TMusicalNotePitch(MusicalNotePitch[i])); *) procedure MusicalNotePitch(const Freq: TVec; const pitchError: TVec; const freqOctave: TVecInt; const aMusicalScale: TVec; const aMusicalNotePitch: TVecInt); overload;