DIY window functions

In digital signal processing, window functions are widely used to limit the signal in time, and their names are well known to everyone who has come across the discrete Fourier transform in one way or another: Hann, Hamming, Blackman, Harris, and others. But are they sufficient, is it possible to come up with something new and is there any sense in it?



In this article, we will look at the output of a window function with new properties using Wolfram Mathematica. It is also assumed that the reader has a general understanding of digital signal processing in the context of the issue under discussion and is at least familiar with the article from Wikipedia .







Introduction



Historically, the first window functions appeared in the process of attempts to improve their spectral properties by reducing the amplitude of the side lobes - since when the signal is multiplied by the window function, their spectra are convoluted, which introduces some limitations for analysis. At that time, the 50s of the 20th century, the computing power did not allow to easily and naturally manipulate symbolic computations, and the selection of the optimal parameters was quite difficult. This is one of the reasons why the functions are called by different names - they, or rather, their spectral properties, have been studied by different researchers for quite a long time. A side effect of this is that the existing set of named window functions is perceived as a kind of "standard set" outside of which nothing can be found;at the same time, the names of these windows do not carry any information about the properties and suggest their separate study and memorization.



Classification



All window functions are obtained by multiplying some function by a rectangular one, which is guaranteed to ensure its time constraint. Therefore, first of all, they can be classified according to the functions that they use:



  1. the sum of the cosines. The most extensive class due to the fact that their spectrum is easily computable and is the sum of weighted sinc functions. This includes Hann, Hamming, Blackman, Harris ...
  2. piecewise continuous polynomial. They are obtained as a result of convolution of simple functions - for example, rectangular and triangular. At the same time, their spectra are multiplied and their finding is also not particularly difficult. These include Dirichlet, Triangular, Parzen, Welch ...
  3. all others - using exponentials, Gaussians, sinc and others, the choice of specific functions in which is more ideological in nature than specifically spectral properties.


They can also be divided by properties at the edges:



  1. no gaps at the edges - values ​​equal to zero. Breaks are of Dirichlet, Hamming, Blackman-Harris. Don't have - Triangular, Hann, Nuttal
  2. absence of discontinuities of the 1st and other derivatives


Two more window functions stand out separately:



  1. Kaiser, which allows you to set the width of the main petal;
  2. Dolph-Chebyshev, all side lobes of which are equal to a given amplitude.


Both of them have discontinuities at the edges of both values ​​and derivatives, and their calculations involve some difficulties - the Kaiser function is calculated through a special function (Bessel), and the Dolph-Chebyshev function is determined in the frequency domain and is calculated through the inverse discrete Fourier transform. Finding their analytical antiderivatives is also particularly difficult.



You can examine window functions for breaks with the following simple code:
wins = {DirichletWindow, HammingWindow, BlackmanWindow, 
   BartlettHannWindow, BartlettWindow, BlackmanHarrisWindow, 
   BlackmanNuttallWindow, BohmanWindow, ExactBlackmanWindow, 
   FlatTopWindow, KaiserBesselWindow, LanczosWindow, NuttallWindow};
Table[{f, 
  Table[Limit[D[f[x], {x, k}], x -> 1/2, 
    Direction -> "FromBelow"], {k, 0, 4}]}, {f, wins}]</code>
↓
<img src="https://habrastorage.org/webt/rm/wq/8f/rmwq8fjkjxjcox-czh8zevkbd0m.png" />

<code>Plot[Evaluate[Through[wins[x]]], {x, -1, 1}, PlotRange -> {-0.25, 1}, 
 GridLines -> {{-0.5, -0.25, 0.25, 0.5, 1}}, AspectRatio -> 5/8, 
 PlotLegends -> "Expressions"]


↓





Reverse engineering



Let's look at the definitions of the Blackman and Nuttel functions:



BlackmanWindow[x] // FunctionExpand


↓

{150(25cos⁑(2Ο€x)+4cos⁑(4Ο€x)+21)βˆ’12≀x≀120|x|>12



NuttallWindow[x] // FunctionExpand


↓

{121849cos⁑(2Ο€x)+36058cos⁑(4Ο€x)+3151cos⁑(6Ο€x)+88942250000βˆ’12≀x≀120|x|>12



They are sums of 3 and 4 cosine waves with even frequencies (starting from zero) and some coefficients. Where did these coefficients come from? It is unlikely that they were selected by hand, at least each separately - after all, there are boundary conditions that the windows must comply with regardless of their spectral properties - at least equal to unity in the center.



Let's try to "invent" the Blackman function on our own. To do this, we define a function with as yet unknown coefficients



f = Function[x, a + b Cos[2 Pi x] + c Cos[4 Pi x]];


and compose a system of equations defining the boundary conditions - equality to unity at the center, zero at the edges, and equality to zero of derivatives at the edges:



ss = Solve[{
   f[0] == 1,
   f[1/2] == 0,
   f'[1/2] == 0
   }, {a, b, c}]


↓





{{bβ†’12,cβ†’12βˆ’a}}



The solution was found, but not one, but many - depending on the coefficient a , which Wolfram politely warned us about. Now, from the found solution, we define a new function depending on the unknown coefficient: ↓ It is easy to see that for a = 0.42 we obtain the Blackman function. But why exactly 0.42? To answer this question, we need to plot its spectrum. Analytical Fourier Transform is not an easy task, but Wolfram can handle it too, saving us from the chore.



hx = Function[{x, a},

Piecewise[{{f[x] /. ss[[1]], -1/2 < x < 1/2}}, 0] // Evaluate]
















hw = Function[{w, a}, 
  FourierTransform[hx[x, a], x, w] // #/Limit[#, w -> 0] & // 
    Simplify // Evaluate]


↓



note
, FourierTransform , β€” , , β€” . w. β€” w, , .







In this form, the graph of the function is still not very informative, since it is more convenient to analyze the spectrum on a logarithmic scale. By adding the appropriate conversion to decibels20log10⁑(|x|), we get the same usual graph of the spectrum of window functions:



the code
Manipulate[
 Plot[{
      hw[w, a],
      hw[w, 0.42]
      } // Abs // 20 Log[10, #] & // Evaluate
  , {w, -111, 111}, PlotRange -> {-120, 0}, GridLines -> Automatic, 
  PlotStyle -> {Default, Thin}, PlotPoints -> 50]
 , {{a, 0.409}, 0.3, 0.5}]
↓








In the process of changing the parameter, we will observe something similar:







Depending on the parameter a, the level of the side lobes changes, and at a = 0.42 it is more or less minimal and uniform. With a = 0.409, we can get a slightly better result if by β€œbetter” we mean the minimum possible level of side lobes.



note
, , .



Development



Obviously, such micro-optimization was not at all worth the effort. Is it possible to qualitatively improve the properties of this window without changing the initial data?



Earlier we looked at the output of window functions that sum to one, which allows you to restore the original signal. Let's try this technique for our window and see how it affects the spectrum.



Let's define an auxiliary function for constructing overlapping windows, taking into account the boundaries of the argument change in the range from -1/2 to 1/2:







Find the antiderivative, shift it to the center of coordinates and scale it to one:





↓





display it on the graph:







As you can see, Wolfram did it on its own here too, and we didn't have to manually set a piecewise continuous definition of an antiderivative. Now the view of our window depends not only on the variable a , but on the degree of overlap - and as it increases, it will tend to the form of the derivative:



the code
Manipulate[
 Plot[
  OverlapShape[hsx, x, a, t]
  , {x, -1, 1}, PlotRange -> All, GridLines -> Automatic]
 , {{a, 0.404}, 0.4, 0.5}, {{t, 4}, 3, 11}]


↓




And the final touch is to find an analytical function for the spectrum in order to determine the optimal value of the parameter a .



Here, if we try to compute the transformation directly, like last time, we will drive Wolfram into deep thought. There are several ways to speed up this process:



- use a discrete transformation instead of an analytical one - the simplest one. But a real mathematician will not apply numerical methods where a purely analytical solution can be found - and we will not (yet); in addition, it has obvious limitations (in terms of resolution and spectrum limitation);



- pass specific values ​​as an overlap parameter and, looking at the results, try to see the pattern and generalize them. Quite a working option, but requiring additional mental and creative efforts. Let's leave it as a last resort.



- make calculations directly in the frequency domain. This is possible due to the following properties of the Fourier transform:



1) it is linear, i.e. the sum of images is equal to the image of the sum;

2) integration in the time domain is equivalent to dividing by iw in the frequency domain.

3) time offset is equivalent to modulation with a complex sinusoid.

(more on wikipedia or here ).



Thus, having the spectrum hw of an arbitrary window function, we can obtain from it the spectrum hwo of a function summed to one with overlapping t :





↓





Now we can see how the spectrum changes in dynamics:







Here, changing the overlap parameter will already affect the window spectrum in a slightly different way :







Having played a little with the parameters, it is easy to notice that "more is not better", and the optimal degree of overlap for that function is in the region of four. Specifically, for t = 4 and a= 0.404 we get the side lobe level not exceeding -80 dB. This is a very good result - especially considering that the raised cosine function, traditionally used for summed windows, gives a lobe level of about -30 dB. Well, when written out explicitly, our new window function will look like this:



the code
OverlapShape[hsx, x, 404/1000, 4] // PiecewiseExpand // FullSimplify


↓


{404Ο€(1βˆ’2x)+36sin⁑(13(16Ο€x+Ο€))+375sin⁑(13(Ο€βˆ’8Ο€x))606Ο€14<x≀12404(2Ο€x+Ο€)+36sin⁑(23Ο€(8x+1))+375sin⁑(13(8Ο€x+Ο€))606Ο€βˆ’12<xβ‰€βˆ’143(125cos⁑(8Ο€x3)+12cos⁑(16Ο€x3))202Ο€+13βˆ’14<x≀140|x|>12



Further development



What else can you do to further reduce the sidelobe level? You can take cosines not with even, but with odd frequencies (here, for compactness, the solution of the system of equations is embedded directly into the definition of the function):



the code
hx1 = Function[{x, a},
  a Cos[1 Pi #] + b Cos[3 Pi #] + c Cos[5 Pi #] & // #[x] /. Solve[{
          #[0] == 1,
          #[1/2] == 0,
          #'[1/2] == 0,
          #''[1/2] == 0
          }, {a, b, c}][[1]] & // # UnitBox[x] & // Evaluate]


↓


(acos⁑(Ο€x)+(58βˆ’a2)cos⁑(3Ο€x)+(38βˆ’a2)cos⁑(5Ο€x))Ξ (x)



and after integration with the parameters a = 0.6628 and an overlap level of 4.5, obtain an attenuation of -90 dB:







Explicitly:

{327sin⁑(17Ο€(45x+2))+24855sin⁑(17Ο€(1βˆ’9x))+3670cos⁑(114Ο€(54x+1))+2151243024518<x≀1224855sin⁑(17Ο€(9x+1))+3670sin⁑(37Ο€(9x+1))+327sin⁑(57Ο€(9x+1))+2151243024βˆ’12<xβ‰€βˆ’518βˆ’3670sin⁑(37Ο€(9xβˆ’1))+24855sin⁑(17Ο€(9x+1))+327sin⁑(57Ο€(9x+1))43024++3670sin⁑(37Ο€(9x+1))+327sin⁑(17Ο€(45x+2))+24855sin⁑(17Ο€(1βˆ’9x))43024βˆ’518<x≀5180|x|>12



You can add one more cosine and increase the number of zero derivatives:



the code
hx2 = Function[{x, a},
  a Cos[1 Pi #] + b Cos[3 Pi #] + c Cos[5 Pi #] + 
       d Cos[7 Pi #] & // #[x] /. Solve[{
          #[0] == 1,
          #[1/2] == 0,
          #'[1/2] == 0,
          #''[1/2] == 0,
          #'''[1/2] == 0,
          #''''[1/2] == 0
          }, {b, c, d}][[1]] & // # UnitBox[x] & // Evaluate]


↓


(acos⁑(Ο€x)+180(35βˆ’16a)cos⁑(3Ο€x)+180(35βˆ’48a)cos⁑(5Ο€x)+140(5βˆ’8a)cos⁑(7Ο€x))Ξ (x)



and after integration with the parameters a = 0.5862 and the overlap level of 6.4, obtain a suppression of -110 dB:







Explicitly:

{βˆ’3077550sin⁑(154Ο€(64xβˆ’5))βˆ’90069sin⁑(554Ο€(64xβˆ’5))βˆ’5820sin⁑(754Ο€(64xβˆ’5))βˆ’560455cos⁑(19Ο€(7βˆ’32x))+260134452026881132<x≀12560455sin⁑(118Ο€(64x+5))+3077550sin⁑(154Ο€(64x+5))+90069sin⁑(554Ο€(64x+5))+5820sin⁑(754Ο€(64x+5))+26013445202688βˆ’12<xβ‰€βˆ’1132βˆ’3077550sin⁑(154Ο€(64xβˆ’5))βˆ’90069sin⁑(554Ο€(64xβˆ’5))βˆ’5820sin⁑(754Ο€(64xβˆ’5))βˆ’560455cos⁑(19Ο€(7βˆ’32x))5202688++560455sin⁑(118Ο€(64x+5))+3077550sin⁑(154Ο€(64x+5))+90069sin⁑(554Ο€(64x+5))+5820sin⁑(754Ο€(64x+5))5202688βˆ’1132<x≀11320|x|>12



An even more significant reduction in the level of side lobes can be achieved by squaring the Fourier transform and thereby reducing the level of side lobes by a factor of 2 at once. This allows you to get rid of the increase in the number of parameters for their manual selection, but adds complexity in calculating the convolution in the time domain.



the code
hx3 = Function[{x, a}, 
  Convolve[hx1[x, a], hx1[x, a], x, y] /. y -> 2 x
 // FullSimplify // Evaluate]


↓






and here it is already possible to achieve suppression over 160 dB:







We will not give the formula explicitly because of its impressive size.



Hypergeometric window functions



To ensure the required number of zeros at the boundaries of our function, we used a search through the solution of a system of equations with subsequent integration. This is not very convenient - you have to change the number of equations every time. Maybe there is a simpler and more beautiful solution? There is! And the hypergeometric function will help us with this.



briefly about hypergeometric functions
, . β€” , . β€” .



Our solution will look like this:

2zΞ“(n+32)2F1(12,βˆ’n2;32;z2)πΓ(n2+1)+a(z+bz3)(1βˆ’z2)n/2

Where z=sin⁑(Ο€x2)



It consists of the sum of two parts: the antiderivative of the function (1βˆ’z2)n2normalized in amplitude to the point (1,1) and a function with free parameters, multiplied by the same (1βˆ’z2)n2in order to preserve the required number of zero derivatives: The







accuracy of the adjustment and their influence on the distribution of side lobes will depend on which function we choose to correct the shape - there are possible options that require separate studies. In this case, the functiona(z+bz3)gives a completely acceptable option - and due to two parameters allows you to make more precise settings.



The formula itself is interesting (and convenient) in that for even n it is simplified to a polynomial, and for odd n it is simplified to the sum of a polynomial with arcsine and square root - which makes the final formula more compact and easier to calculate.



It is already easier (and faster) to select the parameters here through the discrete Fourier transform. We also need an additional definition of the overlap function to work with three parameters:



















As an example, after substituting the parameters from the picture, our function will be simplified to



the code


↓

βˆ’288z11+2315z9βˆ’7380z7+12330z5βˆ’11940z3+8163z3200





Inverse Kaiser Window



If the task of summing the windows into one is not worth it, then the most optimal window is the Kaiser window, which allows you to smoothly adjust the width of the main lobe. However, it has a drawback - since it is expressed in terms of the Bessel function , it is somewhat difficult to read it outside the mathematical packages. You can, of course, separately implement the Bessel function - or you can look for an approximation through elementary functions. And suddenly it turned out that using the function of the spectrum of the Kaiser window (limited in time) for this, you can get even a little better result -



sinh⁑(k1βˆ’4x2)sinh⁑(k)1βˆ’4x2Ξ (x)

note
±12 , ksinh⁑(k).


The payment for such a cunning decision was the impossibility of expressing its spectrum purely analytically - but this is not so important, because in practice, in any case, we operate and analyze data in discrete form using the discrete Fourier transform. Below on the graph you can compare their spectra, where red is the original Kaiser window, green is its approximation:



the code


↓






With the same width of the main petal, the level of the side lobes is slightly lower. And for ease of use, you can draw up a table with approximate values ​​of the parameter k to obtain the required level of sidelobe suppression:

-60 k=8.8-90 k=11.36-120 k=15.18-150 k=18.88



Finding an approximation of the antiderivative of the inverse Kaiser window remains a separate interesting problem. So far, it has only been possible to express it through a power series, which converges rather slowly:



∫sinh⁑(k1βˆ’4x2)sinh⁑(k)1βˆ’4x2dx=βˆ‘n=1βˆžβˆ’212βˆ’nknβˆ’1x2nβˆ’1(βˆ’k(βˆ’1)nKnβˆ’12(βˆ’k)+kKnβˆ’12(k))Ο€(2nβˆ’1)sinh⁑(k)Ξ“(n)



Perhaps experts in mathematics can suggest a better solution in the comments.



Conclusion



Despite the fact that digital signal processing is a completely formed discipline, there is still room for development and finding something new in it - at least in the existing special and scientific literature, the issues of constructing window functions summed up into a unit are not considered; and the capabilities of modern computer algebra systems allow rethinking the accumulated knowledge at a qualitatively new level.



The original Wolfram Mathematica document with all the calculations is available on GitHub .



All Articles