IQ out!
Things are starting to get shape.
The first obstacle was how to get anything out of the SDR#, where to start with the plugin?! Grace to the examples on the SDR# page and thanks to Vasili who kindly shared his source code for the TcpServer plugin, I was able to hook into the IQ stream coming out from the SDR#.
The current version of Vasili’s plugin hooks into the SDR# FilteredAudioOutput, captures the data stream and uses the SDR# resampler to transform it into a 48kHz SR stream of type float. This is more or less the audio mono stream that gets served by his TcpServer plugin. At that point I added an extra input to the Scytale-C demodulator to handle this stream, effectively bypassing the virtual audio cable.
This is the hook type used to tap into the SDR#’s audio:
However we want the IQ stream, not the audio one. Vasili pointed out that I would need the DecimatedAndFilteredIQ hook for this. This hook allows one to tap into the complex IQ data. At this point I was able to modify the plugin to tap into the IQ stream and these are values that I have gotten out for different device inputs:
As one can see, the complex stream symbol rates are all over the place. AirSpy’s Youssef explained the mistery:
The decimation only works on powers of two. This means the final sample rate will be device_samplerate / decimation_ratio, with decimation_ratio = 2 ^ N. For WFM, the N corresponds to the maximum such that the decimated sample rate is >= 200 kHz. For all the other modes, N is set such that the decimated sample rate is >= 32 kHz.
However, Scytale-C as is, needs 48kHz input. Let alone audio or IQ. And therefore, before trying to do anything else I needed to understand how can I get this symbol rate from all the values above.
Youssef pointed out that what I need is to use the resampler class with the following constructor:
Resampler(double inputSampleRate, double outputSampleRate, int tapsPerPhase = DefaultTapsPerPhase, double protectedPassband = DefaultProtectedPassband);
This processes a float stream as:
int Process(float* input, float* output, int inputLength);
So at this point the question was, well my data is complex, how do I process it as float? And the answer is with something like this:
Youssef shared his trick for how to do this more efficiently, and that is to use not the Process() method, use the ProcessInterleaved() method and feed it with the single IQ complex buffer typecasted to float! Pretty cool!
var iQLengthRe = _resamplerRe. ProcessInterleaved(in_ptr, out_ptr, length);
var iQLengthIm = _resamplerIm. ProcessInterleaved(in_ptr + 1, out_ptr + 1, length);
Which super-fancy looks like this:
So far so good, but what does the output represent? Logically it should represent the green band below (decimated and filtered):
Where the yellow band would be returned if one would hook up for the RawIQ ProcessorType.
So here we are, at this point we can output our desired 48kHz sample rate IQ for the filtered band. Below are the first 100 samples captured after pressing the “play” button in the SDR#. Nice to see how values are getting brought up.
And this is for a few seconds at 48kHz SR in our 4kHz filtered band: