Skip to content

Commit 3f5d602

Browse files
secupclaude
andcommitted
Add GUI narrowband selector and responder negotiation for OFDM_NARROW
- Expand expert waveform dropdown from 3 to 5 items: AUTO, OFDM (OFDM_CHIRP), OFDM Narrow, OFDM HiSpeed (OFDM_COX), DPSK. "OFDM" now maps to OFDM_CHIRP (the recommended mode) instead of OFDM_COX. - Add session-scoped narrowband_override_ to Connection so responder negotiateMode() picks OFDM_NARROW when narrowband chirp is detected, without requiring the initiator to send explicit preferred_mode. Override is cleared on disconnect/reset to avoid leaking to later sessions. - Wire narrowband chirp detection to override in app.cpp, virtual station, and cli_simulator ping callbacks. - Fix initiator narrowband: selecting OFDM Narrow in GUI now switches encoder to narrowband chirps via modem_.setNarrowbandControl(true). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e135764 commit 3f5d602

File tree

10 files changed

+71
-9
lines changed

10 files changed

+71
-9
lines changed

src/gui/app.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,10 @@ App::App(const Options& opts) : options_(opts), sim_ui_visible_(opts.enable_sim)
484484
} else {
485485
guiLog("MODEM: Detected PING/PONG (SNR=%.1f dB)", display_snr);
486486
}
487+
// If narrowband chirp detected, set session-scoped override so negotiateMode() picks OFDM_NARROW
488+
if (modem_.isNarrowbandDetected()) {
489+
protocol_.setNarrowbandOverride(protocol::WaveformMode::OFDM_NARROW);
490+
}
487491
protocol_.onPingReceived();
488492
});
489493
ultra::gui::startupTrace("App", "protocol-callbacks-mid6");
@@ -751,6 +755,8 @@ App::App(const Options& opts) : options_(opts), sim_ui_visible_(opts.enable_sim)
751755
protocol_.setPreferredMode(static_cast<protocol::WaveformMode>(waveform));
752756
protocol_.setForcedModulation(static_cast<Modulation>(modulation));
753757
protocol_.setForcedCodeRate(static_cast<CodeRate>(code_rate));
758+
// Switch encoder chirps to narrowband when OFDM_NARROW is forced
759+
modem_.setNarrowbandControl(waveform == static_cast<uint8_t>(protocol::WaveformMode::OFDM_NARROW));
754760
settings_.save();
755761
});
756762
ultra::gui::startupTrace("App", "settings-callbacks-exit");
@@ -760,6 +766,7 @@ App::App(const Options& opts) : options_(opts), sim_ui_visible_(opts.enable_sim)
760766
protocol_.setPreferredMode(static_cast<protocol::WaveformMode>(settings_.forced_waveform));
761767
protocol_.setForcedModulation(static_cast<Modulation>(settings_.forced_modulation));
762768
protocol_.setForcedCodeRate(static_cast<CodeRate>(settings_.forced_code_rate));
769+
modem_.setNarrowbandControl(settings_.forced_waveform == static_cast<uint8_t>(protocol::WaveformMode::OFDM_NARROW));
763770
ultra::gui::startupTrace("App", "apply-expert-exit");
764771

