Random Quote Board
Understanding SDR++ Spectral Displays
I've discussed spectral displays in more than one post. Including this one. Oh, yeah, and this one. Those posts cover the basics. For this post, we're going to take a slightly deeper dive.
Specifically, I want to look at how SDR++ creates spectral displays. The reason is that there is some confusion as to some of the "Display" controls in SDR++.
Let's go through them one at a time.
Show Waterfall
This is straightforward. The "waterfall" refers to the spectrogram. Such displays are also called "falling rasters" and "rising rasters" (when the display scrolls from bottom to top). Regardless, they show the spectrum over time. If you check this box, you'll see the waterfall. If it is not checked, then you'll only see the spectral trace.
Full Waterfall Update
The next option is "Full Waterfall Update". As stated in the "SDR++ User Guide"[1]:
when enabled, the history of the waterfall is updated when viewing parameters like zoom and min/max are changed.
With this option checked, any time you change either the "Zoom", "Max", or "Min" sliders (along the right edge of the window), the entire spectrogram from top to bottom will be updated. If it is not checked, then any changes to these sliders will only affect the current spectral trace and any future ones.
Lock Menu Order
With this option unchecked, the order of the different "plugins" of SDR++, such as "Display", "Radio", and "Recorder", can be changed. For example, I normally have the top to bottom order of the first three plugins as "Source", "Display" and "Radio". I can change that by simply clicking and dragging one of those blocks above or below one of the others. By checking this option, the blocks are fixed in their current positions.
FFT Hold
This is a variation of the "MAX HOLD" to which I'm used to with older spectrum analyzers. The difference is that it has a value. In my day (said in an old man's voice), when you set "MAX HOLD", the spectral trace would be fixed. The only changes would be if the new trace was higher than the old one. The new trace would be modified to the higher levels at those points where it was higher.
In SDR++, if you want this scheme (effectively "MAX HOLD" forever), then set this value to 0. That is the equivalent of "MAX HOLD forever". But what if you want it to decay away? Then you can enter a subtraction value that is (FFT Hold value)/10 dB per second. For example, if you enter a value of 10, then it will subtract 1 dB per second. During every update, the routine will subtract this amount from the previous "max hold" value. Once the value reaches the current spectral value, it uses either the current "max hold" value, or the current spectum value, whichever is greater.
If you're curious how it does the actual calculation, in the code, it calculates:
$$ \Large HoldSubtraction = {{HoldValue} \over {{Framerate} \times 10}} $$
where:
- HoldSubtraction = the amount that is subtracted from the "max hold" spectrum every new FFT.
- HoldValue = the "FFT Hold" value.
- Framerate = the "FFT Framerate" value.
The framerate value does not enter into the overall subtraction on a per time basis. This means it doesn't matter if you set the FFT Framerate to 10 or 100; the drop in the "max hold" on a per second basis will be the same. Why? Because if you use a lower framerate, then the amount subtracted may be larger, but it will be subtracted fewer times per second. On the other hand, if I use a higher framerate, the amount subtracted will be smaller, but it will be subtracted more often. So, again, it doesn't matter.
FFT Smoothing
This is an exponential averaging. It effectively applies a single pole IIR filter with the following form to each point of the FFT:
With this filter, the effective parameter is α. With an α = 1, there is no filtering. A α = 0 means that the output is effectively frozen.
SDR++ calculates this alpha value as follows:
$$ \LARGE \alpha = {{FFTSmoothingValue} \over {{Framerate} \times 10}} $$
where:
- α = the amount of filtering, 0 - 1. 0 = no updates. 1 = no filtering. In general, the closer to 0, the smoother the spectrum will be.
- FFTSmoothingValue = the "FFT Smoothing" value.
- Framerate = the "FFT Framerate" value.
Note that, if the calculated value of α is greater than 1, it will be set to 1.
SNR Smoothing
In the upper, righthand corner of SDR++ is a numbered line from 0 - 90. This is a "signal-to-noise ratio" measurement when the demodulator is active (aka the "Radio" block is checked).
Here's how I think that the SNR is calculated. I think it's just the average (mean) of the signal strength divided by the standard deviation. After that, if "SNR Smoothing" is applied, it's the same as with the FFT smoothing. This means that the SNR smoothing calculates an alpha (α) value similar to the FFT smoothing. If you want to know the specific α value, simply replace the "FFT Smoothing" value in the calculation above with the "SNR Smoothing" value. The rest of the calculation is the same.
High-DPI Scaling
NOTE: This setting should only be changed if there is an issue with the size of fonts or difficulty in accessing parts of the SDR++ console. Otherwise, leave it alone! You've been warned.
Think of this is a "zoom" value for all of the components of the SDR++ screen. With a "DPI Scaling" value of 200%, for example, every font, box, and line will be doubled.
FFT Framerate & FFT Size
I need to cover these two at the same time because they are so interrelated.
These two have probably caused the most confusion. First, "framerate" in this context means the rate at which each spectral trace is drawn. If the "FFT Framerate" is set to 20, for example, then SDR++ will create 20 spectral traces each second. "FFT size" refers to the number of samples fed into the FFT to create the spectral display. On SDR++, this is always a power-of-2.
Here's a diagram that tries to explain the timing created between the sample rate, framerate and FFT size.
If (sample rate)/(framerate) > FFT size, then there are some samples left over. For example, if the sample rate is 2.4 MHz and the framerate is 20, then there are (2.4e6)/(20) = 120000 samples collected between each FFT. If the FFT size is 8192, then this is the number used between each FFt. This, in turn, means that only 6.8% of the samples will be processed for the FFT. The rest? Dropped[2].
Another way to look at it is that, every second, SDR++ wants to process (framerate)x(FFT size) samples for spectral data. If this is less then the sample rate, then some samples will be dropped. Further, we can increase either the frame rate or the FFT size, and either will reduce the number of samples dropped.
When the FFT was first developed for practical purposes, the famous Tukey-Cooley paper from 1965, computers were extremely slow compared to today. For example, when IBM ran an article in Scientific American in 1967 extolling the virtues of the FFT, they stated:
For the test, he chose an earthquake that shook Rat Island, Alaska in 1965. Its seismograph record consisted of 2048 numbers representing longitudinal displacements at instants equally spaced over a 13.5 hour period.
To solve the problem, the conventional program took 1567.8 seconds. The new Fourier analysis algorithm took only 2.4 seconds.
(Scientific American, June, 1967)
In 1967, it took almost 2 1/2 seconds to calculate a 2048 point FFT. This meant that the time needed between FFT calculations was quite large compared to the number of samples being collected. The system processed samples and calculated FFTs as fast as it could, and that was as fast as you were going to get.
Fast forward to today. A similar sized FFT can operate in much less than a fraction of a second. Processing time is no longer the bottleneck. The amount of time needed to calculate the FFT is much less than the time needed to collect the FFT. This means that we can manually adjust the time between FFT calculations. We can either tell the system to slow down (if it finishes the FFT calculation for the current block before its time to collect a new block, just wait), or speed up (start collecting data while another part of the processor is actually calculating the FFT of another block).
What this latter concept means is that FFT blocks can overlap. This means that the (framerate)x(FFT size) is greater than the sample rate. Which leads to a problem. We need more samples than the system is inputting. Where do the extra samples come from?
Two options. We can either reuse samples from a previous FFT block, or we can just zeropad the FFT block.
There are advantages and disadvantages to each method.
| Method | Advantages | Disadvantages |
|---|---|---|
| Reuse Samples | The spectral trace will have a lower resolution bandwidth. | The spectrogram will be smeared in time. |
| Zeropadding | The spectrogram will have excellent time response. The frequency display will have excellent frequency resolution. | The spectral trace will have a poorer (higher) RBW, and the signal levels will be reduced. |
GQRX reuses samples if the sample rate cannot keep up with the desired framerate and FFT size. If this is based on a large FFT size, then this creates a fine RBW. But it also means that the spectrogram will be "smeared" due to the same samples being used in multiple FFT blocks.
SDR++ uses zeropadding if the framerate times the FFT size is greater than the sample rate.
SDR++, on the other hand, uses zeropadding. Zeropadding, as stated above, has the advantage that it allows for the timing of the spectrogram to remain "sharp" (not smeared out). It also allows you to keep the low frequency resolution due to the very low binwidth.
But it has a disadvantage. When calculating the RBW, only actual samples count. Any zeropadding does not count. Thus, as either the framerate and/or FFT size increase, and their product passes the sample rate, the calculated FFT will have a coarser RBW. It also will result in a drop in the signal and noise floor since the zeros also do not add to the power in the spectral display.
FFT Window
I covered windowing in a post several years ago. I look at windowing as affecting three primary aspects of spectral displays. These are the resolution bandwidth, the precision of the amplitude values, and the noise generated by spectral leakage. As of this writing, SDR++ provides for three windows. These are rectangular, Blackman (the approximate Blackman), and Nuttall[3].
Window Affect on RBW
In general, the RBW is a product of the binwidth and the windowing factor. This windowing factor, also called a "shape factor", is typically the normalized equivalent noise bandwidth (NENBW). The following are the NENBWs of the different windows used in SDR++.
| Window | NENBW |
|---|---|
| Rectangular | 1.0000 |
| Blackman (Approx) | 1.7268 |
| Nuttall | 2.0212 |
This RBW value only works for actual data samples; it does not include zeropadding. This means that, if the spectral displays are using overlapping FFT blocks, then not every sample will apply to the RBW. The only samples that apply are actual data samples; zeros will not apply.
To get an idea of the actual RBW, we need to calculate the number of data samples used. This can be calculated using the sample rate, framerate and FFT size.
- If (framerate)x(FFT size) <= sample rate: RBW = (sample rate)(NENBW)/(FFT size).
- If (framerate)x(FFT size) > sample rate: RBW = (sample rate)(NENBW)/(framerate).
Window Affect on Amplitude
There's also the issue of amplitude inaccuracy. This inaccuracy is the consequence of two things. These are spectral leakage and processing gain. Spectral leakage leads to scalloping loss (a reduction in signal amplitude due to its not being in the center of the frequency bin) and an increase in the noise level as the power reduced at the center frequency is spread over the spectrum.
Processing gain is actually a loss, and refers to the amount of power in the processed FFT block that is reduced due to the windowing. Such processing gain can be calculated either based on an incoherent power gain (noise) or a coherent power gain (actual signals). Calibrated systems must account for this loss. Commercial systems typically normalize based on the coherent power gain. SDR++ does not adjust the spectral amplitudes based on processing gain. As it isn't designed for calibrated systems, this does not make much difference.
| Spectral Amplitude Inaccuracies, in dB | |||
|---|---|---|---|
| Window | Scalloping Loss | Coherent Processing Gain | Incoherent Processing Gain |
| Rectangular | 3.92 | 0 | 0 |
| Blackman (Approx) | 1.10 | -7.54 | -5.16 |
| Nuttall | 0.81 | -8.98 | -5.92 |
The last thing I'll mention when discussing spectral amplitudes is the difference between the actual amplitude and the displayed amplitude. Note that the drop in the signal levels for the Nuttall window above. It is not as much as is shown in the table above. That's due to how the signal is processed for display, since it's a 8192 point display on a spectral display that is only 1475 pixels across. In other words, 8192 amplitude points are compressed to fit into 1475 point display. The compression value is roughly 5.6. My guess is that the compression uses the maximum amplitude of each 5 - 6 spectral points to create the display.
Color Map
This does not really have an effect on the display; its more a matter of personal taste. Choose the color map that you like.
Summary
That covers the details of the "Display" section of SDR++. I may cover other aspects of this program in the future, but I wanted to cover this to both (a) document the things I've learned about it and (b) let others benefit from what I've learned.
References
[1]: SDR++ User Guide, John Donkersley (G0OXO), December 2022
[2]: This only means that the samples are dropped from the spectral displays. If SDR++ is also demodulating signals, then the samples will be completely used there.
[3]: Nuttall is actually a family of windows. This particular window is the "Nuttall4b", as discussed in "Spectrum and spectral density estimation by the Discrete Fourier transform (DFT), including a comprehensive list of window functions and some new flat-top windows", G. Heinzel, et al, Max-Planck-Institut für Gravitationsphysik (Albert-Einstein-Institut), Teilinstitut Hannover, 15 February 2002.