diff --git a/adaptive.c b/adaptive.c index 1a5a4d0..15ce920 100644 --- a/adaptive.c +++ b/adaptive.c @@ -33,13 +33,44 @@ static float adaptive_gain_down_db; // // block handling // +// 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. -static unsigned adaptive_block_remaining; // samples in each block -static unsigned adaptive_block_size; // samples remaining in the current block + +static const unsigned adaptive_subblocks_per_block = 20; // subblocks per block +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 +static const unsigned adaptive_subblock_dutycycle_D = adaptive_subblocks_per_block; // subblock duty cycle denominator D +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 void adaptive_init(); void adaptive_update(uint16_t *buf, unsigned length, struct modesMessage *decoded); -static void adaptive_update_single(uint16_t *buf, unsigned length, struct modesMessage *decoded); +static void adaptive_update_subblock(uint16_t *buf, unsigned length, struct modesMessage *decoded); static void adaptive_end_of_block(); static void adaptive_control_update(); @@ -47,7 +78,6 @@ static void adaptive_control_update(); // burst handling // -static unsigned adaptive_burst_window_size; // samples in each burst window 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 @@ -140,14 +170,27 @@ void adaptive_init() if (!Modes.adaptive_burst_control && !Modes.adaptive_range_control) return; + // Set up window, subblock, and block sizes // Look for 40us bursts - adaptive_burst_window_size = Modes.sample_rate / 25000; - adaptive_burst_window_remaining = adaptive_burst_window_size; - adaptive_burst_window_counter = 0; + adaptive_samples_per_window = Modes.sample_rate / 25000; - // Use an overall block size that is an exact multiple of the burst window, close to 1 second long - adaptive_block_size = adaptive_burst_window_size * 25000; - adaptive_block_remaining = adaptive_block_size; + // 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); + if (N <= 0 || N > adaptive_subblock_dutycycle_D) { + fprintf(stderr, "warning: --adaptive-duty-cycle value %.1f%% is out of range, using 100%% instead\n", + Modes.adaptive_duty_cycle * 100.0); + N = adaptive_subblock_dutycycle_D; + } + adaptive_subblock_dutycycle_N = (unsigned)N; + + adaptive_burst_window_remaining = adaptive_samples_per_window; + adaptive_burst_window_counter = 0; adaptive_range_radix = calloc(sizeof(unsigned), 65536); adaptive_range_state = RANGE_SCAN_UP; @@ -180,26 +223,44 @@ void adaptive_update(uint16_t *buf, unsigned length, struct modesMessage *decode { if (!Modes.adaptive_burst_control && !Modes.adaptive_range_control) return; - - // process samples up to a block boundary, then process the completed block - while (length >= adaptive_block_remaining) { - adaptive_update_single(buf, adaptive_block_remaining, decoded); - buf += adaptive_block_remaining; - length -= adaptive_block_remaining; - adaptive_end_of_block(); - adaptive_block_remaining = adaptive_block_size; + // 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(); + } } - // process final samples that don't complete a block + // process final samples that don't complete a subblock if (length > 0) { - adaptive_update_single(buf, length, decoded); - adaptive_block_remaining -= length; + if (adaptive_subblock_active) + adaptive_update_subblock(buf, length, decoded); + adaptive_subblock_samples_remaining -= length; } } -// Feed some samples into the adaptive system. The samples are guaranteed to not cross a block boundary. -static void adaptive_update_single(uint16_t *buf, unsigned length, struct modesMessage *decoded) +// 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) { if (decoded) { if (/* decoded->msgbits == 112 && */ decoded->signalLevel >= adaptive_burst_loud_threshold) @@ -229,8 +290,8 @@ static void adaptive_burst_skip(unsigned length) length -= adaptive_burst_window_remaining; // skip remaining windows, dispatch them - unsigned windows = length / adaptive_burst_window_size; - unsigned samples = windows * adaptive_burst_window_size; + unsigned windows = length / adaptive_samples_per_window; + unsigned samples = windows * adaptive_samples_per_window; while (windows--) adaptive_burst_end_of_window(0); @@ -238,7 +299,7 @@ static void adaptive_burst_skip(unsigned length) // final partial window adaptive_burst_window_counter = 0; - adaptive_burst_window_remaining = adaptive_burst_window_size - length; + adaptive_burst_window_remaining = adaptive_samples_per_window - length; } // Burst measurement: process 'length' samples from 'buf', look for loud bursts; @@ -265,15 +326,15 @@ static void adaptive_burst_update(uint16_t *buf, unsigned length) length -= n; // remaining windows - unsigned windows = length / adaptive_burst_window_size; - unsigned samples = windows * adaptive_burst_window_size; + unsigned windows = length / adaptive_samples_per_window; + unsigned samples = windows * adaptive_samples_per_window; adaptive_burst_scan_windows(buf, windows); buf += samples; length -= samples; // final partial window adaptive_burst_window_counter = adaptive_burst_count_samples(buf, length); - adaptive_burst_window_remaining = adaptive_burst_window_size - length; + adaptive_burst_window_remaining = adaptive_samples_per_window - length; } // Burst measurement: process 'windows' complete burst windows starting at 'buf'; @@ -281,8 +342,8 @@ static void adaptive_burst_update(uint16_t *buf, unsigned length) static void adaptive_burst_scan_windows(uint16_t *buf, unsigned windows) { while (windows--) { - unsigned counter = adaptive_burst_count_samples(buf, adaptive_burst_window_size); - buf += adaptive_burst_window_size; + unsigned counter = adaptive_burst_count_samples(buf, adaptive_samples_per_window); + buf += adaptive_samples_per_window; adaptive_burst_end_of_window(counter); } } @@ -301,7 +362,7 @@ static inline unsigned adaptive_burst_count_samples(uint16_t *buf, unsigned n) // loud samples seen, handle that window. static void adaptive_burst_end_of_window(unsigned counter) { - if (counter > adaptive_burst_window_size / 4) { + if (counter > adaptive_samples_per_window / 4) { // This window is loud, extend any existing run of loud windows ++adaptive_burst_runlength; } else { @@ -365,14 +426,18 @@ static void adaptive_burst_end_of_block() if (!Modes.adaptive_burst_control) return; + // 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; + // maintain an EMA of the number of undecoded loud bursts seen per block Modes.stats_current.adaptive_loud_undecoded += adaptive_burst_block_loud_undecoded; - adaptive_burst_loud_undecoded_smoothed = adaptive_burst_loud_undecoded_smoothed * (1 - Modes.adaptive_burst_alpha) + adaptive_burst_block_loud_undecoded * Modes.adaptive_burst_alpha; + 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; adaptive_burst_block_loud_undecoded = 0; // maintain an EMA of the number of decoded, but loud, messages seen per block Modes.stats_current.adaptive_loud_decoded += adaptive_burst_block_loud_decoded; - adaptive_burst_loud_decoded_smoothed = adaptive_burst_loud_decoded_smoothed * (1 - Modes.adaptive_burst_alpha) + adaptive_burst_block_loud_decoded * Modes.adaptive_burst_alpha; + 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; adaptive_burst_block_loud_decoded = 0; } diff --git a/dump1090.c b/dump1090.c index 611abf1..ac84ff5 100644 --- a/dump1090.c +++ b/dump1090.c @@ -126,6 +126,8 @@ static void modesInitConfig(void) { Modes.adaptive_min_gain_db = 0; Modes.adaptive_max_gain_db = 99999; + Modes.adaptive_duty_cycle = 0.5; + Modes.adaptive_burst_control = false; Modes.adaptive_burst_alpha = 2.0 / (5 + 1); Modes.adaptive_burst_change_delay = 5; @@ -372,6 +374,7 @@ static void showHelp(void) " gain scanning (seconds)\n" "--adaptive-min-gain Set gain adjustment range lower limit (dB)\n" "--adaptive-max-gain Set gain adjustment range upper limit (dB)\n" +"--adaptive-duty-cycle

Set adaptive gain duty cycle %% (1..100)\n" "\n" // ------ 80 char limit ----------------------------------------------------------| " Network connections\n" @@ -764,6 +767,8 @@ int main(int argc, char **argv) { Modes.adaptive_min_gain_db = atof(argv[++j]); } else if (!strcmp(argv[j], "--adaptive-max-gain") && more) { Modes.adaptive_max_gain_db = atof(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-duty-cycle") && more) { + Modes.adaptive_duty_cycle = atof(argv[++j]) / 100.0; } else if (!strcmp(argv[j], "--adaptive-burst")) { Modes.adaptive_burst_control = true; } else if (!strcmp(argv[j], "--adaptive-burst-alpha") && more) { diff --git a/dump1090.h b/dump1090.h index c0eb1bc..8bf6918 100644 --- a/dump1090.h +++ b/dump1090.h @@ -394,6 +394,8 @@ struct _Modes { // Internal state float adaptive_min_gain_db; float adaptive_max_gain_db; + float adaptive_duty_cycle; + bool adaptive_burst_control; float adaptive_burst_alpha; unsigned adaptive_burst_change_delay;