Websdr as a remote receiver with Max/MSP and Elecraft K4

Remote receiver project using Websdr as a remote alternative to a local receiver.

Demonstration of a Max/MSP program that connects an amateur radio transceiver to Websdr – transmitting locally from Maine (USA) while receiving remotely using a radio in the Netherlands. The Max program reads the frequency from a Elecraft K4 transceiver, to control the Websdr sites. It also loads the remote receivers, controls audio routing, mode, filter, and waterfall display settings. An iPad, running touchOSC, acts a a control panel. Up to 4 remote receivers operate at the same time. Websdr is a remarkable system, developed by PA3FWM at http://websdr.org/. It lets you control remote receivers worldwide, from your Web browser.


  • Max/MSP
  • Websdr
  • TouchOSC
  • Elecraft K4 transceiver with antenna system
  • Skookumlogger (logging software)

Max Patches:

websdrjweb7.maxpat : main control program. Contains [jweb] objects for launching websdr instances. Also code for injecting javascript to control parameters like frequency, filter, and volume. This patch acts as an intermediary between TouchOSC, WebSDR, and allows external MIDI control as well as getting frequency input from CAT controlled radios like the Elecraft K4.

websdrCATaudio.maxpat : handles serial port interaction for the K4. Also reads audio stream from either the K4 receiver (via USB) or the websdr receiver (via Blackhole.)  I created an aggregate audio device called K4sdr to allow Max to read both devices at the same time. Audio switching and levels are handled using a Korg nanoControl2. For example to switch between the audio streams or listen to both.

Optional: arduino-ptt-detect2.maxpat : reads serial data from an Arduino, connected to the amplifier keying line, to determine whether the radio is in transmit mode, so we can switch back to the local audio stream to eliminate latency of hearing your signal via websdr. See subsequent post about this setup…


websdrCW3.touchOSC : controls all 4 websdr channels, ie,., volume, mute, filter, CW offset, filtershift, – Also handles window management, loading js code, zoom in/out websdr and selecting channel waterfall views or Max code views.

CW Offset

websdr doesn’t have a control for CW pitch offset. To sync the frequency of the K4, the websdr is run in LSB mode with a frequency offset equal to the CW pitch setting in the transceiver. eg., 450 Hz. This works for most of the websdr sites, but unfortunately some of the sdr’s are off-frequency. You can usually compensate by adjusting the CWfreqOffset for that channel (in Max or TouchOSC).

Setting the offset also requires shifting the filter so it is centered over the actual signal.


This is currently a work in progress, not available on Github. Local files are in max teaching examples folder.

Amateur radio contesting time-lapse map

Mapping geocoded  contest log data using node.js and openlayers.

The goal was to make something that looks like the Reverse Beacon Network map, only for contest log files. I use RBN for testing antennas now. That map display gives you a pretty good idea of your actual antenna pattern.

Code is written in node.js (javascript) and html.

Part 1: Read a Cabrillo log file containing QSO: records. Look up each callsign, get latitude and longitude, and rewrite the file as json data, tagged with geo coordinates. I originally tried getting the data from hamQTH but it was not current, so ended up using the qrz.com xml callsign lookup. For callsigns “not found” I used the qrz.com dxcc prefix lookup to get general coordinates for the country. There are still a few bad/missing data issues to resolve. Like European stations with coordinates at the South Pole.

Part 2: Tried various mapping frameworks – like leaflet, arcgis, and openlayers. Wanted to use a great-circle projection (azimuthal equidistant) like the big ARRL world map. And may still figure this out. But working with map projections and coordinate transforms is way worse than doing a Smith Chart.  I ended up hacking a flight tracking example from openlayers.org and basically replacing airplanes with QSO’s. That is why the lines are animated from source to destination.  Also added a layer for day/night, and QSO/time status display.

It probably makes sense to get rid of the flight animation and just display the entire path in sync with the QSO data – with color code for each band (K1KP) – and speed control on the time lapse, etc., So you can get a better sense of rate and propagation.

It would be cool to have a website where you could upload a log file and generate maps.

Note: this project is not yet available


local files:

generating data:


put the cabrillo data in testdata.cbr (use QSO: records only for now) should be sorted chronologically.

run:  node index.js

the output file will be: geocab.json (which is used as input to the mapping program)



main.js = node source with ol mapping and data processing

index.html = web page for map

geocab.json = geocoded cabrillo json test data

to run, type: npm start

Then open: http://localhost:5173/ in a browser

Additional work / current issues

Some of the qrz.com callsign data has bad geo coordinates. In particular some of the records show a latitude of -89 and longitute -179 – need to check for these numbers and replace with dxcc coordinates.

There should be an argument on the node program to pass in the datafile. Also the program should clean up any non QSO: records, like the file header info and any X-QSO recs.

Also need to clean up the async/await stuff – currently there are several methods for handling state transitions.

mapping ideas:

As mentioned above, its probably a good idea to make a version of the code without the flight animation, and have various controls to stop/start the data playback to look at individual qso’s do speed control, etc.,

