Time-Dependent Fourier Transform


Signals of Physical Processes

Some time signals (or time sequence) exhibit changes in amplitude, frequency and phase with time. When computing the Fourier Transform of these signals (sequence), the Fourier Transform is not appropriate. One easy solution is to divide the original signal (sequence) in blocks and compute the Fourier Transform for each block as shown in the figure below.

ContinuosTimeBlockFt

DiscreteTimeBlockFt

Time-Dependent Fourier Transform

The Time-Dependent Fourier Transform (TDFT) is also known as the short-time Fourier Transform (STFT). The TDFT can be defined for continuous time signals and for discrete time signals. The equation below shows the definition of the TDFT. As it can be seen the input sequence x[n] is transform in a sequence of two discrete variables: n and k.

TDFT

The Implementation of the Time-Dependent Fourier Transform

The previous definition of the TDFT can be rewritten so that it can be implemented as a filter bank as shown in the figure below. As it can be seen, an input sequence x[n] is transform in N sequences.

FilterBank

Kaiser Window in a TDFT

When the Kaiser window is used to compute the TDFT, it is possible to adjust the shape of the window using the parameter beta as shown below.

KaiserWindow

Gabor Transform

When a Gaussian window is used in the Time-Dependent Fourier Transform, the transform is called the Gabor Transform. The equation of the Gaussian window is shown below.

GaussianWindow

Problem 1
Create a Dialog Application called TimeDependentFTView using Wintempla to show the Time-Dependent Fourier Transform. Add a Wintempla Custom Control called Display using the menu Tools > Add Wintempla Item... > Custom Control. Then, edit the GUI as shown in the figure below.

TdftGui

TimeDependentFTView.h
#pragma once //______________________________________ TimeDependentFTView.h
#include "resource.h"

#include "Display.h"
#define RESOLUTION 1000
#define MAX_BETA 10.0
#define MAX_NUM_FREQ POINT_COUNT
#define MAX_WINDOW_WITH (MAX_NUM_FREQ-1)

class TimeDependentFTView: public Win::Dialog
{
public:
     TimeDependentFTView()
     {
          positionA = 0;
          positionB = 0;
          positionC = RESOLUTION/2;
          positionBeta = RESOLUTION/2;
          positionWindowWidth = RESOLUTION/2;
          positionNumFreq = RESOLUTION;
          y.resize(POINT_COUNT);
     }
     ~TimeDependentFTView()
     {
     }
     int positionA;
     int positionB;
     int positionC;
     int positionBeta;
     int positionWindowWidth;
     int positionNumFreq;
     //Math::GaborTransform gaborTransform;
     Math::TimeDependentFT transform;
     valarray<double> y;
     void Run();
     void CreateFilterBank();
protected:
     //______ Wintempla GUI manager section begin: DO NOT EDIT AFTER THIS LINE
     ...
};

TimeDependentFTView.cpp
#include "stdafx.h" //________________________________________ TimeDependentFTView.cpp
#include "TimeDependentFTView.h"

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE , LPTSTR cmdLine, int cmdShow){
     TimeDependentFTView app;
     return app.BeginDialog(IDI_TIMEDEPENDENTFTVIEW, hInstance);
}

