Ham Cockpit   

    Show / Hide Table of Contents

    Demodulator Plugins

    The source code of the demodulator plugins is available here GitHub.

    In this example we demonstrate how to implement multiple plugins in the same .NET assembly, how to create the plugins that are used in DSP Pipeline, and how to make use of the DspFun library for signal processing.

    Three Plugins in the Same Assembly

    The three demodulators presented here demodulate CW, SSB and RTTY respectively. The demodulation process is the same for all three modes, the difference is only in the parameter values. Demodulation is implemented in the base class, BaseDemodulator, each plugin inherits from that class and changes the default values of the Pitch and Sideband parameters, as well as the plugin and mode names:

    public abstract class BaseDemodulator : IPlugin, IDemodulator
    {
      ...
    }
    
    [Export(typeof(IPlugin))]
    [Export(typeof(IDemodulator))]
    public class CwDemodulator : BaseDemodulator
    {
      public CwDemodulator()
      {
        pluginName = "CW Demodulator";
        modeName = "CW";
        settings.Pitch = 600;
        settings.Sideband = Sideband.Upper;
      }
    }
    
    [Export(typeof(IPlugin))]
    [Export(typeof(IDemodulator))]
    public class SsbDemodulator : BaseDemodulator
    {
      public SsbDemodulator()
      {
        pluginName = "SSB Demodulator";
        modeName = "SSB";
        settings.Pitch = 0;
        settings.Sideband = Sideband.Upper;
      }
    }
    
    [Export(typeof(IPlugin))]
    [Export(typeof(IDemodulator))]
    public class RttyDemodulator : BaseDemodulator
    {
      public RttyDemodulator()
      {
        pluginName = "RTTY Demodulator";
        modeName = "RTTY";
        settings.Pitch = 2125;
        settings.Sideband = Sideband.Lower;
      }
    
    ...
    }
    

    The derived classes export IPlugin, which turns them into plugins, and IDEmodulator, so that DSP Pipeline can use them for demodulation.

    Demodulation

    The demodulation process includes these steps:

    • resample the input I/Q signal down to 12 kHz, the standard sampling rate for audio processing;
    • flip the sideband if necessary;
    • if Pitch is non-zero, mix the signal up to the pitch frequency;
    • apply a 0..6 kHz filter to suppress the lower sideband.

    The DspFun library that comes with Ham Cockpit has classes for all these signal processing tasks, which makes our job much easier. Moreover, these classes implement the ISampleStream interface, so they are easy to cascade.

    IDemodulator : IInitSampleStream

    IInitSampleStream.Initialize

    The Initialize method implemented by the plugins creates objects that will preform the signal processing tasks and connects them in a chain:

    private ISampleStream signal;
    ...
    public void Initialize(ISampleStream source)
    {
      //input signal
      signal = source;
    
      //resampler
      Resampler resampler = new Resampler(SignalFormat.AUDIO_SAMPLING_RATE, 30);
      resampler.Initialize(signal);
      signal = resampler;
    
      //sideband flipper
      flipper = new SidebandFlipper();
      flipper.Initialize(signal);
      signal = flipper;
    
      //mixer
      mixer = new Mixer(settings.Pitch);
      mixer.Initialize(signal);
      signal = mixer;
    
      //filter
      float Fc = 2962f / SignalFormat.AUDIO_SAMPLING_RATE;
      var realTaps = Dsp.BlackmanSincKernel(Fc, 235);
      Complex32[] taps = Dsp.FloatToComplex32(realTaps);
      Dsp.Mix(taps, 0.25); //shift filter passband -3..3 kHz -> 0..6 kHz
      filter = new ComplexFirFilter(taps);
      filter.Initialize(signal);
      signal = filter;
    }
    

    The ComplexFirFilter needs an array of filter taps as an input parameter to the constructor. We use the helper methods from DspFun to generate the taps.

    IDemodulator : ISampleStream

    ISampleStream.Format

    The output from the demodulator is real-valued audio data filtered to 0..6000 Hz. To create the Format property that describes the output format of the demodulator, signal.Format of the last processing stage is used as a prototype, and the new parameters are set as follows:

     format = new SignalFormat(signal.Format)
      {
        IsComplex = false,
        PassbandLow = 0,
        PassbandHigh = 6000,
        Sideband = settings.Sideband
    };
    

    ISampleStream.Read

    The Read method of the demodulator is called by the next plugin in the DSP Pipeline chain. When this method is called, the demodulator calls Read on the previous plugin in the chain to get input data, and uses its own signal processing blocks to produce downsampled, freq-shifted, sideband-flipped and 6kHz-filtered I/Q data. The final step is to discard the imaginary part of the I/Q values and make it real-valued data:

    public int Read(float[] buffer, int offset, int count)
    {
      //input I/Q
      int read = count * Dsp.COMPONENTS_IN_COMPLEX;
      if (inputBuffer == null || inputBuffer.Length < read) inputBuffer = new float[read];
    
      //read downsampled, freq-shifted, sideband-flipped and 6kHz-filtered I/Q
      read = signal.Read(inputBuffer, 0, read);
      count = read / Dsp.COMPONENTS_IN_COMPLEX;
    
      //complex to real
      for (int i = 0; i < count; i++) buffer[offset + i] = inputBuffer[i * Dsp.COMPONENTS_IN_COMPLEX] * 100f;
    
      SamplesAvailable?.Invoke(this, new SamplesAvailableEventArgs(buffer, offset, count));
      return count;
    }
    

    IDemodulator : IModeSwitch

    IModeSwitch.Mode

    The Mode property returns the name of the mode demodulated by the plugin. The setter is not implemented since each demodulator supports only one mode:

    public string Mode { get => modeName; set => throw new NotImplementedException(); }
    

    IModeSwitch.Sideband

    The Sideband property is read-write, it reflects the Enabled state of SidebandFlipper used in the demodulator:

    public Sideband Sideband { get => settings.Sideband; set => SetSideband(value); }
    
    Back to top Generated by DocFX