DSP Functions
The DSP Fun library that comes with Ham Cockpit includes a number of signal processing
classes that the plugin authors may use in their projects. The library is published with
source code
, the class reference is
here. This article provides some code examples.
For more examples, please see Demodulator Plugins.
AudioClientErrors
This class is useful when working with CSCore, a popular audio I/O library. It provides the error messages not defined in the library and should be used like this:
private void RethrowException(Exception e)
{
if (e is CoreAudioAPIException && e.Message.Contains("Unknown HRESULT"))
message = AudioClientErrors.Message((e as CoreAudioAPIException).ErrorCode);
else
message = e.Message;
throw new Exception(message);
}
ChannelSelector
is another class that works with the CSCore library. it is used to extract one of the channels from a multi-channel audio data stream. Usage:
CSCore.ISampleSource source = new ChannelSelector(source, ChannelMask.SpeakerFrontLeft);
...
source.Read(buffer, offset, count);
When the Read
method is called on the ChannelSelector
object, it reads
multi-channel data from the source
that was passed to the constructor and
returns only the samples that belong to the specified channel
(the SpeakerFrontLeft
channel in the example above). Since both
source
and ChannelSelector
implement the CSCore.ISampleSource
interface, it is easy to chain multiple signal processors like this.
RealFft and ComplexFft
are wrappers around the FFT functions in the Intel IPP library, one of the best speed-optimized DSP libraries. These classes hide the complexity of invoking the IPP functions behind a simple interface:
//create FFT object
const int FFT_SIZE = 65536;
ComplexFft fft = new ComplexFft(FFT_SIZE);
//put time-domain data multiplied by the window function in fft.TimeData
for (int i = 0; i < FFT_SIZE; i++)
fft.TimeData[i] = data[i] * window[i];
//compute forward FFT and put results in fft.FreqData
fft.ComputeForward();
//compute power spectrum from FreqData
float[] pwr = fft.PowerSpectrum();
An example of RealFft
is available in the
Demodulator Plugins.
ComplexFirFilter and RealFirFilter
is another pair of wrappers around the IPP functions. Sample code is available in Demodulator Plugins.
Dsp.ApproximateRatio()
finds a rational (L / M) approximation of a floating point value
with smallest possible L and M values.
Useful for setting up sampling rate converters, such as
IppResampler
. Small L and M reduce the complexity
of the resampler. Example:
var InputRate = 44100;
var DesiredRate = 48000;
//allow the output rate to be within +/-1% from the desired value
var RateTolerance = 0.01f;
var (L, M) = Dsp.ApproximateRatio(DesiredRate / InputRate, RateTolerance);
var OutputRate = (InputRate * L) / M;
int filterLength = (60 * M) / L;
//when resampling, allow the last 3% of the bandwidth
//to be contaminated with mirror images
var usefulBandwidth = 0.97f;
var resampler = new IppResampler(M, L, filterLength, usefulBandwidth, 10);
IppResampler
An instance of IppResampler
may be created as shown in the ApproximateRatio
section. If the data are complex and/or multi-channel, a separate resampler is
used for each component. In the example below, two instances are used
to resample I/Q data. The offset
and stride
parameters of the Process
method
allow resampling multi-component data stored in a floating point array:
int resampledCount = resamplerI.Process(inputData, 0, stride, count);
resamplerQ.Process(inputData, 1, stride, count);
Resampled data are stored in the IppResampler.OutData
property.
MultipassAverage
is used to apply multiple passes of the moving average filter to real data, one sample at a time.
var stageDelay = 10; //in samples
var boxLength = 2 * stageDelay + 1;
var numberOfStages = 4;
var totalFilterDelay = stageDelay * numberOfStages;
var avg = new MultipassAverage(boxLength, numberOfStages);
...
float outputSample = avg.Process(inputSample);
OmniRigClient
The OmniRigClient
class talks to the
OmniRig engine
and uses it to control the radio via its CAT interface. Only the radio interfacing plugins
need to use this class directly, all other
plugins should talk to the radio plugin to read and set the radio parameters as
demonstrated in the
Frequency Display Demo plugin.
Create an instance of the OmniRigClient
class:
private readonly OmniRigClient Cat = new OmniRigClient();
Subscribe to its events to be notified when the radio settings change:
Cat.Tuned += TunedEventHandler;
Cat.ModeChanged += ModeChangedEventHandler;
Cat.StatusChanged += StatusChangedEventHandler;
Select Rig1 or Rig2, depending on the user settings:
Cat.RigNo = (int)settings.RigNo;
Enable the object only when your plugin is activated, and disable it as soon as the plugin is deactivated. Remember that the OmniRig engine requires exclusive access to the COM port and thus should be turned off when not in use:
private void SetActive(bool value)
{
Cat.Active = value;
...
}
When the OmniRigClient object is active, use its methods and properties to read and change the radio settings:
//read frequency
var current_frequency = Cat.RxFrequency;
//set frequency
Cat.RxFrequency = (int)new_frequency;
RingBuffer
RingBuffer
is a thread-safe buffer of the FIFO type for the floating point values.
This class is used in all places in the plugins when the I/Q or audio data are
received and consumed at different times, in different block sizes, and often on
different threads.
One example is a radio-interfacing plugin. The thread on which the plugin talks to the radio cannot be used for data processing, the code that receives the sampled data just writes the samples to the ring buffer and returns. Another thread reads those samples from the buffer and processes them. An example of such code is available in the Afedri Plugin.
SlidingMax and SlidingMin
These two classes implement the fast sliding minimum/maximum algorithm. Create an instance of the class like this:
var max = new SlidingMax(2 * maxDelay + 1);
The parameter passed to the constructor is filter length that is computed from
the desired filter delay, maxDelay
, expressed in samples.
The object created above may be used to process the values online (one sample at a time):
var filteredValue = max.Process(inputValue);
or to filter an array of floating point values in-place:
max.FilterArrayInplace(float_array);