765772
// Apply initial filter settings from loaded config
@@ -1065,6 +1072,9 @@ void App::initVirtualStation() {
10651072
// Wire up virtual modem ping detection to virtual protocol
10661073
virtual_modem_->setPingReceivedCallback([this](float snr) {
10671074
guiLog("SIM: Virtual modem detected PING/PONG (SNR=%.1f dB)", snr);
1075+
if (virtual_modem_->isNarrowbandDetected()) {
1076+
virtual_protocol_.setNarrowbandOverride(protocol::WaveformMode::OFDM_NARROW);
1077+
}
10681078
virtual_protocol_.onPingReceived();
10691079
});
10701080

src/gui/modem/modem_engine.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,19 @@ std::vector<std::complex<float>> ModemEngine::getConstellationSymbols() const {
616616
return {};
617617
}
618618

619+
bool ModemEngine::isNarrowbandDetected() const {
620+
if (streaming_decoder_) {
621+
return streaming_decoder_->getDetectedBandwidth() == BandwidthMode::NARROW;
622+
}
623+
return false;
624+
}
625+
626+
void ModemEngine::setNarrowbandControl(bool narrowband) {
627+
if (streaming_encoder_) {
628+
streaming_encoder_->setNarrowbandControl(narrowband);
629+
}
630+
}
631+
619632
void ModemEngine::reset() {
620633
std::lock_guard<std::mutex> lock(rx_mutex_);
621634
std::queue<Bytes> empty;

src/gui/modem/modem_engine.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ class ModemEngine {
129129
using PingReceivedCallback = std::function<void(float measured_snr)>;
130130
void setPingReceivedCallback(PingReceivedCallback callback) { ping_received_callback_ = callback; }
131131

132+
// Check if last detected chirp was narrowband (valid after ping callback)
133+
bool isNarrowbandDetected() const;
134+
135+
// Switch encoder to narrowband control chirps (for initiator forcing OFDM_NARROW)
136+
void setNarrowbandControl(bool narrowband);
137+
132138
void reset();
133139
void clearRxBuffer(); // Clear RX audio buffer (use before TX to prevent echo)
134140

src/gui/widgets/settings.cpp

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -607,21 +607,25 @@ void SettingsWindow::renderExpertTab(AppSettings& settings) {
607607
ImGui::SetNextItemWidth(200);
608608

609609
// Current waveform display string
610-
const char* waveform_items[] = { "AUTO", "OFDM", "DPSK" };
610+
const char* waveform_items[] = { "AUTO", "OFDM", "OFDM Narrow", "OFDM HiSpeed", "DPSK" };
611611
int waveform_idx = 0; // AUTO
612-
if (settings.forced_waveform == 0x00) waveform_idx = 1; // OFDM
613-
else if (settings.forced_waveform == 0x04) waveform_idx = 2; // DPSK
612+
if (settings.forced_waveform == 0x05) waveform_idx = 1; // OFDM (OFDM_CHIRP)
613+
else if (settings.forced_waveform == 0x06) waveform_idx = 2; // OFDM Narrow
614+
else if (settings.forced_waveform == 0x00) waveform_idx = 3; // OFDM HiSpeed (OFDM_COX)
615+
else if (settings.forced_waveform == 0x04) waveform_idx = 4; // DPSK
614616

615-
if (ImGui::Combo("##waveform", &waveform_idx, waveform_items, 3)) {
617+
if (ImGui::Combo("##waveform", &waveform_idx, waveform_items, 5)) {
616618
switch (waveform_idx) {
617619
case 0: settings.forced_waveform = 0xFF; break; // AUTO
618-
case 1: settings.forced_waveform = 0x00; break; // OFDM
619-
case 2: settings.forced_waveform = 0x04; break; // DPSK
620+
case 1: settings.forced_waveform = 0x05; break; // OFDM (OFDM_CHIRP)
621+
case 2: settings.forced_waveform = 0x06; break; // OFDM Narrow
622+
case 3: settings.forced_waveform = 0x00; break; // OFDM HiSpeed (OFDM_COX)
623+
case 4: settings.forced_waveform = 0x04; break; // DPSK
620624
}
621625
changed = true;
622626
}
623627
ImGui::SameLine();
624-
ImGui::TextDisabled("OFDM=fast, DPSK=robust");
628+
ImGui::TextDisabled("OFDM=recommended, Narrow=low SNR, DPSK=robust");
625629

626630
ImGui::Spacing();
627631

@@ -696,8 +700,10 @@ void SettingsWindow::renderExpertTab(AppSettings& settings) {
696700

697701
auto getWaveformStr = [](uint8_t w) -> const char* {
698702
switch (w) {
699-
case 0x00: return "OFDM";
703+
case 0x00: return "OFDM HiSpeed";
700704
case 0x04: return "DPSK";
705+
case 0x05: return "OFDM";
706+
case 0x06: return "OFDM Narrow";
701707
case 0xFF: return "AUTO";
702708
default: return "Unknown";
703709
}

src/protocol/connection.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,7 @@ void Connection::enterDisconnected(const std::string& reason) {
10411041
state_ = ConnectionState::DISCONNECTED;
10421042
is_initiator_ = false;
10431043
handshake_confirmed_ = false;
1044+
narrowband_override_ = WaveformMode::AUTO; // Clear session-scoped narrowband override
10441045
std::string old_remote = remote_call_;
10451046
remote_call_.clear();
10461047
pending_remote_call_.clear();
@@ -1160,6 +1161,7 @@ void Connection::reset() {
11601161
timeout_remaining_ms_ = 0;
11611162
connect_retry_count_ = 0;
11621163
connected_time_ms_ = 0;
1164+
narrowband_override_ = WaveformMode::AUTO; // Clear session-scoped narrowband override
11631165
negotiated_mode_ = WaveformMode::OFDM_COX;
11641166
remote_capabilities_ = ModeCapabilities::OFDM_COX;
11651167
remote_preferred_ = WaveformMode::OFDM_COX;

src/protocol/connection.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ class Connection {
159159
void setPreferredMode(WaveformMode mode) { config_.preferred_mode = mode; }
160160
void setModeCapabilities(uint8_t caps) { config_.mode_capabilities = caps; }
161161

162+
// Session-scoped narrowband override (cleared on disconnect/reset)
163+
// Set when responder detects narrowband chirp — overrides config_.preferred_mode for this session only
164+
void setNarrowbandOverride(WaveformMode mode) { narrowband_override_ = mode; }
165+
162166
// Forced data mode - operator can override SNR-based selection
163167
void setForcedModulation(Modulation mod) { config_.forced_modulation = mod; }
164168
void setForcedCodeRate(CodeRate rate) { config_.forced_code_rate = rate; }
@@ -229,6 +233,7 @@ class Connection {
229233
CodeRate pending_forced_code_rate_ = CodeRate::AUTO;
230234

231235
// Waveform mode
236+
WaveformMode narrowband_override_ = WaveformMode::AUTO; // Session-scoped, cleared on disconnect/reset
232237
WaveformMode negotiated_mode_ = WaveformMode::OFDM_COX;
233238
uint8_t remote_capabilities_ = ModeCapabilities::OFDM_COX;
234239
WaveformMode remote_preferred_ = WaveformMode::OFDM_COX;

src/protocol/connection_handlers.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,16 @@ WaveformMode Connection::negotiateMode(uint8_t remote_caps, WaveformMode remote_
510510
}
511511
}
512512

513+
// If narrowband chirp was detected this session, override for this connection
514+
if (narrowband_override_ != WaveformMode::AUTO) {
515+
uint8_t pref_bit = modeToBit(narrowband_override_);
516+
if (common & pref_bit) {
517+
LOG_MODEM(INFO, "Connection: Using narrowband override: %s",
518+
waveformModeToString(narrowband_override_));
519+
return narrowband_override_;
520+
}
521+
}
522+
513523
// If we have explicit preference, use it if remote supports it
514524
if (config_.preferred_mode != WaveformMode::AUTO) {
515525
uint8_t pref_bit = modeToBit(config_.preferred_mode);

src/protocol/protocol_engine.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,11 @@ void ProtocolEngine::setModeCapabilities(uint8_t caps) {
401401
connection_.setModeCapabilities(caps);
402402
}
403403

404+
void ProtocolEngine::setNarrowbandOverride(WaveformMode mode) {
405+
std::lock_guard<ProtocolEngineMutex> lock(mutex_);
406+
connection_.setNarrowbandOverride(mode);
407+
}
408+
404409
void ProtocolEngine::setForcedModulation(Modulation mod) {
405410
std::lock_guard<ProtocolEngineMutex> lock(mutex_);
406411
connection_.setForcedModulation(mod);

src/protocol/protocol_engine.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ class ProtocolEngine {
122122
void setPreferredMode(WaveformMode mode);
123123
void setModeCapabilities(uint8_t caps);
124124

125+
// Session-scoped narrowband override (cleared on disconnect/reset)
126+
void setNarrowbandOverride(WaveformMode mode);
127+
125128
// Forced data mode - operator can override SNR-based selection
126129
// Set before calling connect(). Values are sent in CONNECT frame.
127130
// 0xFF (AUTO) = let responder decide based on SNR

tools/cli_simulator.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,11 +592,13 @@ class SimulatedStation {
592592
decoder_->setPingCallback([this](float snr_db, float cfo_hz) {
593593
last_cfo_hz_ = cfo_hz;
594594
// If narrowband chirp was detected, switch control waveform to narrowband
595+
// and set session-scoped override so negotiateMode() picks OFDM_NARROW
595596
if (decoder_->getDetectedBandwidth() == BandwidthMode::NARROW) {
596597
if (encoder_) {
597598
encoder_->setNarrowbandControl(true);
598599
}
599-
LOG_MODEM(INFO, "[%s] Narrowband chirp detected, switching control waveform", callsign_.c_str());
600+
protocol_.setNarrowbandOverride(WaveformMode::OFDM_NARROW);
601+
LOG_MODEM(INFO, "[%s] Narrowband chirp detected, switching control waveform + narrowband override", callsign_.c_str());
600602
}
601603
protocol_.onPingReceived();
602604
});

0 commit comments

Comments
 (0)