Random Quote Board

Understanding SDR++ Spectral Displays

Gary Schafer, 1 October 2026

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++.

Screenshot of program for controlling a software defined radio. A set of controls is in a column on the left. A relatively short graph takes up the upper, righthand portion of the screen. It shows a series of signals in a spectral trace. The majority of the screen is the below this trace. This shows a spectrogram, consisting of columns of varying colors below each signal. There's a yellow box around one section of the controls on the left.
Screenshot of SDR++, with a yellow box drawn around the "Display" controls.

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.

Screenshot showing a relatively narrow column of controls along the left side, and a spectral trace taking up the rest of the screen.
SDR++ with the "Show Waterfall" setting unchecked. The screen only shows a 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.

Screenshot of SDR++. The left edge is a column of various controls. To the right of these are two displays, one a smaller horizontal display along the top. This is the spectral trace. Below this is a larger graph consisting of a spectrogram. The spectrogram shows a series of thinner colored columns along the bottom, with the center columns expanding to wider columns along the top.
When the "Full Waterfall Update" is unchecked, then if the slider controls along the right side are changed, it will only affect current and future displays of the waterfall. This shows how increasing the zoom (aka "zooming in") shows on the waterfall with this option unchecked.

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:

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:

Block diagram of starting on the left, with the caption input, with a line going into a circle. This line has an arrow over it near its center. The arrow points towards the right, and above it is a Greek letter alpha. The circle that the line is going into has a plus sign at its center. Another line connects from the edge of the circle on the right, and this line extends to the right. It ends at another caption, with the word output. Along this line is another arrow over it. Above this arrow is the number 1. Slightly further along the line is a dot. Yet another line extends down from this dot to a box. The box contains the letter Z with an exponent of -1. A line extends down from this box, then turns left, extends to a point just directly below the circle, turns right again, then extends up to the circle. On the lowest extension of this line is an arrow. Above this arrow is the caption 1 minus the Greek letter alpha.
Block diagram of a single pole IIR filter. The value of α must be between 0 - 1. A value of 1 means no filtering; a value of 0 means no updates to the output.

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:

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.

Screenshot showing a set of controls in a column on the left, a spectral trace along the top right, and a spectrogram taking up the remainder of the window. The fonts, lines and boxes making up the screen are all quite large, and the screen looks somewhat jumbled.
Screenshot of SDR++ with the "High-DPI Scaling" set to 200%. Note how the screen looks somewhat jumbled. Again, this setting should only be changed if there's an issue with the current font size or being able to reach some of the settings.

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.

A set of small blocks extends horizontally. Most of the blocks are a light blue, but periodically a set of four blocks are colored yellow. A line across the top extends from the beginning of one set of yellow blocks to another set. It has a caption of sample rate / framerate. Another line below the row of blocks extends across one set of yellow blocks. It has the caption of FFT size.
The number of samples used in each FFT calculation (yellow blocks) is determined by the setting "FFT Size". The number of samples between the beginning of each FFT calculation is calculated as the sample rate divided by the framerate.

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.

Two rows of small, colored blocks extend horizontally across the image. On each row, most of the blocks are a light blue, but periodically a set of four blocks are colored yellow. A line across the top extends from the beginning of one set of yellow blocks to another set. It has a caption of sample rate / framerate. Another line below the row of blocks extends across one set of yellow blocks. It has the caption of FFT size.
If either the framerate or the FFT size are increased, the number of unprocessed samples used for spectral data will be lower.

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 dis­placements at instants equally spaced over a 13.5 hour period.
To solve the problem, the conven­tional 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.

Four rows of small blocks extend from upper left to lower right. Each row overlaps the row below it. The first blocks of each row are green; the remaining blocks are yellow. A legend in the lower, left hand corner shows a green square with the caption of reused sample. A yellow square has the caption says new sample.
If the framerate multiplied by the FFT size is greater than the sample rate, then the FFT blocks can overlap. The samples required beyond the sample rate can come from reused samples, as shown here. This means that some samples will be used in more than one FFT block.
Four rows of small blocks extend from upper left to lower right. Each row overlaps the row below it. The first blocks of each row are white; the remaining blocks are yellow. A legend in the lower, left hand corner shows a clear square with the caption of zero. A yellow square has the caption says new sample.
This shows how any samples required beyond the sample rate can be added as zeros (zeropadding).

