Add option for TX blanking to suppress decoder when transmitting for half-duplex audio

This commit is contained in:
zenith 2026-02-17 11:53:21 -05:00
commit be38e04153
3 changed files with 64 additions and 2 deletions

View file

@ -252,6 +252,7 @@ public:
}
std::cerr << "Fragmentation: " << (config_.fragmentation_enabled ? "enabled" : "disabled") << std::endl;
std::cerr << "TX Blanking: " << (config_.tx_blanking_enabled ? "enabled" : "disabled") << std::endl;
// Show PTT status
switch (config_.ptt_type) {
@ -533,6 +534,10 @@ private:
std::cerr << packet_visualize(data.data(), data.size(), true, config_.fragmentation_enabled) << std::endl;
}
if (config_.tx_blanking_enabled) {
tx_blanking_active_ = true;
}
#ifdef WITH_UI
if (g_ui_state) {
g_ui_state->transmitting = true;
@ -554,6 +559,7 @@ private:
if (samples.empty()) {
ui_log("TX: Encoding failed");
tx_blanking_active_ = false;
#ifdef WITH_UI
if (g_ui_state) g_ui_state->transmitting = false;
#endif
@ -653,6 +659,8 @@ private:
}
}
tx_blanking_active_ = false;
#ifdef WITH_UI
if (g_ui_state) {
g_ui_state->transmitting = false;
@ -748,10 +756,22 @@ private:
}
};
bool was_blanking = false;
while (rx_running_ && g_running) {
int n = audio_->read(buffer.data(), buffer.size());
if (n > 0) {
decoder_->process(buffer.data(), n, frame_callback);
bool blanking = tx_blanking_active_.load();
if (blanking) {
was_blanking = true;
} else {
if (was_blanking) {
decoder_->reset();
was_blanking = false;
}
decoder_->process(buffer.data(), n, frame_callback);
}
#ifdef WITH_UI
if (g_ui_state && ++level_update_counter >= LEVEL_UPDATE_INTERVAL) {
@ -859,6 +879,9 @@ private:
std::chrono::steady_clock::time_point tx_lockout_until_;
static constexpr float RX_LOCKOUT_SECONDS = 0.5f;
// TX blanking
std::atomic<bool> tx_blanking_active_{false};
public:
// Update config at runtime (called from UI)
void update_config(const TNCConfig& new_config) {
@ -867,6 +890,10 @@ public:
config_.carrier_threshold_db = new_config.carrier_threshold_db;
config_.p_persistence = new_config.p_persistence;
config_.slot_time_ms = new_config.slot_time_ms;
// TX blanking
config_.tx_blanking_enabled = new_config.tx_blanking_enabled;
// Update callsign if changed
if (config_.callsign != new_config.callsign) {
@ -988,6 +1015,9 @@ void print_help(const char* prog) {
<< "\nFragmentation:\n"
<< " --frag Enable packet fragmentation/reassembly\n"
<< " --no-frag Disable fragmentation (default)\n"
<< "\nTX Blanking:\n"
<< " --tx-blank Suppress decoder during TX\n"
<< " --no-tx-blank Disable TX blanking (default)\n"
<< "\n"
#ifdef WITH_UI
<< " -h, --headless Run without TUI\n"
@ -1113,6 +1143,10 @@ int main(int argc, char** argv) {
config.fragmentation_enabled = true;
} else if (arg == "--no-frag") {
config.fragmentation_enabled = false;
} else if (arg == "--tx-blank") {
config.tx_blanking_enabled = true;
} else if (arg == "--no-tx-blank") {
config.tx_blanking_enabled = false;
} else {
std::cerr << "Unknown option: " << arg << std::endl;
print_help(argv[0]);
@ -1171,6 +1205,7 @@ int main(int argc, char** argv) {
config.slot_time_ms = ui_state.slot_time_ms;
config.p_persistence = ui_state.p_persistence;
config.fragmentation_enabled = ui_state.fragmentation_enabled;
config.tx_blanking_enabled = ui_state.tx_blanking_enabled;
// Audio devices
config.audio_input_device = ui_state.audio_input_device;
config.audio_output_device = ui_state.audio_output_device;
@ -1215,6 +1250,7 @@ int main(int argc, char** argv) {
ui_state.p_persistence = config.p_persistence;
ui_state.short_frame = config.short_frame;
ui_state.fragmentation_enabled = config.fragmentation_enabled;
ui_state.tx_blanking_enabled = config.tx_blanking_enabled;
// Audio devices
ui_state.audio_input_device = config.audio_input_device;
ui_state.audio_output_device = config.audio_output_device;
@ -1268,6 +1304,7 @@ int main(int argc, char** argv) {
// Sync fragmentation setting from command line to UI
ui_state.fragmentation_enabled = config.fragmentation_enabled;
ui_state.tx_blanking_enabled = config.tx_blanking_enabled;
ui_state.update_modem_info();
@ -1329,7 +1366,7 @@ int main(int argc, char** argv) {
new_config.p_persistence = state.p_persistence;
new_config.slot_time_ms = state.slot_time_ms;
new_config.fragmentation_enabled = state.fragmentation_enabled;
// Audio devices
new_config.tx_blanking_enabled = state.tx_blanking_enabled;
new_config.audio_input_device = state.audio_input_device;
new_config.audio_output_device = state.audio_output_device;
// PTT settings

View file

@ -101,6 +101,9 @@ struct TNCConfig {
// Fragmentation settings
bool fragmentation_enabled = false;
// TX blanking
bool tx_blanking_enabled = false;
// Settings file path
std::string config_file = "";
};

View file

@ -102,6 +102,7 @@ struct TNCUIState {
float airtime_seconds = 0.0f;
int random_data_size = 0;
bool fragmentation_enabled = false;
bool tx_blanking_enabled = false;
// stats
std::atomic<float> total_tx_time{0.0f};
@ -415,6 +416,7 @@ struct TNCUIState {
fprintf(f, "slot_time_ms=%d\n", slot_time_ms);
fprintf(f, "p_persistence=%d\n", p_persistence);
fprintf(f, "fragmentation_enabled=%d\n", fragmentation_enabled ? 1 : 0);
fprintf(f, "tx_blanking_enabled=%d\n", tx_blanking_enabled ? 1 : 0);
fprintf(f, "# Audio/PTT\n");
fprintf(f, "audio_input=%s\n", audio_input_device.c_str());
fprintf(f, "audio_output=%s\n", audio_output_device.c_str());
@ -463,6 +465,7 @@ struct TNCUIState {
else if (strcmp(key, "slot_time_ms") == 0) slot_time_ms = atoi(value);
else if (strcmp(key, "p_persistence") == 0) p_persistence = atoi(value);
else if (strcmp(key, "fragmentation_enabled") == 0) fragmentation_enabled = atoi(value) != 0;
else if (strcmp(key, "tx_blanking_enabled") == 0) tx_blanking_enabled = atoi(value) != 0;
else if (strcmp(key, "audio_input") == 0) audio_input_device = value;
else if (strcmp(key, "audio_output") == 0) audio_output_device = value;
else if (strcmp(key, "audio_device") == 0) {
@ -740,6 +743,7 @@ private:
FIELD_THRESHOLD,
FIELD_PERSISTENCE,
FIELD_FRAGMENTATION,
FIELD_TX_BLANKING,
FIELD_AUDIO_INPUT,
FIELD_AUDIO_OUTPUT,
FIELD_PTT_TYPE,
@ -1241,6 +1245,11 @@ private:
state_.update_modem_info(); // Update random_data_size limits
state_.add_log("(!) Fragmentation changed, restart required");
break;
case FIELD_TX_BLANKING:
state_.tx_blanking_enabled = !state_.tx_blanking_enabled;
apply_settings();
state_.add_log(std::string("TX blanking ") + (state_.tx_blanking_enabled ? "enabled" : "disabled"));
break;
case FIELD_AUDIO_INPUT:
break;
case FIELD_AUDIO_OUTPUT:
@ -2281,6 +2290,19 @@ private:
if (dy >= 0) draw_toggle_field(dy, c1, c2, "Enabled", FIELD_FRAGMENTATION, state_.fragmentation_enabled);
row += 2;
// TX Blanking section
dy = visible_y(row);
if (dy >= 0) {
attron(A_DIM);
mvaddstr(dy, c1, "TX BLANKING");
attroff(A_DIM);
}
row++;
dy = visible_y(row);
if (dy >= 0) draw_toggle_field(dy, c1, c2, "Enabled", FIELD_TX_BLANKING, state_.tx_blanking_enabled);
row += 2;
// Audio / ptt
dy = visible_y(row);