void TimeDependentFTView::Window_Open(Win::Event& e)
{
     //________________________________________________________ sldA
     sldA.SetRange(0, RESOLUTION);
     sldA.Position = 0;
     tbxA.DoubleValue = 0.0;

     //________________________________________________________ sldB
     sldB.SetRange(0, RESOLUTION);
     sldB.Position = 0;
     tbxB.DoubleValue = 0.0;

     //________________________________________________________ sldC
     sldC.SetRange(0, RESOLUTION);
     sldC.Position = RESOLUTION/2;
     tbxC.DoubleValue = MAX_VALUE/2.0;
     //________________________________________________________ sldNumbFrequencies
     sldNumbFrequencies.SetRange(1, RESOLUTION);
     sldNumbFrequencies.Position = RESOLUTION;
     tbxNumFrequencies.IntValue = (int)((double)MAX_NUM_FREQ*sldNumbFrequencies.Position/RESOLUTION);
     //________________________________________________________ sldWindowWidth
     sldWindowWidth.SetRange(1, RESOLUTION);
     sldWindowWidth.Position = RESOLUTION/2;
     tbxWindowWidth.IntValue = (int)(0.5*MAX_WINDOW_WITH*sldWindowWidth.Position/RESOLUTION);
     //________________________________________________________ sldBeta
     sldBeta.SetRange(0, RESOLUTION);
     sldBeta.Position = RESOLUTION/2;
     tbxBeta.DoubleValue = MAX_BETA*sldBeta.Position/RESOLUTION;
     //________________________________________________________ xyInput
     xyInput.CaptionX = L"Time";
     xyInput.CaptionY = L"y";
     xyInput.MinX= 0.0;
     xyInput.MaxX= 1.0;
     xyInput.MinY= -1.2;
     xyInput.MaxY= 1.2;
     xyInput.DivisionCountY = 12;
     xyInput.Graphs.Add(POINT_COUNT);
     xyInput.SubgridColor = RGB(0, 20, 60);
     xyInput.Graphs[0].Color = RGB(0, 255, 0);
     const double delta_t = 1.0/POINT_COUNT;
     for(int i=0; i<POINT_COUNT; i++)
     {
          xyInput.Graphs[0][i].x = i*delta_t;
          xyInput.Graphs[0][i].y = 0.0;
     }
     xyInput.RefreshAll();


     radioSin.Checked = true;
     CreateFilterBank();
     Run();
}

void TimeDependentFTView::CreateFilterBank()
{
     const double beta = tbxBeta.DoubleValue;
     const int windowWidth = tbxWindowWidth.IntValue;
     const int numFreq = tbxNumFrequencies.IntValue;

     if (transform.Create(windowWidth, numFreq, beta) == false)
     {
          this->tbxWindowWidth.ShowBalloonTip(L"The window width must be less than the number of frequencies", L"TimeDependentFTView", TTI_ERROR);
     }
     else
     {
          this->tbxWindowWidth.HideBalloonTip();
     }

}

void TimeDependentFTView::sldA_Hscroll(Win::Event& e)
{
     const int newPosition = sldA.Position;
     if (newPosition == positionA) return;
     positionA = newPosition;
     //
     tbxA.DoubleValue = (MAX_VALUE*sldA.Position/RESOLUTION);
     Run();
}

void TimeDependentFTView::sldB_Hscroll(Win::Event& e)
{
     const int newPosition = sldB.Position;
     if (newPosition == positionB) return;
     positionB = newPosition;
     //
     tbxB.DoubleValue = (MAX_VALUE*sldB.Position/RESOLUTION);
     Run();
}

void TimeDependentFTView::sldC_Hscroll(Win::Event& e)
{
     const int newPosition = sldC.Position;
     if (newPosition == positionC) return;
     positionB = newPosition;
     //
     tbxC.DoubleValue = (MAX_VALUE*sldC.Position/RESOLUTION);
     Run();
}

void TimeDependentFTView::Run()
{
     Win::HourGlassCursor hgc(true);
     const double a = tbxA.DoubleValue;
     const double b = tbxB.DoubleValue;
     const double c = tbxC.DoubleValue;

     double t;
     double f;
     const double delta_t = 1.0/POINT_COUNT;
     const double twopi = 2.0*M_PI;
     int i;
     if (radioSin.Checked == true)
     {
          for(i = 0; i < POINT_COUNT; i++)
          {
               t = i*delta_t;
               f = a*t*t+b*t+c;
               y[i] = sin(twopi*f*t);
               xyInput.Graphs[0][i].y = y[i];
          }
     }
     else if (radioTriangular.Checked == true)
     {
          for(i = 0; i < POINT_COUNT; i++)
          {
               t = i*delta_t;
               f = a*t*t+b*t+c;
               y[i] = Math::Dsp::Triangular(twopi*f*t, twopi);
               xyInput.Graphs[0][i].y = y[i];
          }
     }
     else if (radioRectangular.Checked == true)
     {
          for(i = 0; i < POINT_COUNT; i++)
          {
               t = i*delta_t;
               f = a*t*t+b*t+c;
               y[i] = Math::Dsp::Rectangular(twopi*f*t, twopi);
               xyInput.Graphs[0][i].y = y[i];
          }
     }
     xyInput.RefreshAll();
     transform.Transform(y, customControlDisplay.output);
     //gaborTransform.Transform(y, customControlDisplay.output);
     customControlDisplay.UpdateImage();
}