There are advantages and disadvantages to each method.

MethodAdvantagesDisadvantages
Reuse SamplesThe spectral trace will have a lower resolution bandwidth.The spectrogram will be smeared in time.
ZeropaddingThe 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.

Screenshot showing two spectral displays. The display on the top left is a spectral trace showing both rounded analog FM stations (roughly triangular shaped) and square-shaped digital HD stations. The majority of the screen is the spectrogram on the bottom, showing several vertical stripes of roughly smeared out colors. A set of controls are in a vertical column on the righthand side.
GQRX screenshot. The FFT size is 131072 and the framerate is 250 frames/second. This requires a total of 32.768 million samples per second. But we're only taking in 2.4 million, or 7.3% of the needed samples. So GQRX reuses most of the samples from the previous block (roughly 93%, as stated in the "Overlap" value in the upper, righthand corner). This leads to a very low resolution bandwidth. Note that the listed "RBW" in the upper, righthand corner of 18.3 is actually the binwidth. The actual RBW is roughly 31.6 Hz, as determined by the binwidth of 18.3 Hz multiplied by the NENBW of the Blackman window, which is 1.727. The spectrogram, however, is smeared due to the overlapping FFTs.

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.

Screenshot of SDR program. A set of controls is in a column on the left edge. This column runs the entire height of the window. To the right of this column are two displays, one on top of the other. The one on top is relatively short and shows a spectral trace. The trace consists of several signals, some of which are rounded and the rest are square-shaped. The display on the bottom takes up most of the screen, and consists of columns of varying colors below each of the signals displayed on the top graph.
SDR++ with a sample rate of 2.4 MHz, a relatively low framerate (20 frames/sec) and a FFT size of 65536 (216). With this sample rate and framerate, there are 120,000 samples between FFTs, but only roughly half are being processed with the FFT. However, every sample in the FFT is actual data, so the resolution bandwidthis still low. The spectrogram time resolution is poor due to the low framerate.

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.

Screenshot of SDR program. A set of controls is in a column on the left edge. This column runs the entire height of the window. To the right of this column are two displays, one on top of the other. The one on top is relatively short and shows a spectral trace. The trace consists of several signals, some of which are rounded and the rest are square-shaped. The display on the bottom takes up most of the screen, and consists of columns of varying colors below each of the signals displayed on the top graph.
SDR++ with a sample rate of 2.4 MHz, a higher framerate (200 frames/sec) and a FFT size of 65536 (216). With this sample rate and framerate, there are 12,000 samples between FFTs. With the FFT size of 65536, this means that most of the samples fed into the FFT are zeros (65536 - 12000 = 53536 zeros total). The frequency resolution remains the same (meaning the ability to measure the frequency at any point in the spectrum), but the resolution bandwidth will be more coarse since zeros do not improve the RBW.

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++.

WindowNENBW
Rectangular1.0000
Blackman (Approx)1.7268
Nuttall2.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.

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
WindowScalloping LossCoherent Processing GainIncoherent Processing Gain
Rectangular3.9200
Blackman (Approx)1.10-7.54-5.16
Nuttall0.81-8.98-5.92
Screenshot of SDR program. A set of controls is in a column on the left edge. This column runs the entire height of the window. To the right of this column are two displays, one on top of the other. The one on top is relatively short and shows a spectral trace. The trace consists of several signals, some of which are rounded and the rest are square-shaped. The display on the bottom takes up most of the screen, and consists of columns of varying colors below each of the signals displayed on the top graph.
SDR++ using a rectangular window. Note the level of the signals and the noise.
Screenshot of SDR program. A set of controls is in a column on the left edge. This column runs the entire height of the window. To the right of this column are two displays, one on top of the other. The one on top is relatively short and shows a spectral trace. The trace consists of several signals, some of which are rounded and the rest are square-shaped. The display on the bottom takes up most of the screen, and consists of columns of varying colors below each of the signals displayed on the top graph.
SDR++ using a Nuttall window. Note how the signal and noise levels are significantly less than with the rectangular window. This is due to the "processing gain" of the Nuttall window.

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.

Here's a Random Fact...