azimuthal equidistant projection: there are some links to examples in leaflet, and arcgis to handle complex projections. In documents, look at:  “map links for projection stuff.txt”

leaflet test version:

in the internetsensors/cabrillomap folder there’s a test file: cbworld1.html that works using websockets when you run the index.js file to generate test data. It uses a leaflet map, but the lines don’t adapt to great circle polar paths.


I believe the arcgis examples are in internetsensors/projected geometries

And: internetsensors/pe-gs-projection

The former is a a very nice world projection with some point markers. The latter is an example that shows how to switch out various projections in realtime.

11 Km feedback delay

Using 40 meter SSB, Max/MSP, and Websdr to build a feedback delay from Maine to the Netherlands.

A Max patch plays an audio file into the transmitter in Maine. Then, using a websdr receiver in the Netherlands, the received signal is amplified and mixed back into the audio to be retransmitted. Effectively creating a feedback delay line.

Here is an example of what it sounds like on 40 meters.


This is the Max patch

The audio from Websdr is routed to the input of Max by creating a multi-output device (In Audio Midi Setup) combining “Blackhole 2ch” and external headphone output (for monitoring). The audio output of Max is assigned to the transmitter SSB input.

Patch not yet available. Local version is in max teaching examples.

Max/MSP text to Morse code generator

work in progress. not published.

I needed a text to morse code generator in Max for the Twitter streaming map project. There was an ancient one that used [mxj] but its kind of a pain to use that object. I thought it can’t be that difficult to write one? I didn’t really have any idea where to start. Something about the blank Max patch causes brain activity? I went through about 5 different approaches. Eventually came up with this pattern thing, from thinking about the lighted buttons on the tr-808 drum machine.

For example, the letter A is . _ (dot dash)

morse code has rules for spacing:

dot = 1
space between tones = 1
dash = 3
space between letters = 3
space between words = 7

If you think of a drum machine pattern, the pattern for letter A would be: 1 0 1 1 1 0 0 0  (with the 3 trailing 0’s for letter spacing)

I made a [coll] with all the letter patterns indexed by ascii codes.

Then just concatenate letter patterns, for a given block of text,  together into one big list and run it through [zl.nth] clocked by a [metro] and [counter]. the ones and zero’s turn an oscillator on and off.

local file: tkzic/internetsensors/twitter-stream2/morse5.maxpat

Hamlib C programming example on Mac OS

Hamlib provides a standardized computer interface for amateur radios.

I was able to get it to run in a C program on Mac OS Ventura.


Install Hamlib using homebrew. ie,., brew install hamlib.

Download this sample C test code – testrig.c – from https://hamlib.sourceforge.net/manuals/1.2.15/_2tests_2testrig_8c-example.html or https://github.com/Hamlib/Hamlib/blob/master/tests/testrig.c

Change the SERIAL_PORT constant to the actual port name. Change the baud rate.

Lookup the hamlib code for your radio. https://github.com/Hamlib/Hamlib/wiki/Supported-Radios

For example, for the Elecraft K3, the code is 2043.

compile using this command:

gcc testrig.c -I /opt/homebrew/include -L /opt/homebrew/lib -l hamlib -o testrig

Type this to run the program:

./testrig 2043

Assuming 2043 is the rig number. If all is working, the program will set a bunch of stuff on your radio, changing frequency, mode, etc.,

Local files: tkzic/chatgpt/radio/ctest.c

notes: link to instructions for compiling hamlib manually: https://github.com/Hamlib/Hamlib/blob/master/README.osx

Using Web Serial API for Radio CAT interface

Work in progress…

An html example that sends and receives CAT commands with an Elecraft K4 connected to the serial port of the machine running the web browser.

The file was built using examples from this article by Francois Beaufort. https://developer.chrome.com/docs/capabilities/serial It’s an excellent resource for Web Serial.

The html file for this project is here: http://www.pr0jex.com/serial/index.html

But it won’t run from that server. It only runs locally.


Download the index.html file from the link above. start a local web server, eg., “npx http-server” and enter the server address into a Chrome browser.

When you press connect, the browser will prompt you to select a serial port for the radio.  On my computer the K4 serial ports appear something like: cu2.usbserial-21100. There are 2 ports. Select either one.

Then press read. If you spin the dial on the K4, and it is autoinfo mode, you should see CAT commands in the read window.

enter a CAT command in the write window and press the write button. It is preloaded with “fa7;” which sets frequency to 7 MHz.

Press the disconnect button to end your session.

local files tkzic/webusb/serial2/index.html


Building SoapySDR and CubicSDR with Linux

How to build SoapySDR and CubicSDR from source in Ubuntu 20.04

After unsuccessful attempts to compile SoapySDR and CubicSDR on Mac OS and Windows, I was able to get it running in Ubuntu 20.04.  The whole process took about 2 hours but could be done in less time if you know what you are doing.

It wasn’t really necessary to install CubicSDR to test SoapySDR. But CubicSDR is the only SoapySDR app that consistently works when it comes to devices, I/Q, files and TCP frequency control.