void TimeDependentFTView::radioSin_Click(Win::Event& e)
{
     Run();
}

void TimeDependentFTView::radioTriangular_Click(Win::Event& e)
{
     Run();
}

void TimeDependentFTView::radioRectangular_Click(Win::Event& e)
{
     Run();
}

void TimeDependentFTView::sldNumbFrequencies_Hscroll(Win::Event& e)
{
     const int newPosition = sldNumbFrequencies.Position;
     if (newPosition == positionNumFreq) return;
     positionNumFreq = newPosition;
     //
     tbxNumFrequencies.IntValue = (int)((double)MAX_NUM_FREQ*newPosition/RESOLUTION);
     CreateFilterBank();
     Run();
}

void TimeDependentFTView::sldWindowWidth_Hscroll(Win::Event& e)
{
     const int newPosition = sldWindowWidth.Position;
     if (newPosition == positionWindowWidth) return;
     positionWindowWidth = newPosition;
     //
     tbxWindowWidth.IntValue = (int)(0.5*MAX_WINDOW_WITH*positionWindowWidth/RESOLUTION);
     CreateFilterBank();
     Run();
}

void TimeDependentFTView::sldBeta_Hscroll(Win::Event& e)
{
     const int newPosition = sldBeta.Position;
     if (newPosition == positionBeta) return;
     positionBeta = newPosition;
     //
     tbxBeta.DoubleValue = MAX_BETA*positionBeta/RESOLUTION;
     CreateFilterBank();
     Run();
}

Display.h
#pragma once

#define MAX_VALUE 100.0
#define POINT_COUNT 512
#define BITMAP_WIDTH 512
#define BITMAP_HEIGHT 512

class Display: public Win::Window
{
public:
     Display();
     ~Display();
     void ComputeBits();
     bool IsEvent(Win::Event& e, int notification);
     MATRIXC output;
     void UpdateImage();
private:
     DWORD* bits;
     CG::DDBitmap bitmap;
     const wchar_t * GetClassName(void){return L"DISPLAY";}
     static bool isRegistered;
protected:
     //______ Wintempla GUI manager section begin: DO NOT EDIT AFTER THIS LINE
     void Window_Open(Win::Event& e);
     void Window_Paint(Win::Event& e);
     void Window_Size(Win::Event& e);
};

Display.cpp
// Display.cpp
#include "stdafx.h"
#include "Display.h"

bool Display::isRegistered= false;

Display::Display()
{     
     if (!this->isRegistered)
     {

          this->RegisterClassEx(
               LoadCursor(NULL, IDC_ARROW), // Cursor: IDC_IBEAM, IDC_WAIT, IDC_CROSS, ...
               (HBRUSH)(COLOR_BTNFACE+1)); //Background: (HBRUSH)(COLOR_WINDOW+1)), ::GetStockObject(BLACK_BRUSH)...
          this->isRegistered = true;
     }
     bits = NULL;
}

Display::~Display()
{
     if (bits != NULL) delete [] bits;
}

