RX Decoder optimizations for improved low SNR decodes#82
Conversation
|
@aknrdureegaesr @jsherer this actually looks good to me, at least the code looks sound. Requesting your review on it. I'm going to pull this one, build and test it on MacOS. @Joe-K0OG if you would be interested in testing this on linux as well, to see what you think. |
|
@punk-kaos would you like me to label this WIP and then you can notify when ready for testing? |
I THINK it should be ready for testing right now... I've been running it for 6-8 hours the last 4 days as I go about my work day just listening to HBs and QSOs and I've yet to see it do anything other than just work nicely. I'd love to get some more opinions from other people testing as I've tried it on 3 radios on my end but only on MacOS so far. Would love other people to confirm I didn't just break the heck out of working on other radios or OS's now :) Dunno why it shows that force push to my tree... That was me cleaning up a push to a wrong branch and is unrelated to this pull request.... Github is weird sometimes I guess. |
I tested this with my weak-signal testbed, which Allan and I used for testing the code when we were working on 2.3.0. I find no difference in performance between 2.5.0 (official) and the @punk-kaos source. Both detect the same weak signal down to -26dB reliably, with an occasional -27dB decode. Neither beats the other with weaker signals, and both appear to report the same dB measurement with very-weak signals. I did not do any testing with stronger signals (above around -25dB), but was testing for differences in detection threshold. My testing does not look for differences in performance when there are other signals present, but only when there is clean band noise. My assessment is that this code variant does not improve the weak-signal detection/decode threshold. 73, |
|
Thanks for the review, Joe. I'm building for MacOS with SDK 26 as we speak. I intend to test the difference with multiple signals where adjacent signals, even though valid, are passband "noise" for the decoder. Did you turn on the decode attempts display in the waterfall for your test? |
No, there was no reason to. There were no other signals. My test is between two radios, both on dummy loads, setting next to each other, all off-air, but with the receiver gain turned up so that the band noise with no signal present is at 30dB on the JS8Call scale, thus simulating on-air but "clean" noise-floor conditions. The transmit power is greatly reduced to milliwatt/microwatt levels to get down deep into the noise floor. 73, |
|
OK, I have this running. Testing with MacOS 26 Tahoe with my IC-7300. Being I'm testing on-air I'll probably want to run this for a couple days or so to evaluate the results. Being we're using a variant of Kalman filtering the measurements over time, including statistical noise, should produce estimates of extremely weak signals that tend to be more accurate. But it's going to take some operating hours to determine how much perceived difference there is. I like the basic design of the filter. |
|
I have this built and running. There's not an immediately noticeable impact (positive or negative). @punk-kaos did you happen to have a test harness for identifying / verifying performance? |
|
Intermediate review: Late tonight is our sked on 160 where we see if we can hook up with stations in Sweden and Russia that we exchange MSG's with. Conditions don't always allow it, but as we get further in the shorter days in the northern hemisphere it gets better on 160 and there's no such thing as a strong signal in the noise on MF running only 4 watts. Keep in mind there's a lot of things that can affect this - the base noise in your area, the quality of your receiver and how it's set, antenna system, etc.. I'm quite familiar with Kalman filters and the design here is sound. We use them extensively in aircraft autopilot code. They are used not only in navigation systems, they are used in software defined radios for signal processing. You have to realize when testing that you not going to see any difference within the normal limits of the decoder. In theory, the design of the filter should provide some gain while attempting to filter the "noise". It's a linear quadradtic estimation, however the variants used in aircraft systems is an EKF (Extended Kalman Filter) for nonlinear systems. So to actually see what the filter can do, it needs to be run in real world "noise" to the limit of your setup to see if it can separate signal from noise. However, I have a question for @punk-kaos This kind of relates to a long-term goal we have for the JS8 code; is it possible to code the filter as a new class, call it kalman.cpp or whatever you want? Call that class from the decoder. The reasoning here is that we want to break down classes in mainwindow eventually, and sort the tree out in folders so the source tree begins to make more sense. In my experience with Kalman filters, I've never seen one that can't be "tweaked" for better performance. If that filter was in a separate class we can tweak on it all we want without affecting the basic decoder class. As long as we're doing this we could make a new folder called Just a thought and question. |
I'm not against refactoring it into its own class. Once you guys get done reviewing it and we're all on the same page if it works or not I'll put some time into that. It would be nice to see it a little less monolithic after all... I was looking at the tree with an eye to an Android port but it was a big task with it's current monolithic design. |
Waiting for greyline in western Russia right now to put this software thru its paces :-) Yes, the monolithic design is bad. BUT, if you are eying an Android port we'll make a separate branch for it, created from master, and that branch is Android development only, so nothing that gets done on master will affect it, and there's no need to make it compatible for merging with master. There was at least one other guy I saw on groups.io that was interested in this. C++ Qt apps will compile and run on Android, I've built them before. But the fact that the UI is powering the back end here is not compatible with how an Android app needs to be developed. And I don't see that possible without having a separate branch where work on the Android port won't get disrupted. So if either you individually, or want to collaborate with some others, need something like this, we'll make it happen and it's your branch. |
We'll see how ambitious I get. QT will compile for Android, but like you said I got stuck on trying to split the UI from the back end in a way that works for Android. I'll noodle on it a bit and see if its something I want to tackle. I was hoping originally that I could split off the frontend and have the backend be a relatively easy port, but when I realized the frontend and the backend are so tightly tied together I realized it was a lot bigger task than I expected it to be... Probably why nobody else has done it yet :P |
Yes, it definitely is a hurdle. I'm looking at a port to iPadOS and I got the same problem. I can design the UI and lay it out with Swift in one afternoon. Now, I have to hook the UI to the back end :-) It would require a complete port to Objective-C for the back end under a difference license. Apple's TOS for the App Store are not compatible with GPL due to the fact they don't distribute the app to everybody - only Mac or iOS users can get it there. The GPL claims that's a "restriction" on people's "rights" so Apple just flat rejects GPL licensed software hosted on the App Store. Thinking about it, if some guys (and gals) would want to try an Android port a completely separate repository for it would better than a branch. Then your commit history, PR's, etc. is separate from the desktop version. Could make that happen too. |
|
Last night I only saw one instance where this may have done something. I sent a group SNR? query to see who was on and five stations responded. One of them decoded at -31. I sent the standard group QUERY MSGS and got MSG ID's from two of them, the extremely weak station was one of them. Still at -31. I queried the MSG ID on that one first, and now he came up to -24. But I got a 100% successful decode on the MSG with no dropped frames and it took about 5 minutes to transmit it. To me that's pretty impressive because at -24 it can sometimes take a couple tries on a long MSG. My basic settings are to adjust audio drive with the radio with antennas disconnected so I'm down near 5dB. With antennas connected I adjust RF gain to show 25-30 dB on a clean bandpass with no signals on it. On 160 I run AGC on Fast with 0.1 second decay time (IC-7300). Antenna system used was my 127' vertical for transmit, 920ft Beverage for receive, the antennas are switched with a relay downstream of the radio, the Beverage is pointed E/NE. Running it with my FlexRadio today, but 30m is dead at present. Although about an hour ago I did decode a MSG that was sent on the same offset I'm using that came from a station in Canada. SNR says he was -25 and was using "F" mode, it was a pretty long MSG and no dropped frames. Overall, I think I'm seeing better decode reliability with this with signals in the mid -20's range. The Flex does have a better receiver than the IC-7300 and there is no hardware interface or physical connection between the computer and the radio with the Flex. Which usually makes it an overall better system with less "built in" noise. The IC-7300 is probably a better test bed and it is the radio I use for digital modes most of the time. The Flex, due to its superior audio, is normally used for SSB phone. So I'm continuing to run it and test it. Not disappointed and it definitely doesn't do anything bad. |
|
Just curious, are any folks measuring the impact of features like this on older hardware, say like a Pi trying to multi decode?
…On Dec 7, 2025 at 12:13 PM -0500, Chris Olson ***@***.***>, wrote:
Chris-AC9KH left a comment (JS8Call-improved/JS8Call-improved#82)
Last night I only saw one instance where this may have done something. I sent a group SNR? query to see who was on and five stations responded. One of them decoded at -31. I sent the standard group QUERY MSGS and got MSG ID's from two of them, the extremely station was one of them. Still at -31. I queried the MSG ID on that one first, and now he came up to -24. But I got a 100% successful decode on the MSG with no dropped frames and it took about 5 minutes to transmit it. To me that's pretty impressive because at -24 it can sometimes take a couple tries on a long MSG.
My basic settings are to adjust audio drive with the radio with antennas disconnected so I'm down near 5dB. With antennas connected I adjust RF gain to show 25-30 dB on a clean bandpass with no signals on it. On 160 I run AGC on Fast with 0.1 second decay time (IC-7300).
Antenna system used was my 127' vertical for transmit, 920ft Beverage for receive, the antennas are switched with a relay downstream of the radio, the Beverage is pointed E/NE.
Running it with my FlexRadio today, but 30m is dead at present. Although about an hour ago I did decode a MSG that was sent on the same offset I'm using that came from a station in Canada. SNR says he was -25 and was using "F" mode, it was a pretty long MSG and no dropped frames.
Overall, I think I'm seeing better decode reliability with this with signals in the mid -20's range. The Flex does have a better receiver than the IC-7300 and there is no hardware interface or physical connection between the computer and the radio with the Flex. Which usually makes it an overall better system with less "built in" noise. The IC-7300 is probably a better test bed and it is the radio I use for digital modes most of the time. The Flex, due to its superior audio, is normally used for SSB phone.
So I'm continuing to run it and test it. Not disappointed and it definitely doesn't do anything bad.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
@jsherer @Chris-AC9KH Ever since Allan reworked the decoder, and we did some tweaks on it last year, I've seen improved results similar to Chris' description. We based the improvements we did on my testbed (two radios, next to each other, connected to separate dummy loads, noise floor adjusted to 30dB with RF and audio gain controls, so that most of that 30dB is radio noise). The radios used are a Yaesu FT-817nd (my primary station) and a Yaesu FT-100D. In my testing with this "improved" code, I found no difference in performance. I was primarily measuring for weak-signal detection threshold with clean band noise (no other signals on band, obviously since I'm on dummy loads). I haven't looked into the theory of the filtering scheme used on the "improved" code, but if it has the theoretical possibility of improving detection thresholds with other signals present in-band, then it would be good to have. As far as I can tell, the "improved" code caused no problems and reported identical S/N numbers as compared to "standard" code. I ran it on-air for several hours and noticed no difference in the feel of how it performed. Doing rigerous tests on-air would be difficult or impossible. It would be interesting to do some simulations with tightly-controlled additional nearby signals present and compare S/N reports and detection thresholds between the two codesets, but is probably a lot more work than is needed. If the theory is sound and the proposed code doesn't cause any problems, or unnecessarily consume CPU resources, I say let's go with it after a little more on-air training. Something to consider for 2.4.2? ;) 73, P.S. Part of my point is that to answer Jordan's question, I'm using "old" hardware, my workstations using the internal audio chipsets (one is Linux, the other is Windows). My Linux workstation is quite minimal by today's standards: A dual-core Lenovo Thinkpad x200 from about 15 years ago, 6GB RAM, Linux Mint 22.2. That's probably in or below the class of many RPi stations. P.P.S. I would expect that modern rigs with sophisticated digital filtering and noise reduction, as Chris points out, would get better S/N readings than my older analog equipment can get, even with my Collins filters, so Chris' -31dB reading seems entirely possible. I've been able with my tests to achieve -29dB decodes on occasion, -26dB with 100% reliable copy. This result was unchanged in testing original code comparing it to the "improved" code. |
@jsherer I have a Raspberry Pi5 but it's non-functional right now. I was using it as a wfview server on my IC-7300 and the microSD card that it runs on is not suited for server-duty so it eats another SD card after about two weeks of runtime. I'm running it on a Mac mini M4. So what we're looking at here is not a thing where where, ok, the decoder could decode at -28 before and now it's decode at -35. That's not the way it works. I've had decodes at -31 before like I had last night. There's a limit to what the decoder is going to be able do no matter what. A strong deciding factor with that is going to be the quality of the receiver, and the hardware you're running it on. All audio systems have built-in "noise". Some are better than others, some are really bad. No, what this is a filter that almost all software defined radios have (if they are higher-end SDR's). It uses a pseudo Kalman algorithm (Kalman filters are a whole nuther topic) to help ascertain what is signal and what is noise. So it uses an estimation to pull signal and suppress noise before the decoder gets ahold of it. That's in basic terms. Today, not much exciting going on. I'm on 30m and it's the normal stuff. But 30 is a band with less noise. Where this shines is on 80 or 160 where the noise floor is higher. Based on what I saw last night with decode reliability, I was impressed. I didn't have to use a single AGN? last night and several of the other guys did. A lot of times you can use two AGN?'s and it still don't get 100% decode so you just send a manual ACK that you got it to clear their queue because you actually did get it, just different frames didn't decode on the second one. This definitely does not reduce the performance of the decoder or affect it adversely in any way that I have seen. But it think it does have the capability to improve decode reliability within the limits of what the decoder can do in the first place. So the lower the hardware quality, the more advantage I think this could provide. So still running it, switching to 80 in a bit, continuing evaluation......... |
Just to piggy back on this, it's not likely to show a wild improvement when noise is steady and consistent. It's really built to thrive on a chaotic noise floor. I wrote it primarily to see if I could beat the horrible S9+ noise in my home urban environment. I've been testing it by running the audio output of the radio into two different laptops, one running vanilla js8call and one running my fork. That lets me watch both codebases try and tackle exactly the same signal. It seems to pull signal out of terrible noise much more consistently from my view. |
|
In my original testing, the Kalman filter never really made a meaningful difference. Any improvement it showed was tiny...basically within the normal variation you’d see anyway...so it didn’t end up being worth the extra CPU. The design here seems fine though, so I'd just recommend we let folks with lower power devices disable extra filtering. |
This is more than JUST a Kalman filter though. I've also added Soft combining of repeat frames as the other heavy hitter. This caches normalized LLR pairs keyed by mode, coarse freq/dt bins, and signature of bit signs and attempts to match repeats across the multiple decode passes and produces a 'confidence score' and pull out a complete frame across the the summary of all the decodes. There's also a couple of noise sampling changes, but they're more of a little boost than a heavy hitter. |
|
I built an Apple Silicon build only. A little more optimized on the M4 due to the binary and libraries being less bloated. I've been watching memory page ins, Mach and Unix system calls, threads and cpu time for about 22 hours. Total cpu time is within 0.8% which is so minor it's not worth mentioning. I'm running Flrig to test that fiasco, it's about the same as Flrig on cpu and Flrig don't even have a decoder. No memory leaks, get a few page ins when threads jump from 15 to 21 or more depending on how many decodes we're running. But it frees physical memory correctly when threads terminate. Have had no hangs in any threads in 22 hours. I don't see anything wrong with it.
That's a result of a signal well outside my bandpass, using a soft-edge filter so it leaks thru. The SNR calculation in JS8Call is a pretty basic formula so all a -60 is is a direct relationship to the level of the signal vs total noise across the detector, the signal was 1/60th of the total noise, which is supposed to be clamped at -28 but it's not if the signal was outside the passband. What little bit of signal leaked thru was so weak that it would never show on the waterfall, nor be able to hear it with the human ear. But the decoder decoded it which I have never seen yet. Indicating to me that this actually works. The combination of filtering and improved multi-pass error correction pulled that signal out of the noise. Since this is a weak signal mode IMO we cannot afford to NOT have this in it. |
|
@punk-kaos how hard would it be for you to break this out into a separate class? We might want to tweak on it yet at some point
Basically, you don't see any improvement until you hit one of those extremes. Or are operating in a very noisy environment like MF or 80m. Granted, I'm running it on a M4 which is one of the powerful desktop processors on the planet. I could build a universal app and run it on my Intel MacBook Pro and see what it does there. @Joe-K0OG did you see any difference in cpu time? |
I can make that happen. I'll dig into it and get it rolling. |
No, I can not detect any difference in CPU or RAM usage on my minimal dual-core laptop running Linux Mint. The enhanced code certainly does NOT harm anything, so I'm fine with going forward with it if it can at least theoretically yield better decodes in very-noisy conditions. My testing has been between two systems, and on 40m, neither of which have very much noise to deal with. 40m is fairly quiet right now. It would be interesting to see how the enhanced code performs with thunderstorm static. So, as far as I can tell, I don't think it's necessary to give the user the ability to enable/disable enhanced decoding since it doesn't seem to noticeably use more resources. I have questions, considering @Chris-AC9KH -60dB decode:
73, |
|
@Joe-K0OG that's pretty much what I'm seeing here as far as cpu and memory. According to some diagnostics I ran on it (on MacOS 26) it's using about 0.8% more cpu time for just the decoder, but that's so minimal you can't detect it. Memory pages and usage is totally normal. That -60 decode is totally normal considering the setup and conditions it was done under. The SNR calculations are not designed that type of scenario, and they are perfectly ok for all normal operating conditions. The amazing part (to me) was that the decoder was able decode that transmission, which consisted of two frames. The stock software no way can do that, and it was not a false decode. The signal signature appeared in the bandpass due to the type of filter I was using and it decoded a perfectly valid very weak signal even though the SNR calculation for it was not valid because it wasn't taking into account the power of the noise in the total spectrum that the radio filter was allowing. There was plenty of FT8 from lower down bleeding in too and the decoder was ignoring those. I was running the soft-edge filter specifically to increase "noise" in the bandpass. So I see nothing wrong with how this works, the design is sound. JS8 is a weak signal mode and this is a further tweak to the original decoder design that makes it more sensitive and accurate in estimating what is valid signal and what is noise that should be rejected. Is it a big change? No. It's only going to help in very noisy conditions where you can't normally see the signal on the waterfall, or hear it with the human ear, to provide better decode reliability. The only other mode I've seen that's able to do that is Olivia, pulling "ghost" signals out of nothing and you get a print on the screen. This is the first time I've seen JS8 do it. |
|
And I'll add that I'm continuing to run this for going on 36 hours now. I'm seeing improvement in decode reliability even in normal conditions. We got one guy who I'll call a "professional heartbeater". He sends out HB's and if he don't get ACK's back that report his signal at +10 he fires up the amp and throws a thousand watts at it. Like he figures this is more like FT8 where a HB ACK is a "contact". When he comes on there with the amp the signal splatters halfway across the waterfall and get dropped frames on every other frame. It's even decoding more of those. I think this is the LLR pairs being cached and better matching on the decode passes to provide a better estimation of the symbols. |
huh, now THATS fascinating. I never thought about how it might act on overdriven signals. Sorta makes sense its decoding though. Not really a huge difference to the decoder between bad hot signals and bad weak signal I guess. |
Nope, there's not any difference. It will work the same either way, and that's I'm seeing with this misguided user. It's still not getting every one, but I'd say roughly a 50% improvement. I got him blocked - my station never responds to his HB's. And he sometimes sets this up so it transmits every 15 minutes and he's not even at the controls. So it is a quite common thing when he comes on the band and I've seen enough of 'em now to see I'm getting more decodes on that. |
|
Well, it seems that if @punk-kaos can break out the code in a separate class before we go to release for 2.5, maybe we should roll it in? I've run it for a couple of days and it's working fine, even though my conditions don't take full advantage of the possible improvement. Is there additional testing we need to do? I think we've proven it on all platforms. Or, do we even need to wait for class separation, since that may be part of the greater effort of code cleanup? I would like to continue testing with it integrated into 2.5.0 main. 73, |
|
I did a test by using a nearby transmitter hooked to a dummy load to splatter the frequency of my receiver with noise while trying to receive some JS8Call signals (this was on 40m). I found that some signals were decoding with reports of -30 to -60dB. When I tried it with unmodified code, I also got a couple of decodes in the low -30s, but the modified code seemed to do a little more. This was NOT a rigorous test, but just a "look & feel" kind of test. Without the noise generator, I notice I'm seeing more decodes than I would consider normal between -20 and -30dB for 40m on a quiet afternoon, so I think this enhanced code may be yielding some benefit. 73, |
|
@Joe-K0OG yep, those -30 to -60 ones are going to be outside the usable range of the SNR calculations and that is duplicatable with your revised test setup to insert some extra "noise" into the bandpass. Even the low 30's with the stock software is outside the range of the calcs. SNR is a sampling of power in the signal fundamental relative to the RMS sum of the energy of the in-band noise components, excluding harmonics. When it shows anything less than -28 you can ignore that. It was a very weak signal and it attempted to calculate it. I'm not seeing outside the usual on 30m today. Very limited activity on JS8, the dB meter is dead on 30 and not even moving. The FT8 guys are going strong a few KC up the waterfall (on my Flex). But I'll be going to 80 at 6:30 before we eat supper and that's where the more interesting things happen :-) |
|
I've just pushed a commit to break out the changes into their own classes and files. Just a forklift move without any functionality changes. |
|
@punk-kaos I'm going to make a request that you document your code before any merge. See #4 for instructions. Thanks! |
Bah.... Documentation :P Yeah, I get it. I'll add that to my list! |
|
Ok, classes include Doxygen hooks for basic descriptions etc. |
Chris-AC9KH
left a comment
There was a problem hiding this comment.
This is excellent. Makes the code much more maintainable.
Nice work! Approved for merge.
|
@punk-kaos BTW - if you're still eyeing that Android port, I created a new Android-port repository for it yesterday. There's some other guys interested in it too, and announcement made on groups.io, so you might have collaborators with time. All it will take is for somebody to get a rough start, and I think they'll come. So if you get a slow day looking for something to do, let me know and that repo is yours. We'll create an Android Development team for it. |
I MIGHT have noticed that already, and I've been giving it the eyeball. Dunno yet if I'll tackle it but it sure would be nice to have a good Android port. I'll keep you posted :) |
|
I sent you an invite just in case. With holidays coming up everybody is busy. But if and when you get the urge, it's there and ready to go :-) |


Some decoder optimizations to increase the sensitivity of the decoder.
In THEORY these changes should net a +3-+6db sensitivity increase...
With these changes I'm seeing consistent 'normal' mode decodes down to about -28db.
These changes are fully compatible with standard JS8Call's decoder. Nothing on the TX path has been touched.
I've been running these changes for a few days now and haven't noticed anything breaking so I'm REASONABLY confident that they're good to go. Hopefully they're useful!