Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aliasing in downconverters #1846

Closed
srcejon opened this issue Oct 12, 2023 · 16 comments
Closed

Aliasing in downconverters #1846

srcejon opened this issue Oct 12, 2023 · 16 comments
Assignees

Comments

@srcejon
Copy link
Collaborator

srcejon commented Oct 12, 2023

I'm seeing what I think is aliasing in the downconverters. It appears to be sample rate and demod input frequency offset dependent. With the steps below, you should be able to see it for both the AM and NFM Demods, but I suspect it is a problem for other demods too. Worst case I've seen so far, is that the out of band signal is only attenuated by ~20dB.

I'm using an RTL SDR with it's input connected via coax to a signal generator.
Set RTL SDR center frequency to 100MHz.
Set sample rate to 2.4MS/s and Dec to 1.
Add AM Demod with 25kHz frequency offset. Set RF BW to 20k. Lower squelch just above noise floor.
Add AM Demod with 620kHz frequency offset. Set RF BW to 20k. Mute audio.
Set signal generator to generate a fairly strong AM modulated signal at 100.62Mhz.
At this point, you should be able to hear the AM signal from the first demod (even though the spectrum shows no signal), with the demod's Channel Power measurement reading about 20dB below the second demod.

You should see the same thing if you change AM Demod for NFM Demod.

As another example, set sample rate to 1.6MS/s, and then put the signal and second demod at 100.42MHz (with the first demod still at 25kHz offset).

For a slightly different example at 1.6MS/s, set first demod to 5kHz offset, then put the signal and second demod at 100.4MHz

Doesn't happen for every sample rate as far as I can see, but not just a theoretical problem, as I originally noticed it while listening to some ATC.

@srcejon
Copy link
Collaborator Author

srcejon commented Oct 13, 2023

Here's the problem seen visually

image

Both File Sink's are close to the center of the main spectrum with 16x decimation, and thus shouldn't see any of the signal to the right of main spectrum.

There's no problem for the File Sink with delta f set to -74k (bottom file sink), however, quite a bit of the signal appears when delta f is set to -75k (top file sink), although the signal is ~400kHz away.

@f4exb
Copy link
Owner

f4exb commented Oct 14, 2023

Probably due to the fact that there is a chain of half-band downsamplers with limited FIR filter length so that out of band attenuation is limited. That's a compromise with CPU workload. I think the only way to fix this is to change the design of the channelizer.

Uncommenting the debug messages in DownChannelizer::createFilterChain we see the following in the log...

For -75k:

2023-10-14 07:24:17.087 (D) FileSinkBaseband::applySettings: m_log2Decim: 4 m_inputFrequencyOffset: -75000 m_fileRecordName:  "" m_centerFrequency:  100000000 force:  false
2023-10-14 07:24:17.087 (D) DownChannelizer::createFilterChain: Signal [-1200000.0, 1200000.0] (BW 2400000.0), Channel [-150000.0, 0.0], Rot 600000.0
2023-10-14 07:24:17.087 (D) DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)
2023-10-14 07:24:17.087 (D) DownChannelizer::createFilterChain: Signal [-1200000.0, 0.0] (BW 1200000.0), Channel [-150000.0, 0.0], Rot 300000.0
2023-10-14 07:24:17.087 (D) DownChannelizer::createFilterChain: -> take right half (rotate by -1/4 and decimate by 2)
2023-10-14 07:24:17.087 (D) DownChannelizer::createFilterChain: Signal [-600000.0, 0.0] (BW 600000.0), Channel [-150000.0, 0.0], Rot 150000.0
2023-10-14 07:24:17.087 (D) DownChannelizer::createFilterChain: -> take right half (rotate by -1/4 and decimate by 2)
2023-10-14 07:24:17.087 (D) DownChannelizer::createFilterChain: Signal [-300000.0, 0.0] (BW 300000.0), Channel [-150000.0, 0.0], Rot 75000.0
2023-10-14 07:24:17.087 (D) DownChannelizer::createFilterChain: -> take right half (rotate by -1/4 and decimate by 2)
2023-10-14 07:24:17.087 (D) DownChannelizer::createFilterChain: Signal [-150000.0, 0.0] (BW 150000.0), Channel [-150000.0, 0.0], Rot 37500.0
2023-10-14 07:24:17.087 (D) DownChannelizer::createFilterChain: -> complete (final BW 150000.0, frequency offset 0.0)
2023-10-14 07:24:17.087 (D) DownChannelizer::applyChannelization done:  nb stages: 4  in (baseband): 2400000  req: 150000  out (channel): 150000  fc: 0

For -74k:

2023-10-14 07:27:51.914 (D) FileSinkBaseband::applySettings: m_log2Decim: 4 m_inputFrequencyOffset: -74000 m_fileRecordName:  "" m_centerFrequency:  100000000 force:  false
2023-10-14 07:27:51.914 (D) DownChannelizer::createFilterChain: Signal [-1200000.0, 1200000.0] (BW 2400000.0), Channel [-149000.0, 1000.0], Rot 600000.0
2023-10-14 07:27:51.914 (D) DownChannelizer::createFilterChain: -> take center half (decimate by 2)
2023-10-14 07:27:51.914 (D) DownChannelizer::createFilterChain: Signal [-600000.0, 600000.0] (BW 1200000.0), Channel [-149000.0, 1000.0], Rot 300000.0
2023-10-14 07:27:51.915 (D) DownChannelizer::createFilterChain: -> take center half (decimate by 2)
2023-10-14 07:27:51.915 (D) DownChannelizer::createFilterChain: Signal [-300000.0, 300000.0] (BW 600000.0), Channel [-149000.0, 1000.0], Rot 150000.0
2023-10-14 07:27:51.915 (D) DownChannelizer::createFilterChain: -> take center half (decimate by 2)
2023-10-14 07:27:51.915 (D) DownChannelizer::createFilterChain: Signal [-150000.0, 150000.0] (BW 300000.0), Channel [-149000.0, 1000.0], Rot 75000.0
2023-10-14 07:27:51.915 (D) DownChannelizer::createFilterChain: -> complete (final BW 300000.0, frequency offset -74000.0)
2023-10-14 07:27:51.915 (D) DownChannelizer::applyChannelization done:  nb stages: 3  in (baseband): 2400000  req: 150000  out (channel): 300000  fc: -74000

That's one stage less and always taking the center. In the -75k case the passband is always at the edge of the filter. The first filter takes the right part but it folds the signal around 99.8 MHz which is on the left side back on the right side. And then it is carried over in the rest of the filter chain.

@srcejon
Copy link
Collaborator Author

srcejon commented Oct 14, 2023

In the -75k case the passband is always at the edge of the filter.

That probably explains the slight loss I can see for in-band signals. E.g:

image

There's nearly 2dB difference between power when delta f is -76k vs -73k.

I haven't looked at the code yet, but just going from your description, would a 1/8 rotation be possible / helpful, to keep the passband closer to the centre of the filter?

@f4exb
Copy link
Owner

f4exb commented Oct 14, 2023

In the first case the AM Demod at +25k offset generates this filter chain in the down channelizer:

2023-10-14 11:28:39.476 (D) AMDemod::applySettings:  m_inputFrequencyOffset:  25000  m_rfBandwidth:  20000  m_volume:  2  m_squelch:  -55  m_audioMute:  false  m_bandpassEnable:  false  m_audioDeviceName:  "System default device"  m_pll:  false  m_syncAMOperation:  0  m_streamIndex:  0  m_useReverseAPI:  false  m_reverseAPIAddress:  "127.0.0.1"  m_reverseAPIPort:  8888  m_reverseAPIDeviceIndex:  0  m_reverseAPIChannelIndex:  0  force:  false
2023-10-14 11:28:39.477 (D) AMDemodBaseband::handleMessage: MsgConfigureAMDemodBaseband
2023-10-14 11:28:39.477 (D) DownChannelizer::createFilterChain: Signal [-1200000.0, 1200000.0] (BW 2400000.0), Channel [1000.0, 49000.0], Rot 600000.0
2023-10-14 11:28:39.477 (D) DownChannelizer::createFilterChain: -> take right half (rotate by -1/4 and decimate by 2)
2023-10-14 11:28:39.477 (D) DownChannelizer::createFilterChain: Signal [0.0, 1200000.0] (BW 1200000.0), Channel [1000.0, 49000.0], Rot 300000.0
2023-10-14 11:28:39.477 (D) DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)
2023-10-14 11:28:39.477 (D) DownChannelizer::createFilterChain: Signal [0.0, 600000.0] (BW 600000.0), Channel [1000.0, 49000.0], Rot 150000.0
2023-10-14 11:28:39.477 (D) DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)
2023-10-14 11:28:39.477 (D) DownChannelizer::createFilterChain: Signal [0.0, 300000.0] (BW 300000.0), Channel [1000.0, 49000.0], Rot 75000.0
2023-10-14 11:28:39.477 (D) DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)
2023-10-14 11:28:39.477 (D) DownChannelizer::createFilterChain: Signal [0.0, 150000.0] (BW 150000.0), Channel [1000.0, 49000.0], Rot 37500.0
2023-10-14 11:28:39.477 (D) DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)
2023-10-14 11:28:39.478 (D) DownChannelizer::createFilterChain: Signal [0.0, 75000.0] (BW 75000.0), Channel [1000.0, 49000.0], Rot 18750.0
2023-10-14 11:28:39.478 (D) DownChannelizer::createFilterChain: -> complete (final BW 75000.0, frequency offset -12500.0)
2023-10-14 11:28:39.478 (D) DownChannelizer::applyChannelization done:  nb stages: 5  in (baseband): 2400000  req: 48000  out (channel): 75000  fc: -12500
2023-10-14 11:28:39.478 (D) AMDemodSink::applyChannelSettings:  channelSampleRate:  75000  channelFrequencyOffset:  -12500  m_audioSampleRate:  48000

The AM demodulator works at 48 kHz sample rate regardless of the bandwidth setting. So starting with a sample rate of 2400 kS/s the nearest possible bandwidth larger than 48 kHz is 75 kHz which is 2400/32 hence the 5 stages (2⁵). The strategy is to take the first right half for the first stage then the left half for all other stages. To see what happens at the first stage let's add a File Sink at +600 kHz with a decimation of two (I have transposed frequencies around 30 MHz with a CW signal at 30.62 MHz):
image
Notice that the carrier is extremely close to the center of my first halfband filter
Lets make one step further with the next stage. We are switching to positional mode to drive the filter chain more easily. Here the first stage takes the right part and then the next takes the left part of that right part:
image
Because the carrier was so close to the center it falls close to the edge of the FIR filter and then folds back at the left of the spectrum.
Every next stage takes the left part and carries over this ghost carrier. Let's do one more step:
image
And finally:
image

@f4exb
Copy link
Owner

f4exb commented Oct 14, 2023

You may find other cases as you move along the possible filter chains here is another one:
image
This time with this filter chain:

2023-10-14 11:54:01.101 (D) FileSinkBaseband::applySettings: m_log2Decim: 5 m_inputFrequencyOffset: 937500 m_fileRecordName:  "" m_centerFrequency:  30000000 force:  false
2023-10-14 11:54:01.101 (D) DownChannelizer::createFilterChain: Signal [-1200000.0, 1200000.0] (BW 2400000.0), Channel [900000.0, 975000.0], Rot 600000.0
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: -> take right half (rotate by -1/4 and decimate by 2)
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: Signal [0.0, 1200000.0] (BW 1200000.0), Channel [900000.0, 975000.0], Rot 300000.0
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: -> take right half (rotate by -1/4 and decimate by 2)
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: Signal [600000.0, 1200000.0] (BW 600000.0), Channel [900000.0, 975000.0], Rot 150000.0
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: -> take right half (rotate by -1/4 and decimate by 2)
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: Signal [900000.0, 1200000.0] (BW 300000.0), Channel [900000.0, 975000.0], Rot 75000.0
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: Signal [900000.0, 1050000.0] (BW 150000.0), Channel [900000.0, 975000.0], Rot 37500.0
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: Signal [900000.0, 975000.0] (BW 75000.0), Channel [900000.0, 975000.0], Rot 18750.0
2023-10-14 11:54:01.102 (D) DownChannelizer::createFilterChain: -> complete (final BW 75000.0, frequency offset 0.0)
2023-10-14 11:54:01.102 (D) DownChannelizer::applyChannelization done:  nb stages: 5  in (baseband): 2400000  req: 75000  out (channel): 75000  fc: 0
2023-10-14 11:54:01.102 (D) FileSinkSink::applyChannelSettings:  channelSampleRate:  75000  sinkSampleRate:  75000  channelFrequencyOffset:  0  centerFrequency:  30937500  force:  false

The sequence is right -> right -> right -> left -> left. At the third step a different option using the center could have been chosen that would eliminate the ghost carrier. A smarter strategy may be the solution.

At each step we may calculate all possible solutions and take the center one if possible. To be more efficient try center first then other solutions.

Edit: in fact it is not so simple. Choosing always the center option may not lead to the best final solution. I think you have to consider how your final band of interest fits in the network of possible final solutions. The best one is the one that has the band of interest the further away from its edges. If there is no solution that puts the band of interest away of a certain amount from any of the edges then the filter chain must be reduced by one if possible else we take the full bandwitdh,

@f4exb
Copy link
Owner

f4exb commented Oct 14, 2023

It relatively easy to find all possible final intervals. Let's say you want to decimate by 8. Divide the full bandwidth in sections of 1/16th then your intervals are all 1/8th intervals starting at a 1/16th mark. The difficulty is then to find which filter chain leads you to this interval but in practice if your bandwidth of interest is not too close to the decimated bandwidth there will be always a solution unless you are at the very edge of the full bandwidth in which case you can expect some degradation anyway.

If your bandwidth of interest is too close to a decimated bandwidth then choose the one just larger. Say you start from 2400 kS/s and you would like to carve out 72 kHz of bandwidth. As we have seen the solution would be to decimate by 32 to get a bandwidth just slightly higher at 75 kHz however this is dangerously close to 72 kHz (ratio to be defined here it is just 3/75 = 4%) then instead you should decimate by 16 for a decimated bandwidth of 150 kHz.

@f4exb f4exb self-assigned this Oct 14, 2023
@f4exb
Copy link
Owner

f4exb commented Oct 15, 2023

I had a second look and in fact it could be much simpler. The logic is to try to find which zone fits the desired channel at each stage from coarse to fine. Presently the order for searching is left, right and lastly center. This order simply needs to be changed to center first and then left or right.

@f4exb f4exb closed this as completed in 8ca7dbb Oct 15, 2023
@f4exb
Copy link
Owner

f4exb commented Oct 15, 2023

The above strategy at least fixes all the issues described here.

@srcejon
Copy link
Collaborator Author

srcejon commented Oct 16, 2023

Thanks Edouard. Earlier test case looks fixed. Here's another example with the updated code though:

image