void Display::UpdateImage()
{
     int row, col;
     double value;
     const int data_rows = output.size();
     const int data_cols = (data_rows == 0) ? 0 : output[0].size();
     int maxrow, maxcol;
     const complex<double> maximum = Math::Statistics::GetMagnitudeMax(output, maxrow, maxcol);
     if (maxrow == -1) return;
     double maxMagnitude = abs(maximum);
     if (maxMagnitude== 0) maxMagnitude = 0.000001;

     COLORREF color;
     //
     for(row = 0; row < BITMAP_HEIGHT; row++)
     {
          for(col = 0; col < BITMAP_WIDTH; col++)
          {
               if (row < data_rows && col < data_cols)
               {
                    value = abs(output[row][col]);
                    value /= maxMagnitude;
                    color = Sys::Convert::DoubleToColorRef(value, false, 5);
               }
               else
               {
                    color = RGB(255, 255, 255);
               }
               bits[col + BITMAP_WIDTH*(BITMAP_HEIGHT-row-1)] = color;
          }
     }
     bitmap.CreateCompatibleFromBits(hWnd, BITMAP_WIDTH, BITMAP_HEIGHT, bits);
     ::InvalidateRect(hWnd, NULL, FALSE);
}

void Display::Window_Open(Win::Event& e)
{
     bits = new DWORD[BITMAP_WIDTH*BITMAP_HEIGHT];
}

void Display::Window_Paint(Win::Event& e)
{
     CG::Gdi gdi(e.hWnd, true, false);
     gdi.DrawCompatibleBitmap(bitmap, 0, 0);
}

void Display::Window_Size(Win::Event& e)
{
}

bool Display::IsEvent(Win::Event& e, int notification)
{
     if (e.uMsg!=WM_COMMAND) return false;
     const int id = LOWORD(e.wParam);
     const int notificationd = HIWORD(e.wParam);
     if (id != this->id) return false;
     if (notificationd!=notification) return false;
     return true;
}


TimeDependentFTViewRun

Problem 2
Create a Dialog Application called GaborView using Wintempla to show the Gabor Transform. The file Display.h and Display.cpp are the same as those of problem 1. After creating the application edit the GUI as shown, copy the files Display.h and Display.cpp from the TimeDependentFTView project to the folder of the GaborView project. Using the menu Project > Add existing item... , add the files Display.h and Display.cpp to the GaborView project.

GaborViewGui

GaborView.h
#pragma once //______________________________________ GaborView.h
#include "resource.h"
#include "Display.h"
#define RESOLUTION 1000

class GaborView: public Win::Dialog
{
public:
     GaborView()
     {
          positionA = 0;
          positionB = 0;
          positionC = RESOLUTION/2;
          positionNumFreq = POINT_COUNT;
          y.resize(POINT_COUNT);
     }
     ~GaborView()
     {
     }
     int positionA;
     int positionB;
     int positionC;
     int positionNumFreq;
     Math::GaborTransform transform;
     valarray<double> y;
     void Run();
     void CreateFilterBank();
protected:
     ...
};

GaborView.cpp
#include "stdafx.h" //________________________________________ GaborView.cpp
#include "GaborView.h"

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE , LPTSTR cmdLine, int cmdShow){
     GaborView app;
     return app.BeginDialog(IDI_GABORVIEW, hInstance);
}

void GaborView::Window_Open(Win::Event& e)
{
     //________________________________________________________ sldA
     sldA.SetRange(0, RESOLUTION);
     sldA.Position = 0;
     tbxA.DoubleValue = 0.0;

     //________________________________________________________ sldB
     sldB.SetRange(0, RESOLUTION);
     sldB.Position = 0;
     tbxB.DoubleValue = 0.0;

     //________________________________________________________ sldC
     sldC.SetRange(0, RESOLUTION);
     sldC.Position = RESOLUTION/2;
     tbxC.DoubleValue = MAX_VALUE/2.0;

     //________________________________________________________ sldNumbFreq
     sldNumbFreq.SetRange(1, POINT_COUNT);
     sldNumbFreq.Position = POINT_COUNT/2;
     tbxNumbFreq.IntValue = POINT_COUNT/2;

     //________________________________________________________ xyInput
     xyInput.CaptionX = L"Time";
     xyInput.CaptionY = L"y";
     xyInput.MinX= 0.0;
     xyInput.MaxX= 1.0;
     xyInput.MinY= -1.2;
     xyInput.MaxY= 1.2;
     xyInput.DivisionCountY = 12;
     xyInput.Graphs.Add(POINT_COUNT);
     xyInput.SubgridColor = RGB(0, 20, 60);
     xyInput.Graphs[0].Color = RGB(0, 255, 0);
     const double delta_t = 1.0/POINT_COUNT;
     for(int i=0; i<POINT_COUNT; i++)
     {
          xyInput.Graphs[0][i].x = i*delta_t;
          xyInput.Graphs[0][i].y = 0.0;
     }
     xyInput.RefreshAll();
     //
     radioSin.Checked = true;
     CreateFilterBank();
     Run();
}

void GaborView::CreateFilterBank()
{
     const int numFreq = tbxNumbFreq.IntValue;

     if (transform.Create(numFreq) == false)
     {
          this->Text = L"The input parameters are invalid";
     }
     else
     {
          this->Text = L"GaborView";
     }
}

void GaborView::sldA_Hscroll(Win::Event& e)
{
     const int newPosition = sldA.Position;
     if (newPosition == positionA) return;
     positionA = newPosition;
     //
     tbxA.DoubleValue = (MAX_VALUE*sldA.Position/RESOLUTION);
     Run();
}

void GaborView::sldB_Hscroll(Win::Event& e)
{
     const int newPosition = sldB.Position;
     if (newPosition == positionB) return;
     positionB = newPosition;
     //
     tbxB.DoubleValue = (MAX_VALUE*sldB.Position/RESOLUTION);
     Run();
}

void GaborView::sldC_Hscroll(Win::Event& e)
{
     const int newPosition = sldC.Position;
     if (newPosition == positionC) return;
     positionB = newPosition;
     //
     tbxC.DoubleValue = (MAX_VALUE*sldC.Position/RESOLUTION);
     Run();
}

void GaborView::sldNumbFreq_Hscroll(Win::Event& e)
{
     const int newPosition = sldNumbFreq.Position;
     if (newPosition == positionNumFreq) return;
     positionNumFreq = newPosition;
     //
     tbxNumbFreq.IntValue = positionNumFreq;
     CreateFilterBank();
     Run();
}

void GaborView::Run()
{
     Win::HourGlassCursor hgc(true);
     const double a = tbxA.DoubleValue;
     const double b = tbxB.DoubleValue;
     const double c = tbxC.DoubleValue;

     double t;
     double f;
     const double delta_t = 1.0/POINT_COUNT;
     const double twopi = 2.0*M_PI;
     int i;
     if (radioSin.Checked == true)
     {
          for(i = 0; i < POINT_COUNT; i++)
          {
               t = i*delta_t;
               f = a*t*t+b*t+c;
               y[i] = sin(twopi*f*t);
               xyInput.Graphs[0][i].y = y[i];
          }
     }
     else if (radioTriangular.Checked == true)
     {
          for(i = 0; i < POINT_COUNT; i++)
          {
               t = i*delta_t;
               f = a*t*t+b*t+c;
               y[i] = Math::Dsp::Triangular(twopi*f*t, twopi);
               xyInput.Graphs[0][i].y = y[i];
          }
     }
     else if (radioRectangular.Checked == true)
     {
          for(i = 0; i < POINT_COUNT; i++)
          {
               t = i*delta_t;
               f = a*t*t+b*t+c;
               y[i] = Math::Dsp::Rectangular(twopi*f*t, twopi);
               xyInput.Graphs[0][i].y = y[i];
          }
     }
     xyInput.RefreshAll();
     transform.Transform(y, customControlDisplay.output);
     customControlDisplay.UpdateImage();
}

void GaborView::radioSin_Click(Win::Event& e)
{
     Run();
}

void GaborView::radioTriangular_Click(Win::Event& e)
{
     Run();
}

void GaborView::radioRectangular_Click(Win::Event& e)
{
     Run();
}

GaborViewRun

© Copyright 2000-2019 Wintempla selo. All Rights Reserved. Sep 05 2019. Home