After completing this install I was also able to compile and run the SoapySDR example code here:  https://github.com/pothosware/SoapySDR/wiki/Cpp_API_Example


There are excellent instructions at the CubicSDR wiki on github: https://github.com/cjcliffe/CubicSDR/wiki/Build-Linux

I also added the rtlsdr driver and the airspyhf driver. Instructions for rtlsdr are on the wiki. Instructions for airspyhf are below. You can add these drivers/libraries at any time, after SoapySDR is installed.

There were several missing libraries – described here.


Hamlib is an option in CubicSDR. It was included because we’re using it to send frequency data to the devices via rigctld. You’ll need to install hamlib before you try to compile CubicSDR

Instructions and source code here: https://github.com/Hamlib/Hamlib

Instructions are somewhat vague. Here’s what I did.

Install libtool:

sudo apt install libtool

Clone the repository, build, and install:

git clone https://github.com/Hamlib/Hamlib.git
cd Hamlib
sudo make install


airspyhf library code from Airspy. Instructions here: https://github.com/airspy/airspyhf

This is what I did, if I can remember correctly. You may need to install libusb, but if you have done all the stuff above you probably already have it.

git clone https://github.com/airspy/airspyhf
cd airspyhf
mkdir build
cd build
sudo make install
sudo ldconfig


Now you can add the Soapy AirspyHF drivers. Instructions here:  https://github.com/pothosware/SoapyAirspyHF/wiki

git clone https://github.com/pothosware/SoapyAirspyHF.git
cd SoapyAirspyHF
mkdir build
cd build
cmake ..
sudo make install


Max8radio CubicSDR I/Q prototype

Another working prototype with Max and CubicSDR

Now working some better… The Max SDR patch is receiving an IQ audio stream at 96 KHz from CubicSDR and sending frequency data to rigctld daemon via a python script that recodes OSC to tcp data.

repository: https://github.com/tkzic/max8radio



py3rigctl2.py (python script)


Basically the same as instructions in the previous prototype here: https://reactivemusic.net/?p=19995

make sure to start the rigctl daemon before CubicSDR

 rigctld -m 1 4532 & 

And make sure there is some audio gain on CubicSDR

But… There is only one Max patch now and – after you start the rigcltd daemon, you need to run the python script in the max8radio folder like this:

python3 py3rigctl2.py

The most important thing is to start CubicSDR first before you run the Max patch. Make sure to get everything working correctly. Then start the Max SDR.

In CubicSDR make sure you only have one “modem” running – otherwise the IQ data stream will be a complete mess. Als0 make sure that the audio sample rate in CubicSDR is set to 96 KHz. It will revert to 48 KHz. everytime you load the program. You can use the ‘bookmarks’ from a previous CubicSDR session (lower left part of the screen) to load a previous session with the same parameters.

These are the necessary settings:

  • I/Q modem
  • Audio out: Existential Audio Inc. Blackhole 2 ch.
  • Audio sample rate: 96 KHz.
  • Rig Control Menu: enable rig and follow rig should be ‘checked’
  • Frequency should equal Center frequency and the V delta lock toggle should be on
  • Demodulator Gain level should be very low to prevent excess AGC (upper right corner)

Actually if you have loaded everything ok in a previous session, try this:

  • get the rigctld daemon running from the command
  • load CubicSDR
  • First thing: click ‘enable rig’ under rig control (this will probably load some crazy frequency like 145 Mhz
  • Then in the bookmarks (lower left) double click on your previous session, under ‘recents’ for example: 7007MHzI/Q – this should restore almost all the settings.
  • Then change the audio sample rate to 96 KHz if needed.
  • If the input to Max seems wrong, try clicking the S  (over near the top right)  to solo the modems. There may be more than one going.

Max settings

  • Set audio input to Blackhole 2ch @ 96 KHz. (to match output from CubicSDR
  • Click the ‘flip IQ’ toggle – for some reason CubicSDR sends out the I/Q signal flipped
  • The arrow key tuning and all other tuning methods should work now


One of the problems with CubicSDR is sometimes you’ll accidentally change something and all the settings go crazy.

note: I tried a new version of CubicSDR (2.6) from the sdrplay website. It would not detect any connected devices or audio drivers.

Once you get it working, the audio quality inside Max is excellent – using the Airspy HF+


Igloos on the Air

Operating the 2016 ARRL DX CW contest from inside an igloo at the summit of Witt Hill in Norway, Maine.

You can see the dipole feedline entering the air hole near the top right of the igloo – in the above photo.

Inside the igloo, the rig is balanced on a board, supported by plastic storage containers. I ran full power, 100 watts, from an IC-7300, using Bioenno LiFePo batteries. The temperature inside stays a few degrees above zero Celsius. I thought the rig would help heat up the igloo, but instead had to rely on wool blankets.

All of the gear was pulled up on sleds using human power.

About 3 feet of snow fell in the week before the contest.

Results: KA1IS – 40 meters SOSB LP claimed score: 86,286 (394 QSO’s + 73 countries)