Skip to content

AM Co-Channel Canceller (client-side DSP for MW/SW DX) #578

Description

@k9mq

Request preparation

  • I used an AI assistant to help structure this request
  • I checked for existing issues covering the same feature

What would you like?

AM Co-Channel Canceller (client-side DSP for MW/SW DX)

Labels: feature, dsp, audio


What

An AM Co-Channel Canceller is a client-side DSP processing stage that, when
enabled in AM or SAM (Synchronous AM) mode, estimates and subtracts the carrier
and modulated audio of a dominant interfering station from the received I/Q
stream — leaving the weaker buried station audible underneath. The user tunes
to the channel of interest, identifies the offset of the interfering carrier,
and the algorithm suppresses it in real time.

A secondary (simpler) variant of this concept is sideband nulling in SAM
mode: selecting only the LSB or USB half of the demodulated audio to avoid
the sideband where co-channel interference dominates. KiwiSDR implements this
as a SAM mode option ("null LSB" / "null USB").


Why

Medium wave (MW) and shortwave (SW) DXers frequently encounter two or more
stations occupying the same or nearly the same channel:

  • Regional co-channel allocation — many countries share the same MW
    frequency, relying on antenna directivity and power limits that break down at
    long range.
  • DX propagation — a distant weak station arrives directly beneath a strong
    local that normally dominates.
  • Jamming — e.g. broadband noise jamming or deliberate carrier injection.

With standard AM or SAM demodulation both stations mix together and the weaker
one is unintelligible. A co-channel canceller can make the weaker signal fully
readable, which is otherwise impossible regardless of AGC, filter bandwidth, or
noise reduction settings.

For amateur radio operators, this applies equally to 60 m (5 MHz band) and
other frequencies shared with broadcast services, as well as cross-band
interference on HF.

The FLEX-8600's onboard DSP does not offer this feature. It must be implemented
client-side in the audio post-processing pipeline, downstream of the radio's
demodulator output.


How Other Clients Do It

SDR# (AirSpy)

SDR# introduced an AM Co-Channel Canceller plugin in late 2020 (around build
1770). Key implementation details:

  • Works in DSB (double-sideband) or RAW modenot in USB/LSB/AM mode.
    The algorithm needs access to both sidebands simultaneously.
  • A companion Micro Tuner plugin displays a ±15 Hz zoomed spectrum around
    the tuned carrier, making sub-Hz carrier offset visible.
  • The user clicks a carrier peak in the Micro Tuner to set the IF offset
    the exact fractional-Hz difference between the wanted and interfering carriers.
  • Controls exposed:
    • IF Offset (Hz, fractional) — frequency of the interfering carrier
      relative to the tuned center
    • Channel Bandwidth (Hz) — how wide a band around that carrier to model
      and subtract
    • Auto Tune — attempts automatic carrier peak detection (often best left
      off)
    • Lock Carrier — phase-locks to the selected carrier peak
    • Enable toggle

The underlying algorithm estimates the interfering carrier's complex amplitude
and phase via a narrow PLL or Goertzel-style tone tracker, then reconstructs
and subtracts the full AM signal (carrier + both sidebands) from the baseband
I/Q. Multiple carriers can theoretically be suppressed by running the algorithm
iteratively.

KiwiSDR (SAM mode sideband nulling)

KiwiSDR's web client offers a simpler but practical variant in SAM mode:

  • SAM — standard synchronous AM (phase-locked carrier, both sidebands)
  • SAL — SAM, null LSB (only upper sideband is used after synchronous
    detection)
  • SAU — SAM, null USB (only lower sideband is used)

This does not cancel the interferer's carrier — it simply discards one sideband.
If the interfering station's modulation is stronger in one sideband than the
other (common when the two carriers have slightly different frequencies and
phasing), nulling the affected sideband gives a clean audio output from the
remaining one.

This is a much simpler implementation but useful in many real-world cases.


Suggested Behavior in AetherSDR

Phase 1 — SAM sideband nulling (simpler, implement first)

Implement a SAM mode (or sub-modes of AM) with three options, matching
KiwiSDR:

Mode button Behavior
SAM Synchronous AM: PLL locks carrier, demodulates both sidebands
SAL SAM, null LSB — output only USB half after sync detection
SAU SAM, null USB — output only LSB half after sync detection

The radio's AM mode would be used at the protocol level; the sync detection and
sideband selection happen entirely client-side in AudioEngine
after the float32 audio is received via PCC 0x03E3.

The mode selector buttons in the VFO widget would gain SAM / SAL / SAU
options, or a sub-selector that appears when AM is active.

Phase 2 — Full co-channel canceller (harder, higher impact)

Implement a CoChannelCanceller class in src/core/, analogous to
SpectralNR and RNNoiseFilter:

// src/core/CoChannelCanceller.h
class CoChannelCanceller {
public:
    void setEnabled(bool on);
    void setIfOffsetHz(double offsetHz);   // interferer carrier offset from center
    void setBandwidthHz(double bwHz);      // cancellation bandwidth
    void setSampleRate(int rate);          // 24000 Hz from radio
    // processes stereo float32 buffer in-place
    void process(float* samples, int frameCount);
private:
    // PLL or Goertzel tone tracker + adaptive subtraction
};

User workflow:

  1. Tune to the desired AM station in AM or DSB mode.
  2. Open RX Applet → new CCC (Co-Channel Canceller) section (alongside
    existing NB/NR/ANF toggles).
  3. Click CCC toggle to enable.
  4. A small offset dial appears (range ±500 Hz, resolution 0.1 Hz) — user
    sets the carrier offset of the interfering station. Optionally an Auto
    button attempts to find the strongest nearby carrier automatically.
  5. A BW slider controls cancellation bandwidth (default ~9 kHz for a
    standard AM channel).
  6. The DSP runs in the existing AudioEngine post-processing chain, after
    NR/NB and before the audio sink.

The canceller state should be persisted per-slice via AppSettings:

CoChannelCancellerEnabled_0 = True
CoChannelCancellerOffset_0  = 12.3
CoChannelCancellerBW_0      = 9000

Protocol Hints

No new FlexLib API calls are required. The co-channel canceller operates
entirely client-side on the received audio stream (PCC 0x03E3, float32 stereo
at 24 kHz) and does not interact with the radio firmware.

The only protocol-adjacent concern is that the radio should be in AM or
DSB mode (not USB/LSB) so that both sidebands are present in the audio
stream. The mode set command is:

slice set 0 mode=AM

For Phase 1 SAM sideband nulling, the AetherSDR mode selector would send
mode=AM to the radio regardless of whether SAM/SAL/SAU is selected (the
distinction is purely client-side post-processing).

DSP algorithm references for implementation:

  • Goertzel algorithm for precise tone detection at fractional-Hz resolution
  • Complex LMS/RLS adaptive filter for carrier + sideband subtraction
  • Simple PLL (phase-locked loop) for carrier phase tracking

No upstream firmware changes needed. No optimistic updates. All processing is
in the client audio pipeline.

Metadata

Metadata

Assignees

No one assigned

    Labels

    New FeatureNew feature requestaudioAudio engine and streamingenhancementImprovement to existing featuremaintainer-reviewRequires maintainer review before any action is takenpriority: lowLow priorityprotocolSmartSDR protocolupstreamRequires upstream firmware/protocol change

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions