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

Refactor squelch #225

Merged
merged 4 commits into from
Feb 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,7 @@ static struct freq_t *mk_freqlist( int n )
fl[i].frequency = 0;
fl[i].label = NULL;
fl[i].agcavgfast = 0.5f;
fl[i].agcavgslow = 0.5f;
fl[i].filter_avg = 0.5f;
fl[i].agcmin = 100.0f;
fl[i].agclow = 0;
fl[i].sqlevel = -1;
fl[i].squelch = Squelch();
fl[i].active_counter = 0;
}
return fl;
Expand Down Expand Up @@ -258,7 +254,6 @@ static int parse_channels(libconfig::Setting &chans, device_t *dev, int i) {
channel->wavein[k] = 20;
channel->waveout[k] = 0.5;
}
channel->agcsq = 1;
channel->axcindicate = NO_SIGNAL;
channel->modulation = MOD_AM;
channel->mode = MM_MONO;
Expand Down Expand Up @@ -331,7 +326,7 @@ static int parse_channels(libconfig::Setting &chans, device_t *dev, int i) {
if(libconfig::Setting::TypeList == chans[j]["squelch"].getType()) {
// New-style array of per-frequency squelch settings
for(int f = 0; f<channel->freq_count; f++) {
channel->freqlist[f].sqlevel = (int)chans[j]["squelch"][f];
channel->freqlist[f].squelch = Squelch((int)chans[j]["squelch"][f]);
}
// NB: no value check; -1 allows auto-squelch for
// some frequencies and not others.
Expand All @@ -343,7 +338,7 @@ static int parse_channels(libconfig::Setting &chans, device_t *dev, int i) {
error();
}
for(int f = 0; f<channel->freq_count; f++) {
channel->freqlist[f].sqlevel = sqlevel;
channel->freqlist[f].squelch = Squelch(sqlevel);
}
} else {
cerr<<"Invalid value for squelch (should be int or list - use parentheses)\n";
Expand Down
2 changes: 1 addition & 1 deletion input-file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ int file_init(input_t * const input) {

dev_data->input_file = fopen(dev_data->filepath, "rb");
if(!dev_data->input_file) {
cerr << "File input failed to open '" << dev_data->filepath << "'\n";
cerr << "File input failed to open '" << dev_data->filepath << "' - " << strerror(errno) << endl;
error();
}

Expand Down
8 changes: 5 additions & 3 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ SUBDIRS = hello_fft
CLEANDIRS = $(SUBDIRS:%=clean-%)

BIN = rtl_airband
OBJ = rtl_airband.o input-common.o input-helpers.o input-file.o output.o config.o util.o mixer.o
OBJ = rtl_airband.o input-common.o input-helpers.o input-file.o output.o config.o util.o mixer.o squelch.o
FFT = hello_fft/hello_fft.a

.PHONY: all clean install help $(SUBDIRS) $(CLEANDIRS)
Expand Down Expand Up @@ -129,7 +129,7 @@ help:

$(FFT): hello_fft ;

config.o: rtl_airband.h input-common.h
config.o: rtl_airband.h input-common.h squelch.h

input-common.o: input-common.h

Expand All @@ -145,14 +145,16 @@ input-file.o: rtl_airband.h input-common.h input-helpers.h input-file.h

mixer.o: rtl_airband.h

rtl_airband.o: rtl_airband.h input-common.h
rtl_airband.o: rtl_airband.h input-common.h squelch.h

output.o: rtl_airband.h input-common.h

pulse.o: rtl_airband.h

util.o: rtl_airband.h

squelch.o : squelch.h

$(SUBDIRS):
$(MAKE) -C $@

Expand Down
2 changes: 1 addition & 1 deletion output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ static void output_channel_noise_levels(FILE *f) {
channel_t* channel = devices[i].channels + j;
for (int k = 0; k < channel->freq_count; k++) {
print_channel_metric(f, "channel_noise_level", channel->freqlist[k].frequency, channel->freqlist[k].label);
fprintf(f, "\t%.3f\n", channel->freqlist[k].agcmin);
fprintf(f, "\t%.3f\n", channel->freqlist[k].squelch.noise_floor());
}
}
}
Expand Down
156 changes: 72 additions & 84 deletions rtl_airband.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#include <lame/lame.h>
#include "input-common.h"
#include "rtl_airband.h"
#include "squelch.h"

#ifdef WITH_PROFILING
#include "gperftools/profiler.h"
Expand Down Expand Up @@ -533,107 +534,75 @@ void *demodulate(void *params) {
AFC afc(dev, i);
channel_t* channel = dev->channels + i;
freq_t *fparms = channel->freqlist + channel->freq_idx;

// TODO: why not just do this in the loop below?
#if defined (__arm__) || defined (__aarch64__)
float agcmin2 = fparms->agcmin * 4.5f;
float agcmin2 = fparms->squelch.noise_floor() * 4.5f;
for (int j = 0; j < WAVE_BATCH + AGC_EXTRA; j++) {
channel->waveref[j] = min(channel->wavein[j], agcmin2);
}
#else
__m128 agccap = _mm_set1_ps(fparms->agcmin * 4.5f);
__m128 agccap = _mm_set1_ps(fparms->squelch.noise_floor() * 4.5f);
for (int j = 0; j < WAVE_BATCH + AGC_EXTRA; j += 4) {
__m128 t = _mm_loadu_ps(channel->wavein + j);
_mm_storeu_ps(channel->waveref + j, _mm_min_ps(t, agccap));
}
#endif

for (int j = AGC_EXTRA; j < WAVE_BATCH + AGC_EXTRA; j++) {
// auto noise floor
if (fparms->sqlevel < 0 && j % 16 == 0) {
fparms->agcmin = fparms->agcmin * 0.97f + min(fparms->agcavgslow, fparms->agcmin) * 0.03f + 0.0001f;
}

// average power
fparms->agcavgslow = fparms->agcavgslow * 0.99f + channel->waveref[j] * 0.01f;

float sqlevel = fparms->sqlevel >= 0 ? (float)fparms->sqlevel : 3.0f * fparms->agcmin;
if (channel->agcsq > 0) {
channel->agcsq = max(channel->agcsq - 1, 1);
if (channel->agcsq == 1 && fparms->agcavgslow > sqlevel) {
channel->agcsq = -AGC_EXTRA * 2;
channel->axcindicate = SIGNAL;
if(channel->modulation == MOD_AM) {
// fade in
for (int k = j - AGC_EXTRA; k < j; k++) {
if (channel->wavein[k] > sqlevel) {
fparms->agcavgfast = fparms->agcavgfast * 0.9f + channel->wavein[k] * 0.1f;
}
}
}
}
} else {
if (channel->wavein[j] > sqlevel) {
if(channel->modulation == MOD_AM)
fparms->agcavgfast = fparms->agcavgfast * 0.995f + channel->wavein[j] * 0.005f;
fparms->agclow = 0;
} else {
fparms->agclow++;
}
channel->agcsq = min(channel->agcsq + 1, -1);
if ((channel->agcsq == -1 && fparms->agcavgslow < sqlevel) || fparms->agclow == AGC_EXTRA - 12) {
channel->agcsq = AGC_EXTRA * 2;
channel->axcindicate = NO_SIGNAL;
if(channel->modulation == MOD_AM) {
// fade out
for (int k = j - AGC_EXTRA + 1; k < j; k++) {
channel->waveout[k] = channel->waveout[k - 1] * 0.94f;
}
}
}
}
float &real = channel->iq_in[2*(j - AGC_EXTRA)];
float &imag = channel->iq_in[2*(j - AGC_EXTRA)+1];

fparms->squelch.process_reference_sample(channel->waveref[j]);

bool signal_filtered = false;
if(channel->agcsq < 0 && channel->needs_raw_iq) {
// If squelch is open / opening and using I/Q, then cleanup the signal and possibly update squelch.
if (fparms->squelch.should_filter_sample() && channel->needs_raw_iq) {
// remove phase rotation introduced by FFT sliding window
float swf, cwf, re, im;
float swf, cwf, re_tmp, im_tmp;
sincosf_lut(channel->dm_phi, &swf, &cwf);
multiply(channel->iq_in[2*(j - AGC_EXTRA)], channel->iq_in[2*(j - AGC_EXTRA)+1], cwf, -swf, &re, &im);
multiply(real, imag, cwf, -swf, &re_tmp, &im_tmp);
channel->dm_phi += channel->dm_dphi;
channel->dm_phi &= 0xffffff;

// apply lowpass filter, will be a no-op if not configured
fparms->lowpass_filter.apply(re, im);
fparms->lowpass_filter.apply(re_tmp, im_tmp);

// update I/Q and wave
channel->iq_in[2*(j - AGC_EXTRA)] = re;
channel->iq_in[2*(j - AGC_EXTRA)+1] = im;
channel->wavein[j] = sqrt(re * re + im * im);

if(fparms->lowpass_filter.enabled()) {
fparms->filter_avg = fparms->filter_avg * 0.999f + channel->wavein[j] * 0.001f;

if (fparms->filter_avg < sqlevel) {
signal_filtered = true;
channel->axcindicate = NO_SIGNAL;
} else {
channel->axcindicate = SIGNAL;
}
real = re_tmp;
imag = im_tmp;
channel->wavein[j] = sqrt(real * real + imag * imag);

// update squelch post-cleanup
if (fparms->lowpass_filter.enabled()) {
fparms->squelch.process_filtered_sample(channel->wavein[j]);
}
}
if(channel->agcsq != -1 || signal_filtered) {
channel->waveout[j] = 0;
if(channel->has_iq_outputs) {
channel->iq_out[2*(j - AGC_EXTRA)] = 0;
channel->iq_out[2*(j - AGC_EXTRA)+1] = 0;

if(channel->modulation == MOD_AM) {
// if squelch is just opening then fade in, or if just closing fade out
if (fparms->squelch.should_fade_in()) {
for (int k = j - AGC_EXTRA; k < j; k++) {
if (channel->wavein[k] >= fparms->squelch.squelch_level()) {
fparms->agcavgfast = fparms->agcavgfast * 0.9f + channel->wavein[k] * 0.1f;
}
}
} else if (fparms->squelch.should_fade_out()) {
for (int k = j - AGC_EXTRA + 1; k < j; k++) {
channel->waveout[k] = channel->waveout[k - 1] * 0.94f;
}
}
} else {
const float &re = channel->iq_in[2*(j - AGC_EXTRA)];
const float &im = channel->iq_in[2*(j - AGC_EXTRA)+1];

if(channel->has_iq_outputs) {
channel->iq_out[2*(j - AGC_EXTRA)] = re;
channel->iq_out[2*(j - AGC_EXTRA)+1] = im;
if( (fparms->squelch.get_state() == Squelch::OPEN || fparms->squelch.get_state() == Squelch::OPENING) && channel->wavein[j] > fparms->squelch.squelch_level() ) {
// TODO: Possible Improvement - re-visit this, should it move to is_open()?
fparms->agcavgfast = fparms->agcavgfast * 0.995f + channel->wavein[j] * 0.005f;
}
}

// If squelch is still open then do modulation-specific processing
if (fparms->squelch.is_open()) {
if(channel->modulation == MOD_AM) {

channel->waveout[j] = (channel->wavein[j - AGC_EXTRA] - fparms->agcavgfast) / (fparms->agcavgfast * 1.5f);
if (abs(channel->waveout[j]) > 0.8f) {
channel->waveout[j] *= 0.85f;
Expand All @@ -642,23 +611,40 @@ void *demodulate(void *params) {
}
#ifdef NFM
else if(channel->modulation == MOD_NFM) {
// FM demod
// FM demod
if(fm_demod == FM_FAST_ATAN2) {
channel->waveout[j] = polar_disc_fast(re, im, channel->pr, channel->pj);
channel->waveout[j] = polar_disc_fast(real, imag, channel->pr, channel->pj);
} else if(fm_demod == FM_QUADRI_DEMOD) {
channel->waveout[j] = fm_quadri_demod(re, im, channel->pr, channel->pj);
channel->waveout[j] = fm_quadri_demod(real, imag, channel->pr, channel->pj);
}
channel->pr = re;
channel->pj = im;
// de-emphasis IIR + DC blocking
channel->pr = real;
channel->pj = imag;

// de-emphasis IIR + DC blocking
fparms->agcavgfast = fparms->agcavgfast * 0.995f + channel->waveout[j] * 0.005f;
channel->waveout[j] -= fparms->agcavgfast;
channel->waveout[j] = channel->waveout[j] * (1.0f - channel->alpha) + channel->waveout[j-1] * channel->alpha;
}
#endif // NFM

// apply the notch filter. If no filter configured, this will no-op
// apply the notch filter, will be a no-op if not configured
fparms->notch_filter.apply(channel->waveout[j]);

channel->axcindicate = SIGNAL;
if(channel->has_iq_outputs) {
channel->iq_out[2*(j - AGC_EXTRA)] = real;
channel->iq_out[2*(j - AGC_EXTRA)+1] = imag;
}

// Squelch is closed
} else {
channel->waveout[j] = 0;
// TODO: Possible Improvement - set channel->axcindicate to NO_SIGNAL at start of loop and dont clear here to allow output() to pick up the end of a transmission
channel->axcindicate = NO_SIGNAL;
if(channel->has_iq_outputs) {
channel->iq_out[2*(j - AGC_EXTRA)] = 0;
channel->iq_out[2*(j - AGC_EXTRA)+1] = 0;
}
}
}
memmove(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * sizeof(float));
Expand All @@ -675,16 +661,18 @@ void *demodulate(void *params) {
if (tui) {
if(dev->mode == R_SCAN) {
GOTOXY(0, device_num * 17 + dev->row + 3);
// TODO: change to dB
printf("%4.0f/%3.0f%c %7.3f ",
fparms->agcavgslow,
(fparms->sqlevel >= 0 ? fparms->sqlevel : fparms->agcmin),
fparms->squelch.power_level(),
fparms->squelch.squelch_level(),
channel->axcindicate,
(dev->channels[0].freqlist[channel->freq_idx].frequency / 1000000.0));
} else {
GOTOXY(i*10, device_num * 17 + dev->row + 3);
// TODO: change to dB
printf("%4.0f/%3.0f%c ",
fparms->agcavgslow,
(fparms->sqlevel >= 0 ? fparms->sqlevel : fparms->agcmin),
fparms->squelch.power_level(),
fparms->squelch.squelch_level(),
channel->axcindicate);
}
fflush(stdout);
Expand Down
8 changes: 2 additions & 6 deletions rtl_airband.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include <pulse/stream.h>
#endif
#include "input-common.h" // input_t
#include "squelch.h"

#ifndef RTL_AIRBAND_VERSION
#define RTL_AIRBAND_VERSION "3.1.0"
Expand Down Expand Up @@ -240,11 +241,7 @@ struct freq_t {
int frequency; // scan frequency
char *label; // frequency label
float agcavgfast; // average power, for AGC
float agcavgslow; // average power, for squelch level detection
float filter_avg; // average power, for post-filter squelch level detection
float agcmin; // noise level
int sqlevel; // manually configured squelch level
int agclow; // low level sample count
Squelch squelch;
size_t active_counter; // count of loops where channel has signal
NotchFilter notch_filter; // notch filter - good to remove CTCSS tones
LowpassFilter lowpass_filter; // lowpass filter, applied to I/Q after derotation, set at bandwidth/2 to remove out of band noise
Expand All @@ -264,7 +261,6 @@ struct channel_t {
uint32_t dm_dphi, dm_phi; // derotation frequency and current phase value
enum modulations modulation;
enum mix_modes mode; // mono or stereo
int agcsq; // squelch status, negative: signal, positive: suppressed
status axcindicate;
unsigned char afc; //0 - AFC disabled; 1 - minimal AFC; 2 - more aggressive AFC and so on to 255
struct freq_t *freqlist;
Expand Down
Loading