2021-06-29 12:11:13 +00:00
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
//
// adaptive.c: adaptive gain control
//
// Copyright (c) 2021 FlightAware, LLC
//
// This file is free software: you may copy, redistribute and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 2 of the License, or (at your
// option) any later version.
//
// This file is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
# include "dump1090.h"
# include "adaptive.h"
//
// gain limits
//
static int adaptive_gain_min ;
static int adaptive_gain_max ;
// gain steps relative to current gain
static float adaptive_gain_up_db ;
static float adaptive_gain_down_db ;
//
// block handling
//
2021-07-15 08:44:26 +00:00
// 1 block = approx 1 second of samples. Control updates are done at the end of each block only.
// Each block is made up of an integer number of subblocks (currently 20)
//
// 1 subblock = approx 50ms of samples. Duty cycle decisions are made at the subblock level;
// either the whole subblock is processed, or the whole subblock is skipped.
// Each subblock is made up of an integer number of windows (currently 1250)
//
// 1 window = approx 40us of samples. Burst measurements are made by counting samples within each window.
//
// All three levels are aligned, i.e. every block boundary is also a subblock boundary;
// every subblock boundary is also a window boundary.
2021-06-29 12:11:13 +00:00
2021-07-15 08:44:26 +00:00
2021-07-21 12:51:57 +00:00
static const unsigned adaptive_subblocks_per_block = 20 ; // subblocks per block
2021-07-15 08:44:26 +00:00
static unsigned adaptive_subblocks_remaining ; // subblocks remaining in the current block
// Duty cycle is expressed as N/D
// where N = adaptive_subblbock_dutycycle_N = adaptive_subblocks_per_block * Modes.adaptive_duty_cycle
// and D = adaptive_subblocks_dutycycle_D = adaptive_subblocks_per_block
//
// i.e. within each block, there are exactly N active subblocks out of D total subblocks
//
// The active subblocks are distributed evenly across the block by increasing a counter by N on each
// subblock, modulo D, and marking the subblock as active each time the counter rolls over.
static unsigned adaptive_subblock_dutycycle_N ; // subblock duty cycle numerator N
2021-07-21 12:51:57 +00:00
// stretch gcc doesn't like this as a separate const
# define adaptive_subblock_dutycycle_D adaptive_subblocks_per_block
2021-07-15 08:44:26 +00:00
static unsigned adaptive_subblock_dutycycle_counter ; // subblock duty cycle counter (modulo D)
static bool adaptive_subblock_active ; // is the current subblock active i.e. samples should be processed, not skipped?
static unsigned adaptive_samples_per_subblock ; // samples per subblock
static unsigned adaptive_subblock_samples_remaining ; // samples remaining in the current subblock
static unsigned adaptive_samples_per_window ; // samples per window
2021-06-29 12:11:13 +00:00
void adaptive_init ( ) ;
void adaptive_update ( uint16_t * buf , unsigned length , struct modesMessage * decoded ) ;
2021-07-15 08:44:26 +00:00
static void adaptive_update_subblock ( uint16_t * buf , unsigned length , struct modesMessage * decoded ) ;
2021-06-29 12:11:13 +00:00
static void adaptive_end_of_block ( ) ;
2021-07-07 12:57:34 +00:00
static void adaptive_control_update ( ) ;
2021-06-29 12:11:13 +00:00
//
// burst handling
//
2021-07-07 12:57:34 +00:00
static unsigned adaptive_burst_window_remaining ; // samples remaining in the current burst window
static unsigned adaptive_burst_window_counter ; // loud samples seen in current burst window
static unsigned adaptive_burst_runlength ; // consecutive loud burst windows seen
static unsigned adaptive_burst_block_loud_undecoded ; // loud undecoded bursts seen in this block so far
static unsigned adaptive_burst_block_loud_decoded ; // loud decoded messages seen in this block so far
static double adaptive_burst_loud_undecoded_smoothed ; // smoothed rate of loud misdecodes per block
static double adaptive_burst_loud_decoded_smoothed ; // smoothed rate of loud successful decodes per block
static unsigned adaptive_burst_change_timer ; // countdown inhibiting control after changing gain
static double adaptive_burst_loud_threshold ; // current signal level threshold for a "loud decode"
static unsigned adaptive_burst_loud_blocks = 0 ; // consecutive blocks with loud rate
static unsigned adaptive_burst_quiet_blocks = 0 ; // consecutive blocks with quiet rate
2021-06-29 12:11:13 +00:00
static void adaptive_burst_update ( uint16_t * buf , unsigned length ) ;
static void adaptive_burst_skip ( unsigned length ) ;
static unsigned adaptive_burst_count_samples ( uint16_t * buf , unsigned n ) ;
static void adaptive_burst_scan_windows ( uint16_t * buf , unsigned windows ) ;
static void adaptive_burst_end_of_window ( unsigned counter ) ;
static void adaptive_burst_end_of_block ( ) ;
//
// noise floor measurement (adaptive dynamic range)
//
2021-07-07 12:57:34 +00:00
static unsigned * adaptive_range_radix ; // radix-sort buckets for current block
static unsigned adaptive_range_radix_counter ; // sum of all radix-sort buckets (= number of samples sorted)
static double adaptive_range_smoothed ; // smoothed noise floor estimate, dBFS
2021-12-07 09:29:50 +00:00
static enum { RANGE_SCAN_IDLE , RANGE_SCAN_UP , RANGE_SCAN_DOWN , RANGE_RESCAN_UP , RANGE_RESCAN_DOWN } adaptive_range_state = RANGE_SCAN_UP ;
2021-07-07 12:57:34 +00:00
static unsigned adaptive_range_change_timer ; // countdown inhibiting control after changing gain
static unsigned adaptive_range_rescan_timer ; // countdown to next upwards gain reprobe
static int adaptive_range_gain_limit ; // probed maximum gain step with acceptable dynamic range
2021-06-29 12:11:13 +00:00
static void adaptive_range_update ( uint16_t * buf , unsigned length ) ;
static void adaptive_range_end_of_block ( ) ;
2021-07-07 12:57:34 +00:00
// Try to change the SDR gain to 'step' and tell the user about it,
// with 'why' as the reason to show. Return true if the gain actually changed.
2021-06-29 12:11:13 +00:00
static bool adaptive_set_gain ( int step , const char * why )
{
if ( step < adaptive_gain_min )
step = adaptive_gain_min ;
if ( step > adaptive_gain_max )
step = adaptive_gain_max ;
int current_gain = sdrGetGain ( ) ;
if ( current_gain = = step )
return false ;
fprintf ( stderr , " adaptive: changing gain from %.1fdB (step %d) to %.1fdB (step %d) because: %s \n " ,
sdrGetGainDb ( current_gain ) , current_gain , sdrGetGainDb ( step ) , step , why ) ;
int new_gain = sdrSetGain ( step ) ;
2021-07-07 12:57:34 +00:00
bool changed = ( current_gain ! = new_gain ) ;
if ( changed )
+ + Modes . stats_current . adaptive_gain_changes ;
return changed ;
2021-06-29 12:11:13 +00:00
}
2021-07-07 12:57:34 +00:00
// Update internal state to reflect a gain change
// (usually after adaptive_set_gain returns true, but also called during init)
2021-06-29 12:11:13 +00:00
static void adaptive_gain_changed ( )
{
int new_gain = sdrGetGain ( ) ;
adaptive_gain_up_db = sdrGetGainDb ( new_gain + 1 ) - sdrGetGainDb ( new_gain ) ;
adaptive_gain_down_db = sdrGetGainDb ( new_gain ) - sdrGetGainDb ( new_gain - 1 ) ;
double loud_threshold_dbfs = 0 - adaptive_gain_up_db - 3.0 ;
adaptive_burst_loud_threshold = pow ( 10 , loud_threshold_dbfs / 10.0 ) ;
2021-07-07 12:57:34 +00:00
adaptive_range_change_timer = Modes . adaptive_range_change_delay ;
adaptive_burst_change_timer = Modes . adaptive_burst_change_delay ;
adaptive_burst_loud_blocks = 0 ;
adaptive_burst_quiet_blocks = 0 ;
2021-06-29 12:11:13 +00:00
}
2021-07-07 12:57:34 +00:00
// External init entry point
2021-06-29 12:11:13 +00:00
void adaptive_init ( )
{
int maxgain = sdrGetMaxGain ( ) ;
// If the SDR doesn't support gain control, disable ourselves
if ( maxgain < 0 ) {
if ( Modes . adaptive_burst_control | | Modes . adaptive_range_control ) {
fprintf ( stderr , " warning: adaptive gain control requested, but SDR gain control not available, ignored. \n " ) ;
}
Modes . adaptive_burst_control = false ;
Modes . adaptive_range_control = false ;
}
// If we're disabled, do nothing
if ( ! Modes . adaptive_burst_control & & ! Modes . adaptive_range_control )
return ;
2021-07-15 08:44:26 +00:00
// Set up window, subblock, and block sizes
2021-06-29 12:11:13 +00:00
// Look for 40us bursts
2021-07-15 08:44:26 +00:00
adaptive_samples_per_window = Modes . sample_rate / 25000 ;
2021-06-29 12:11:13 +00:00
2021-07-15 08:44:26 +00:00
// Use ~50ms subblocks; ensure it's an exact multiple of window size
adaptive_samples_per_subblock = adaptive_samples_per_window * 1250 ;
adaptive_subblocks_remaining = adaptive_subblocks_per_block ;
adaptive_subblock_samples_remaining = adaptive_samples_per_subblock ;
adaptive_subblock_active = false ;
float N = roundf ( adaptive_subblock_dutycycle_D * Modes . adaptive_duty_cycle ) ;
2021-07-15 08:51:03 +00:00
if ( N < = 0 )
N = 1 ;
if ( N > adaptive_subblock_dutycycle_D )
2021-07-15 08:44:26 +00:00
N = adaptive_subblock_dutycycle_D ;
2021-07-15 08:51:03 +00:00
fprintf ( stderr , " adaptive: using %.0f%% duty cycle \n " , 100.0 * N / adaptive_subblock_dutycycle_D ) ;
2021-07-15 08:44:26 +00:00
adaptive_subblock_dutycycle_N = ( unsigned ) N ;
adaptive_burst_window_remaining = adaptive_samples_per_window ;
adaptive_burst_window_counter = 0 ;
2021-06-29 12:11:13 +00:00
adaptive_range_radix = calloc ( sizeof ( unsigned ) , 65536 ) ;
2021-12-07 09:29:50 +00:00
adaptive_range_state = RANGE_RESCAN_UP ;
2021-06-29 12:11:13 +00:00
// select and enforce gain limits
for ( adaptive_gain_min = 0 ; adaptive_gain_min < maxgain ; + + adaptive_gain_min ) {
if ( sdrGetGainDb ( adaptive_gain_min ) > = Modes . adaptive_min_gain_db )
break ;
}
for ( adaptive_gain_max = maxgain ; adaptive_gain_max > adaptive_gain_min ; - - adaptive_gain_max ) {
if ( sdrGetGainDb ( adaptive_gain_max ) < = Modes . adaptive_max_gain_db )
break ;
}
fprintf ( stderr , " adaptive: enabled adaptive gain control with gain limits %.1fdB (step %d) .. %.1fdB (step %d) \n " ,
sdrGetGainDb ( adaptive_gain_min ) , adaptive_gain_min , sdrGetGainDb ( adaptive_gain_max ) , adaptive_gain_max ) ;
if ( Modes . adaptive_range_control )
fprintf ( stderr , " adaptive: enabled dynamic range control, target dynamic range %.1fdB \n " , Modes . adaptive_range_target ) ;
if ( Modes . adaptive_burst_control )
fprintf ( stderr , " adaptive: enabled burst control \n " ) ;
adaptive_set_gain ( sdrGetGain ( ) , " constraining gain to adaptive gain limits " ) ;
adaptive_gain_changed ( ) ;
2021-07-07 12:57:34 +00:00
adaptive_range_gain_limit = sdrGetGain ( ) ;
2021-06-29 12:11:13 +00:00
}
// Feed some samples into the adaptive system. Any number of samples might be passed in.
void adaptive_update ( uint16_t * buf , unsigned length , struct modesMessage * decoded )
{
if ( ! Modes . adaptive_burst_control & & ! Modes . adaptive_range_control )
return ;
2021-07-15 08:44:26 +00:00
// process complete subblocks
while ( length > = adaptive_subblock_samples_remaining ) {
if ( adaptive_subblock_active )
adaptive_update_subblock ( buf , adaptive_subblock_samples_remaining , decoded ) ;
buf + = adaptive_subblock_samples_remaining ;
length - = adaptive_subblock_samples_remaining ;
adaptive_subblock_samples_remaining = adaptive_samples_per_subblock ;
adaptive_subblock_dutycycle_counter + = adaptive_subblock_dutycycle_N ;
if ( adaptive_subblock_dutycycle_counter > = adaptive_subblock_dutycycle_D ) {
adaptive_subblock_dutycycle_counter - = adaptive_subblock_dutycycle_D ;
adaptive_subblock_active = true ;
} else {
adaptive_subblock_active = false ;
// fake a quiet window to reset any existing run
adaptive_burst_end_of_window ( 0 ) ;
}
if ( ! - - adaptive_subblocks_remaining ) {
// Block completed, do a control update
adaptive_subblocks_remaining = adaptive_subblocks_per_block ;
adaptive_end_of_block ( ) ;
}
2021-06-29 12:11:13 +00:00
}
2021-07-15 08:44:26 +00:00
// process final samples that don't complete a subblock
2021-06-29 12:11:13 +00:00
if ( length > 0 ) {
2021-07-15 08:44:26 +00:00
if ( adaptive_subblock_active )
adaptive_update_subblock ( buf , length , decoded ) ;
adaptive_subblock_samples_remaining - = length ;
2021-06-29 12:11:13 +00:00
}
}
2021-07-15 08:44:26 +00:00
// Feed some samples into the adaptive system. The samples are guaranteed to not cross a subblock boundary.
// The samples should be processsed (i.e. duty cycle is in the active part)
static void adaptive_update_subblock ( uint16_t * buf , unsigned length , struct modesMessage * decoded )
2021-06-29 12:11:13 +00:00
{
if ( decoded ) {
if ( /* decoded->msgbits == 112 && */ decoded - > signalLevel > = adaptive_burst_loud_threshold )
2021-07-07 12:57:34 +00:00
+ + adaptive_burst_block_loud_decoded ;
2021-06-29 12:11:13 +00:00
adaptive_burst_skip ( length ) ;
} else {
adaptive_burst_update ( buf , length ) ;
adaptive_range_update ( buf , length ) ;
}
}
// Burst measurement: ignore the next 'length' samples (they are a successfully decoded message)
static void adaptive_burst_skip ( unsigned length )
{
if ( ! Modes . adaptive_burst_control )
return ;
// first window
if ( length < adaptive_burst_window_remaining ) {
// partial fill
adaptive_burst_window_remaining - = length ;
return ;
}
// skip remainder of first window, dispatch it
adaptive_burst_end_of_window ( adaptive_burst_window_counter ) ;
length - = adaptive_burst_window_remaining ;
// skip remaining windows, dispatch them
2021-07-15 08:44:26 +00:00
unsigned windows = length / adaptive_samples_per_window ;
unsigned samples = windows * adaptive_samples_per_window ;
2021-06-29 12:11:13 +00:00
while ( windows - - )
adaptive_burst_end_of_window ( 0 ) ;
length - = samples ;
// final partial window
adaptive_burst_window_counter = 0 ;
2021-07-15 08:44:26 +00:00
adaptive_burst_window_remaining = adaptive_samples_per_window - length ;
2021-06-29 12:11:13 +00:00
}
// Burst measurement: process 'length' samples from 'buf', look for loud bursts;
// the samples might cross burst window boundaries;
// the samples will not cross a block boundary.
static void adaptive_burst_update ( uint16_t * buf , unsigned length )
{
if ( ! Modes . adaptive_burst_control )
return ;
// first window
if ( length < adaptive_burst_window_remaining ) {
// partial fill
adaptive_burst_window_counter + = adaptive_burst_count_samples ( buf , length ) ;
adaptive_burst_window_remaining - = length ;
return ;
}
// complete fill of first partial window
unsigned n = adaptive_burst_window_remaining ;
unsigned counter = adaptive_burst_window_counter + adaptive_burst_count_samples ( buf , n ) ;
adaptive_burst_end_of_window ( counter ) ;
buf + = n ;
length - = n ;
// remaining windows
2021-07-15 08:44:26 +00:00
unsigned windows = length / adaptive_samples_per_window ;
unsigned samples = windows * adaptive_samples_per_window ;
2021-06-29 12:11:13 +00:00
adaptive_burst_scan_windows ( buf , windows ) ;
buf + = samples ;
length - = samples ;
// final partial window
adaptive_burst_window_counter = adaptive_burst_count_samples ( buf , length ) ;
2021-07-15 08:44:26 +00:00
adaptive_burst_window_remaining = adaptive_samples_per_window - length ;
2021-06-29 12:11:13 +00:00
}
// Burst measurement: process 'windows' complete burst windows starting at 'buf';
// 'buf' is aligned to the start of a burst window
static void adaptive_burst_scan_windows ( uint16_t * buf , unsigned windows )
{
while ( windows - - ) {
2021-07-15 08:44:26 +00:00
unsigned counter = adaptive_burst_count_samples ( buf , adaptive_samples_per_window ) ;
buf + = adaptive_samples_per_window ;
2021-06-29 12:11:13 +00:00
adaptive_burst_end_of_window ( counter ) ;
}
}
// Burst measurement: process 'n' samples from 'buf', look for loud samples;
// the samples are guaranteed not to cross window boundaries;
// return the number of loud samples seen
static inline unsigned adaptive_burst_count_samples ( uint16_t * buf , unsigned n )
{
2021-07-08 10:53:02 +00:00
unsigned counter ;
starch_count_above_u16 ( buf , n , 46395 /* -3dBFS */ , & counter ) ;
2021-06-29 12:11:13 +00:00
return counter ;
}
// Burst measurement: we reached the end of a burst window with 'counter'
// loud samples seen, handle that window.
static void adaptive_burst_end_of_window ( unsigned counter )
{
2021-07-15 08:44:26 +00:00
if ( counter > adaptive_samples_per_window / 4 ) {
2021-06-29 12:11:13 +00:00
// This window is loud, extend any existing run of loud windows
+ + adaptive_burst_runlength ;
} else {
// Quiet window. If we saw a run of loud windows >= 80us long, count
// that as a candidate for an over-amplified message that was
// not decoded.
if ( adaptive_burst_runlength > = 2 & & adaptive_burst_runlength < = 5 )
2021-07-07 12:57:34 +00:00
+ + adaptive_burst_block_loud_undecoded ;
2021-06-29 12:11:13 +00:00
adaptive_burst_runlength = 0 ;
}
}
// Noise measurement: process 'length' samples from 'buf'.
// The samples will not cross a block boundary.
static void adaptive_range_update ( uint16_t * buf , unsigned length )
{
if ( ! Modes . adaptive_range_control )
return ;
2021-07-07 12:57:34 +00:00
adaptive_range_radix_counter + = length ;
2021-06-29 12:11:13 +00:00
while ( length - - ) {
// do a very simple radix sort of sample magnitudes
// so we can later find the Nth percentile value
+ + adaptive_range_radix [ buf [ 0 ] ] ;
+ + buf ;
}
}
// Noise measurement: we reached the end of a block, update
// our noise estimate
static void adaptive_range_end_of_block ( )
{
if ( ! Modes . adaptive_range_control )
return ;
unsigned n = 0 , i = 0 ;
// measure Nth percentile magnitude
2021-07-07 12:57:34 +00:00
unsigned count_n = adaptive_range_radix_counter * Modes . adaptive_range_percentile / 100 ;
2021-06-29 12:11:13 +00:00
while ( i < 65536 & & n < = count_n )
n + = adaptive_range_radix [ i + + ] ;
uint16_t percentile_n = i - 1 ;
// maintain an EMA of the Nth percentile
adaptive_range_smoothed = adaptive_range_smoothed * ( 1 - Modes . adaptive_range_alpha ) + percentile_n * Modes . adaptive_range_alpha ;
// .. report to stats in dBFS
if ( adaptive_range_smoothed > 0 ) {
Modes . stats_current . adaptive_noise_dbfs = 20 * log10 ( adaptive_range_smoothed / 65536.0 ) ;
} else {
Modes . stats_current . adaptive_noise_dbfs = 0 ;
}
// reset radix sort for the next block
memset ( adaptive_range_radix , 0 , 65536 * sizeof ( unsigned ) ) ;
2021-07-07 12:57:34 +00:00
adaptive_range_radix_counter = 0 ;
2021-06-29 12:11:13 +00:00
}
// Burst measurement: we reached the end of a block, update our burst rate estimate
static void adaptive_burst_end_of_block ( )
{
if ( ! Modes . adaptive_burst_control )
return ;
2021-07-15 08:44:26 +00:00
// scale rates based on the actual duty cycle fraction
// (e.g. if we are only inspecting 2/5 of samples, then scale the rate by 5/2)
double scale = ( double ) adaptive_subblock_dutycycle_D / adaptive_subblock_dutycycle_N ;
2021-07-07 12:57:34 +00:00
// maintain an EMA of the number of undecoded loud bursts seen per block
Modes . stats_current . adaptive_loud_undecoded + = adaptive_burst_block_loud_undecoded ;
2021-07-15 08:44:26 +00:00
adaptive_burst_loud_undecoded_smoothed = adaptive_burst_loud_undecoded_smoothed * ( 1 - Modes . adaptive_burst_alpha ) + scale * adaptive_burst_block_loud_undecoded * Modes . adaptive_burst_alpha ;
2021-07-07 12:57:34 +00:00
adaptive_burst_block_loud_undecoded = 0 ;
2021-06-29 12:11:13 +00:00
// maintain an EMA of the number of decoded, but loud, messages seen per block
2021-07-07 12:57:34 +00:00
Modes . stats_current . adaptive_loud_decoded + = adaptive_burst_block_loud_decoded ;
2021-07-15 08:44:26 +00:00
adaptive_burst_loud_decoded_smoothed = adaptive_burst_loud_decoded_smoothed * ( 1 - Modes . adaptive_burst_alpha ) + scale * adaptive_burst_block_loud_decoded * Modes . adaptive_burst_alpha ;
2021-07-07 12:57:34 +00:00
adaptive_burst_block_loud_decoded = 0 ;
2021-06-29 12:11:13 +00:00
}
void flush_stats ( uint64_t now ) ;
static void adaptive_increase_gain ( const char * why )
{
if ( adaptive_set_gain ( sdrGetGain ( ) + 1 , why ) )
adaptive_gain_changed ( ) ;
}
static void adaptive_decrease_gain ( const char * why )
{
if ( adaptive_set_gain ( sdrGetGain ( ) - 1 , why ) )
adaptive_gain_changed ( ) ;
}
// Adaptive gain: we reached a block boundary. Update measurements and act on them.
static void adaptive_end_of_block ( )
{
adaptive_range_end_of_block ( ) ;
adaptive_burst_end_of_block ( ) ;
2021-07-07 12:57:34 +00:00
adaptive_control_update ( ) ;
2021-06-29 12:11:13 +00:00
2021-07-07 12:18:54 +00:00
Modes . stats_current . adaptive_valid = true ;
2021-06-29 12:11:13 +00:00
unsigned current = Modes . stats_current . adaptive_gain = sdrGetGain ( ) ;
2021-07-07 12:57:34 +00:00
Modes . stats_current . adaptive_range_gain_limit = adaptive_range_gain_limit ;
2021-06-29 12:11:13 +00:00
+ + Modes . stats_current . adaptive_gain_seconds [ current < STATS_GAIN_COUNT ? current : STATS_GAIN_COUNT - 1 ] ;
}
2021-07-07 12:57:34 +00:00
static void adaptive_control_update ( )
2021-06-29 12:11:13 +00:00
{
2021-07-07 12:57:34 +00:00
// votes for what to do with the gain
// "gain_not_up" overlaps somewhat with "gain_down", but they are not identical;
// burst control may want to prevent gain from increasing, but not necessarily
// decrease gain.
2021-06-29 12:11:13 +00:00
2021-07-07 12:57:34 +00:00
bool gain_up = false ;
const char * gain_up_reason = NULL ;
bool gain_down = false ;
const char * gain_down_reason = NULL ;
bool gain_not_up = false ;
int current_gain = sdrGetGain ( ) ;
if ( adaptive_burst_change_timer )
- - adaptive_burst_change_timer ;
if ( adaptive_range_change_timer > 0 )
- - adaptive_range_change_timer ;
if ( adaptive_range_rescan_timer > 0 )
- - adaptive_range_rescan_timer ;
2021-06-29 12:11:13 +00:00
2021-07-07 12:57:34 +00:00
if ( Modes . adaptive_burst_control & & ! adaptive_burst_change_timer ) {
if ( adaptive_burst_loud_undecoded_smoothed > Modes . adaptive_burst_loud_rate ) {
2021-06-29 12:11:13 +00:00
adaptive_burst_quiet_blocks = 0 ;
+ + adaptive_burst_loud_blocks ;
2021-07-07 12:57:34 +00:00
} else if ( adaptive_burst_loud_decoded_smoothed < Modes . adaptive_burst_quiet_rate ) {
2021-06-29 12:11:13 +00:00
adaptive_burst_loud_blocks = 0 ;
+ + adaptive_burst_quiet_blocks ;
} else {
adaptive_burst_loud_blocks = 0 ;
adaptive_burst_quiet_blocks = 0 ;
}
if ( adaptive_burst_loud_blocks > = Modes . adaptive_burst_loud_runlength ) {
// we need to reduce gain (further)
2021-07-07 12:57:34 +00:00
gain_down = gain_not_up = true ;
gain_down_reason = " high rate of loud undecoded messages " ;
// if we're currently doing a downward scan, reducing gain further may confuse it;
// stop that scan and restart it once we are no longer in a reduced-gain state
2021-12-07 09:29:50 +00:00
if ( adaptive_range_state = = RANGE_SCAN_DOWN | | adaptive_range_state = = RANGE_RESCAN_DOWN ) {
2021-07-07 12:57:34 +00:00
adaptive_range_state = RANGE_SCAN_IDLE ;
adaptive_range_rescan_timer = 0 ;
2021-06-29 12:11:13 +00:00
}
2021-07-07 12:57:34 +00:00
} else if ( adaptive_burst_quiet_blocks < Modes . adaptive_burst_quiet_runlength ) {
// we're OK at the current gain, but should not increase it
gain_not_up = true ;
} else if ( current_gain < adaptive_range_gain_limit ) {
// we're OK at the current gain, and can increase gain to the previously discovered
// dynamic range limit
gain_up = true ;
gain_up_reason = " low loud message rate and gain below dynamic range limit " ;
2021-06-29 12:11:13 +00:00
}
}
2021-07-07 12:57:34 +00:00
if ( Modes . adaptive_range_control & & ! adaptive_range_change_timer ) {
float available_range = - 20 * log10 ( adaptive_range_smoothed / 65536.0 ) ;
// allow the gain limit to increase if this gain setting is acceptable
2021-07-15 08:43:02 +00:00
// (decreasing the limit is done separately depending on the current state as we make slightly different decisions in IDLE
// to provide hysteresis)
if ( available_range > = Modes . adaptive_range_target & & current_gain > adaptive_range_gain_limit ) {
2021-07-07 12:57:34 +00:00
adaptive_range_gain_limit = current_gain ;
2021-07-15 08:43:02 +00:00
}
2021-07-07 12:57:34 +00:00
switch ( adaptive_range_state ) {
case RANGE_SCAN_UP :
2021-12-07 09:29:50 +00:00
case RANGE_RESCAN_UP :
2021-07-07 12:57:34 +00:00
if ( available_range < Modes . adaptive_range_target ) {
// Current gain fails to meet our target. Switch to downward scanning.
fprintf ( stderr , " adaptive: available dynamic range (%.1fdB) < required dynamic range (%.1fdB), switching to downward scan \n " , available_range , Modes . adaptive_range_target ) ;
gain_down = gain_not_up = true ;
gain_down_reason = " probing dynamic range gain lower bound " ;
2021-12-07 09:29:50 +00:00
adaptive_range_state = ( adaptive_range_state = = RANGE_RESCAN_UP ? RANGE_RESCAN_DOWN : RANGE_SCAN_DOWN ) ;
2021-07-15 08:43:02 +00:00
if ( adaptive_range_gain_limit > = current_gain ) {
2021-07-07 12:57:34 +00:00
adaptive_range_gain_limit = current_gain - 1 ;
2021-07-15 08:43:02 +00:00
}
2021-07-07 12:57:34 +00:00
break ;
}
2021-06-29 12:11:13 +00:00
2021-07-07 12:57:34 +00:00
if ( sdrGetGain ( ) > = adaptive_gain_max ) {
// We have reached our upper gain limit
fprintf ( stderr , " adaptive: reached upper gain limit, halting dynamic range scan here \n " ) ;
adaptive_range_state = RANGE_SCAN_IDLE ;
adaptive_range_rescan_timer = Modes . adaptive_range_rescan_delay ;
break ;
}
2021-06-29 12:11:13 +00:00
2021-07-07 12:57:34 +00:00
// This gain step is OK and we have more to try, try the next gain step up.
// (But if burst detection has inhibited increasing gain, don't do anything yet, just try again next block)
if ( ! gain_not_up ) {
fprintf ( stderr , " adaptive: available dynamic range (%.1fdB) >= required dynamic range (%.1fdB), continuing upward scan \n " , available_range , Modes . adaptive_range_target ) ;
gain_up = true ;
gain_up_reason = " probing dynamic range gain upper bound " ;
}
2021-06-29 12:11:13 +00:00
break ;
2021-07-07 12:57:34 +00:00
case RANGE_SCAN_DOWN :
2021-12-07 09:29:50 +00:00
case RANGE_RESCAN_DOWN :
2021-07-07 12:57:34 +00:00
if ( available_range > = Modes . adaptive_range_target ) {
// Current gain meets our target; we are done with the scan.
fprintf ( stderr , " adaptive: available dynamic range (%.1fdB) >= required dynamic range (%.1fdB), stopping downwards scan here \n " , available_range , Modes . adaptive_range_target ) ;
adaptive_range_state = RANGE_SCAN_IDLE ;
2021-12-07 09:29:50 +00:00
adaptive_range_rescan_timer = ( adaptive_range_state = = RANGE_SCAN_DOWN ? Modes . adaptive_range_scan_delay : Modes . adaptive_range_rescan_delay ) ;
2021-07-07 12:57:34 +00:00
break ;
}
2021-06-29 12:11:13 +00:00
2021-07-15 08:43:02 +00:00
if ( adaptive_range_gain_limit > = current_gain ) {
adaptive_range_gain_limit = current_gain - 1 ;
}
2021-07-07 12:57:34 +00:00
if ( sdrGetGain ( ) < = adaptive_gain_min ) {
fprintf ( stderr , " adaptive: reached lower gain limit, halting dynamic range scan here \n " ) ;
adaptive_range_state = RANGE_SCAN_IDLE ;
adaptive_range_rescan_timer = Modes . adaptive_range_rescan_delay ;
break ;
}
2021-06-29 12:11:13 +00:00
2021-07-07 12:57:34 +00:00
// This gain step is too loud and we have more to try, try the next gain step down
fprintf ( stderr , " adaptive: available dynamic range (%.1fdB) < required dynamic range (%.1fdB), continuing downwards scan \n " , available_range , Modes . adaptive_range_target ) ;
gain_down = gain_not_up = true ;
gain_down_reason = " probing dynamic range gain lower bound " ;
2021-06-29 12:11:13 +00:00
break ;
2021-07-07 12:57:34 +00:00
case RANGE_SCAN_IDLE :
// Look for increased noise that could be compensated for by decreasing gain.
// Do this even if we're waiting to rescan or if burst control is also active
if ( available_range + adaptive_gain_down_db / 2 < Modes . adaptive_range_target & & sdrGetGain ( ) > adaptive_gain_min ) {
fprintf ( stderr , " adaptive: available dynamic range (%.1fdB) + half gain step down (%.1fdB) < required dynamic range (%.1fdB), starting downward scan \n " ,
available_range , Modes . adaptive_range_target , adaptive_gain_down_db ) ;
2021-07-15 08:43:02 +00:00
if ( adaptive_range_gain_limit > = current_gain ) {
2021-07-07 12:57:34 +00:00
adaptive_range_gain_limit = current_gain - 1 ;
2021-07-15 08:43:02 +00:00
}
2021-07-07 12:57:34 +00:00
adaptive_range_state = RANGE_SCAN_DOWN ;
gain_down = gain_not_up = true ;
gain_down_reason = " dynamic range fell below target value " ;
break ;
}
2021-06-29 12:11:13 +00:00
2021-07-07 12:57:34 +00:00
// Infrequently consider increasing gain to handle the case where we've selected a too-low gain where the noise floor is dominated by noise unrelated to the gain setting.
// But don't do this while burst control is preventing gain increases.
if ( ! adaptive_range_rescan_timer & & ! gain_not_up ) {
if ( available_range > = Modes . adaptive_range_target & & sdrGetGain ( ) < adaptive_gain_max ) {
fprintf ( stderr , " adaptive: start periodic scan for acceptable dynamic range at increased gain \n " ) ;
gain_up = true ;
gain_up_reason = " periodic re-probing of dynamic range gain upper bound " ;
2021-12-07 09:29:50 +00:00
adaptive_range_state = RANGE_RESCAN_UP ;
2021-07-07 12:57:34 +00:00
break ;
}
// Nothing to do for a while.
adaptive_range_rescan_timer = Modes . adaptive_range_rescan_delay ;
}
2021-06-29 12:11:13 +00:00
break ;
2021-07-07 12:57:34 +00:00
default :
fprintf ( stderr , " adaptive: in a weird state (%d), trying to fix it \n " , adaptive_range_state ) ;
adaptive_range_state = RANGE_SCAN_IDLE ;
adaptive_range_rescan_timer = Modes . adaptive_range_rescan_delay ;
2021-06-29 12:11:13 +00:00
break ;
}
2021-07-07 12:57:34 +00:00
}
2021-06-29 12:11:13 +00:00
2021-07-07 12:57:34 +00:00
// now actually perform any gain changes
2021-06-29 12:11:13 +00:00
2021-07-07 12:57:34 +00:00
if ( gain_down )
adaptive_decrease_gain ( gain_down_reason ) ;
else if ( gain_up & & ! gain_not_up )
adaptive_increase_gain ( gain_up_reason ) ;
2021-06-29 12:11:13 +00:00
}