Generating a pan adaptor and waterfall display
Using the SmartSDR API a client program can instruct the radio to generate a panafall display. Once created, the radio streams data to the client enabling a pan adaptor and waterfall to be displayed.
FlexRadio Systems provides a C# implementation of the SmartSDR API called FlexLib - this is available in source form and is the definitive description of using the raw API over Ethernet.
From time to time questions appear on the community about how to generate the pan adaptor and waterfall display. These have been answered (in part) by myself and others who have used FlexLib to build their own client or who have implemented interface libraries for other languages/operating systems.
Hopefully the following description of the data streams for both pan adaptor and waterfall will help others get up the learning curve faster.
If you find this description helpful, please "like" it so I get some feedback!
Thanks
Stu K6TU
Data Streams for Pan Adaptor & Waterfall
Creating a panafall for display requires use of the command API to instruct the radio. You can generate the API commands either by using FlexLib or by sending a command string to the radio over the TCP socket connected to port 4992 on the radio.
Data describing the panafall is streamed to the client via UDP on a port designated by the client and is wrapped in a VITA-49 protocol header. This stream is decoded directly by FlexLib or some of the other interface libraries available to authors of new clients.
Creating a panafall results in TWO data streams each with a unique stream id. The stream id for the pan adaptor and waterfall streams are sent to the client as status messages after the radio creates the panafall.
Here are descriptions of the data contained in each stream.
Pan adaptor
Lets say that you have a window in which you want to display a pan adaptor - and that its 1024 pixels wide by 700 deep. You create a panafall using the command:display panafall create x=1024 y=700 fps=24 center=14.100 bandwidth=0.2 min_dbm=-130 max_dbm=-50This tells the radio to create a panafall centered on 14.1 MHz, 0.2 MHz wide, that is updated 24 times a second and with a scale from -130 dBm at the bottom to -40 dBm at the top - and that you want to render this in a window 1024 pixels wide and 700 deep.
The radio will begin to send you updates to display from the origin of 0,0 (top left of the pan adaptor). The data is sent you to you as an array of 16 bit unsigned integers that has 1024 entries in the array - one for each X value in the display starting from 0.
Each unsigned 16 bit int is the Y offset in the display where the radio has already:So to draw the pan adaptor you must (here in pseudo code)...
- Run the FFT for the pan adaptor display
- Scaled the output so that a Y value of zero = -40 dBm and a Y value of 699 = -130 dBm
moveToPoint(0, panvalues[0]); for (i=1; i < 1024; i++) { drawLineToPoint(i, panValues[i]) }Its that straight forward. The radio sends you an array of points to draw - each entry in the array is the Y value (from 0 to 699) of the point to be drawn. The radio pre-scales the data so that all you have to do is draw the line on the screen every time your get an update from the radio.
Waterfall
This isn't so straight forward...
The radio send you the waterfall information one line at a time - think of the waterfall being a bitmap that is X pixels wide on the screen and occupies some number of lines (Y) depending on how long you want the waterfall to present data into the past. The waterfall bitmap is drawn so that the line at the top (NOW) is added and all the lines below (the PAST) are scrolled down.
FlexLib sends you this "line" of information as a WaterfallTile that is defined as an object in Util/WaterfallTile.cs with the following properties:VitaFrequency FirstPixelFreq; VitaFrequency BinBandwidth; uint LineDurationMs; ushort width; ushort height; uint Timecode; uint AutoBlackLevel; ushort[] Data;If you are processing the raw payload in the Waterfall VITA-49 frame, the items with the VitaFrequency type are sent as 64-bit integers in Big End format. To convert between the raw 64 bit int and a VitaFrequency (which is held as a 64 bit floating point number), you need to scale the integer by 1.048576E12.Float64 FirstPixelFreq = ConvertInt64BigToHost(value) / 1.048576E12;FirstPixelFreq is a misleading term - FirstBinFrequency would be a better description. Here's why...
The last item in this "tile" is an array of data values of length (width * height) laid out as rows of width "bins" end to end. The current SSDR implementation currently sends a "tile" that always has a height of ONE.
The width value will be GREATER than the width of you panafall. What the radio is doing is sending you data to enable a waterfall to be displayed for a RANGE of frequencies that is larger than that displayed in the pan adaptor and overlaps it at both ends.
In other words, the Waterfall "bins" start before the first X value of the pan adaptor display (in our case above the first frequency would be 14.000 (center - bandwidth/2)) and continue BEYOND the frequency of the right hand edge of the pan adaptor (14.200).
FirstPixelFrequency tells you the frequency corresponding to "bin" zero in the array of Data (Data[0]). BinBandwidth tells you the width of each "bin" in MegaHertz (it will always be a fraction).
So we have a series of "bins" set out as:Data[0] has frequency of FirstPixelFreq Data[1] has frequency of FirstPixelFreq + BinBandwidth Data[2] has frequency of FirstPixelFreq + (BinBandwidth * 2) Data[3] has frequency of FirstPixelFreq + (BinBandwidth * 3) ...Each "bin" has a unsigned 16 bit integer (range of 0 - 65535) which represents the magnitude of the signal power that the FFT found in each frequency bin.
LineDurationMs is what it sounds like - the duration of the line in milliseconds.
Timecode is a sequence number relating this "tile" to the last and is incremented by one for each "tile" sent from the radio. This allows late tiles that arrive out of order due to network issues to be dropped (in general, its too late to do anything with them).
AutoBlackLevel is essentially the noise threshold of the waterfall - its a value for the Data bin that can be considered as the background color of the waterfall - all values in the Data array that are this value or less can be rendered as the "black" color in the waterfall. "Black" because depending on the color gradient of the waterfall, its generally the first color in the gradient. This value is pre-calculated for you by the radio.
IMPORTANT NOTE: The "bins' in the waterfall "tile" DO NOT CORRESPOND to "pixel" frequencies in the pan adaptor and the bandwidth of a "bin" may be smaller or larger than the offset of adjacent pixels in the pan display.
So to render the waterfall, you need to:This needs to be done for each line in the bitmap which is rendered from the list of tiles received - with each update you draw the top line of the bitmap and scroll all the older lines down by one pixel vertically.
- Map the pixel offset in the display to a frequency using the pan adaptor settings as reference. In the above example, pixel zero in the pan adaptor is 14.000 and pixel one is 14.0001953125 etc.
- Find the nearest "bin" at or below this frequency, interpolate the "bin" values of adjacent bins to determine the appropriate magnitude for this pixel frequency.
- Map the 16 bit unsigned integer value into an appropriate color space.
- Set the corresponding X pixel in the bitmap to this color.
A note on color gradients
Color gradients are not complicated but it helps to have an example. The color gradient is a mapping algorithm that takes a value (in our case the uint16 of the "bin" value) and maps it into a color range represented by appropriate values of Red, Green and Blue.
This color is used to set the appropriate X pixel in each line of the waterfall bitmap.
This link:
http://www.andrewnoske.com/wiki/Code_-_heatmaps_and_color_gradients
has a good description of how color gradients work and also provides a C++ class implementation that can serve as a template for other languages.