2023-10-16 11:12:03.302 (D) FileSinkBaseband::applySettings: m_log2Decim: 4 m_inputFrequencyOffset: -525000 m_fileRecordName: "" m_centerFrequency: 100000000 force: false
2023-10-16 11:12:03.302 (D) DownChannelizer::createFilterChain: Signal [-1200000.0, 1200000.0] (BW 2400000.0), Channel [-600000.0, -450000.0], Rot 600000.0
2023-10-16 11:12:03.302 (D) DownChannelizer::createFilterChain: -> take center half (decimate by 2)
2023-10-16 11:12:03.302 (D) DownChannelizer::createFilterChain: Signal [-600000.0, 600000.0] (BW 1200000.0), Channel [-600000.0, -450000.0], Rot 300000.0
2023-10-16 11:12:03.302 (D) DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)
2023-10-16 11:12:03.302 (D) DownChannelizer::createFilterChain: Signal [-600000.0, 0.0] (BW 600000.0), Channel [-600000.0, -450000.0], Rot 150000.0
2023-10-16 11:12:03.302 (D) DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)
2023-10-16 11:12:03.302 (D) DownChannelizer::createFilterChain: Signal [-600000.0, -300000.0] (BW 300000.0), Channel [-600000.0, -450000.0], Rot 75000.0
2023-10-16 11:12:03.303 (D) DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)
2023-10-16 11:12:03.303 (D) DownChannelizer::createFilterChain: Signal [-600000.0, -450000.0] (BW 150000.0), Channel [-600000.0, -450000.0], Rot 37500.0
2023-10-16 11:12:03.303 (D) DownChannelizer::createFilterChain: -> complete (final BW 150000.0, frequency offset 0.0)
2023-10-16 11:12:03.303 (D) DownChannelizer::applyChannelization done: nb stages: 4 in (baseband): 2400000 req: 150000 out (channel): 150000 fc: 0
2023-10-16 11:12:03.303 (D) FileSinkSink::applyChannelSettings: channelSampleRate: 150000 sinkSampleRate: 150000 channelFrequencyOffset: 0 centerFrequency: 99475000 force: false
2023-10-16 11:12:03.303 (D) NCOF::setFreq: freq: 0.000000 sr: 150000.000000 m_phaseIncrement: 0.000000

Offset of +230k looks like it may have a problem as well.

@f4exb f4exb reopened this Oct 16, 2023
@f4exb
Copy link
Owner

f4exb commented Oct 16, 2023

Yes... my first solution is too rudimentary. By choosing the "center" option first at the first stage the band of interest appears at the very left of the first centered filter and the big signal that appears at the right of this filter folds back to the left. Then like in the first case this is carried over by all other filters. So one should rather consider the position of the final band of interest inside the filter and choose the most centered one i.e. the one for which any of the band limits are further away from the edges of the filter. And if there is no solution that keeps the limits far enough of the filter edges then you should stop the recursive process and leave the chain as set at the previous step.

@f4exb f4exb closed this as completed in 9abd62f Oct 17, 2023
@f4exb
Copy link
Owner

f4exb commented Oct 17, 2023

This last fix should work better.

@srcejon
Copy link
Collaborator Author

srcejon commented Oct 17, 2023

Looks good as far as I can see, thanks!

@f4exb
Copy link
Owner

f4exb commented Oct 17, 2023

Only some compilation glitch on Windows and Mac that I did not notice while compiling on Linux

@f4exb
Copy link
Owner

f4exb commented Oct 17, 2023

@srcejon I see you already noticed and fixed it #1849

@srcejon
Copy link
Collaborator Author

srcejon commented Oct 17, 2023

A note to myself:

Even when working optimally, the halfband filters can have some aliasing. E.g:

image

Meaning the top and bottom ~12% of the channel shouldn't be used and needs to be filtered. This filtering is typically done by the m_interpolator in the demod.

However, not thinking of this at the time, I tried to optimise out that filtering out in a few of the higher sample rate demods, but:

  • For ADS-B, it's probably OK, as unlikely to have a strong adjacent signal.
  • DAB should probably be OK, as it's only optimized out when sample rate is 2048k and it's an OFDM signal 1537k wide.

@f4exb
Copy link
Owner

f4exb commented Oct 17, 2023

There is a 10% guard on each side for the channel that is carved out that should keep you outside unwanted signals (maybe 12% would be even better). However this maybe suboptimal because what actually counts is the final bandwidth of the demodulator that is filtered with the "interpolator" as you noticed. In practice this will mean one less filter in the chain than it used to be. I may look at optimizing it next.

Edit: my initial assumption was wrong. Tried with a AM modulator that requires 48 kHz bandwidth and it is this bandwidth that is used as the "channel" bandwidth and not the bandwidth decimated by powers of two which in this case is 75 kHz. Maybe just take 12.5% guard instead of 10% (taking bandwidth divided by 8 instead of 10).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants