diff --git a/CONTROL_PORT.md b/CONTROL_PORT.md deleted file mode 100644 index 4cb83d8..0000000 --- a/CONTROL_PORT.md +++ /dev/null @@ -1,119 +0,0 @@ -# Control Port API - -TCP JSON protocol on port 8073 - -Wire format: 4-byte big-endian length prefix + JSON payload. - -## Commands - -| Command | Description | -|---|---| -| `get_status` | Current modem/channel state | -| `get_config` | Current configuration | -| `set_config` | Update configuration (partial updates OK) | -| `rigctl` | Passthrough command to rigctld | -| `tx` | Transmit data via KISS | - ---- - -## `get_status` - -**Request:** `{"cmd": "get_status"}` - -**Response:** - -| Field | Type | Description | -|---|---|---| -| `channel_state` | string | `"idle"`, `"tx"`, or `"rx"` | -| `ptt_on` | bool | PTT currently keyed | -| `rx_frame_count` | int | Successfully decoded frames | -| `tx_frame_count` | int | Transmitted frames | -| `rx_error_count` | int | Preamble + CRC errors | -| `sync_count` | int | Preamble sync detections | -| `preamble_errors` | int | Sync found but preamble decode failed | -| `symbol_errors` | int | Symbol-level errors (OFDM only) | -| `crc_errors` | int | CRC check failures | -| `last_snr` | float | Last decoded frame SNR (dB) | -| `last_ber` | float | Last decoded frame BER (0.0-1.0, -1 if unavailable) | -| `ber_ema` | float | Exponential moving average BER | -| `client_count` | int | Connected KISS clients | -| `rigctl_connected` | bool | rigctld connection status | -| `audio_connected` | bool | Audio device health | - -Stats switch between OFDM and MFSK decoder based on active `modem_type`. - ---- - -## `get_config` - -**Request:** `{"cmd": "get_config"}` - -**Response:** - -| Field | Type | Description | -|---|---|---| -| `callsign` | string | Station callsign | -| `modem_type` | int | `0` = OFDM, `1` = MFSK | -| `mfsk_mode` | int | `0` = MFSK-8, `1` = MFSK-16, `2` = MFSK-32, `3` = MFSK-32R | -| `modulation` | string | OFDM: `"BPSK"`..`"QAM4096"`. MFSK: `"MFSK-8"`..`"MFSK-32R"` | -| `code_rate` | string | `"1/2"`, `"2/3"`, `"3/4"`, `"5/6"`, `"1/4"` (OFDM only) | -| `short_frame` | bool | Short frame mode (OFDM only) | -| `center_freq` | int | Center frequency in Hz | -| `payload_size` | int | Current PHY payload capacity in bytes | -| `csma_enabled` | bool | CSMA carrier sense enabled | -| `carrier_threshold_db` | float | CSMA threshold (dB) | -| `p_persistence` | int | P-persistence value (0-255) | -| `slot_time_ms` | int | CSMA slot time (ms) | -| `tx_blanking_enabled` | bool | Suppress decoder during TX | - ---- - -## `set_config` - -**Request:** `{"cmd": "set_config", ...fields...}` - -Send only the fields you want to change. All fields from `get_config` are accepted. - - -Example: -```json -{"cmd": "set_config", "modulation": "8PSK", "code_rate": "1/2"} -``` - -**Response:** `{"ok": true}` or `{"ok": false}` - ---- - -## `rigctl` - -**Request:** `{"cmd": "rigctl", "command": "F"}` - -Passes the command string to rigctld and returns the response. - -**Response:** `{"ok": true, "response": "145000000\n"}` - ---- - -## `tx` - -**Request:** -```json -{"cmd": "tx", "data": "", "oper_mode": -1} -``` - -| Field | Type | Description | -|---|---|---| -| `data` | string | Base64-encoded raw payload bytes | -| `oper_mode` | int | OFDM mode override (-1 = use current config) | - -**Response:** `{"ok": true, "size": 123}` - ---- - -## Events - -The control port broadcasts events to all connected clients: - -| Event | When | -|---|---| -| `config_changed` | Any configuration change | diff --git a/Makefile b/Makefile index 6e205e5..5cf1a71 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,8 @@ INCLUDES = -I$(AICODIX_DSP) -I$(AICODIX_CODE) -I$(MODEM_SRC) TARGET = modem73 SRCS = kiss_tnc.cc -HDRS = kiss_tnc.hh miniaudio_audio.hh rigctl_ptt.hh modem.hh phy/mfsk_modem.hh tnc_ui.hh control_port.hh -OBJS = deps/miniaudio.o deps/cJSON.o +HDRS = kiss_tnc.hh miniaudio_audio.hh rigctl_ptt.hh modem.hh tnc_ui.hh control_port.hh +OBJS = miniaudio.o cJSON.o # defualt to build with UI, headless operations through --headless UI_FLAGS = -DWITH_UI @@ -37,11 +37,11 @@ endif all: $(TARGET) -deps/miniaudio.o: deps/miniaudio.c deps/miniaudio.h - $(CC) -c -O2 -o $@ deps/miniaudio.c +miniaudio.o: miniaudio.c miniaudio.h + $(CC) -c -O2 -o $@ miniaudio.c -deps/cJSON.o: deps/cJSON.c deps/cJSON.h - $(CC) -c -O2 -o $@ deps/cJSON.c +cJSON.o: cJSON.c cJSON.h + $(CC) -c -O2 -o $@ cJSON.c $(TARGET): $(SRCS) $(HDRS) $(OBJS) $(CXX) $(CXXFLAGS) $(UI_FLAGS) $(CM108_FLAGS) $(INCLUDES) -o $@ $(SRCS) $(OBJS) $(LDFLAGS) @@ -53,7 +53,7 @@ ifneq ($(HIDAPI_LIBS),) endif clean: - rm -f $(TARGET) $(OBJS) + rm -f $(TARGET) $(OBJS) cJSON.o install: $(TARGET) install -m 755 $(TARGET) /usr/local/bin/ diff --git a/README.md b/README.md index 5d9f911..e0be661 100644 --- a/README.md +++ b/README.md @@ -21,18 +21,16 @@ Windows fork by SP5LOT https://github.com/SP5LOT/modem73 ### Linux -On a system with `apt`? Run the installer script: -``` -./install.sh -``` - 1. Install dependencies ``` # Debian/Ubuntu/Pi sudo apt install git build-essential libncurses-dev g++ ``` - +Or if on a system with `apt` run: +``` +./install.sh +``` #### Optional Addons @@ -126,10 +124,6 @@ while running `rigctld` ./modem73 --ptt cm108 --cm108-gpio 3 ``` -### Control port - -A control port for modem73 will automatically start on port `8073` by default. View `CONTROL_PORT.md` for the full JSON spec - ## Updating diff --git a/deps/cJSON.c b/cJSON.c similarity index 100% rename from deps/cJSON.c rename to cJSON.c diff --git a/deps/cJSON.h b/cJSON.h similarity index 100% rename from deps/cJSON.h rename to cJSON.h diff --git a/control_port.hh b/control_port.hh index 9550aea..93d1528 100644 --- a/control_port.hh +++ b/control_port.hh @@ -19,7 +19,7 @@ #include extern "C" { -#include "deps/cJSON.h" +#include "cJSON.h" } // Base64 decode (RFC 4648) diff --git a/kiss_tnc.cc b/kiss_tnc.cc index 5f34922..eb2b97e 100644 --- a/kiss_tnc.cc +++ b/kiss_tnc.cc @@ -34,7 +34,6 @@ #include "cm108_ptt.hh" #endif #include "modem.hh" -#include "phy/mfsk_modem.hh" #include "control_port.hh" #ifdef WITH_UI @@ -133,19 +132,13 @@ public: class KISSTNC { public: KISSTNC(const TNCConfig& config) : config_(config) { - // Allocate OFDM encoder/decoder - std::cerr << " Creating OFDM encoder/decoder" << std::endl; + // Allocate encoder/decoder on heap + std::cerr << " Creating encoder" << std::endl; encoder_ = std::make_unique(); + std::cerr << " Creating decoder" << std::endl; decoder_ = std::make_unique(); - - // Allocate MFSK encoder/decoder - std::cerr << " Creating MFSK encoder/decoder" << std::endl; - mfsk_encoder_ = std::make_unique(); - mfsk_decoder_ = std::make_unique( - (MFSKMode)config.mfsk_mode, config.center_freq); - - std::cerr << " All encoders/decoders created" << std::endl; - + std::cerr << " Encoder/decoder created" << std::endl; + // Set up constellation callback for UI display #ifdef WITH_UI decoder_->constellation_callback = [this](const DSP::Complex* symbols, int count, int mod_bits) { @@ -160,7 +153,7 @@ public: } }; #endif - + // Init modem configuration modem_config_.sample_rate = config.sample_rate; modem_config_.center_freq = config.center_freq; @@ -170,19 +163,15 @@ public: config.code_rate.c_str(), config.short_frame ); - + if (modem_config_.call_sign < 0) { throw std::runtime_error("Invalid callsign"); } if (modem_config_.oper_mode < 0) { throw std::runtime_error("Invalid modulation or code rate"); } - - if (config.modem_type == 1) { - payload_size_ = mfsk_encoder_->get_payload_size((MFSKMode)config.mfsk_mode); - } else { - payload_size_ = encoder_->get_payload_size(modem_config_.oper_mode); - } + + payload_size_ = encoder_->get_payload_size(modem_config_.oper_mode); std::cerr << "Payload size: " << payload_size_ << " bytes" << std::endl; } @@ -569,21 +558,12 @@ private: auto framed_data = frame_with_length(data); // Encode to audio - std::vector samples; - if (config_.modem_type == 1) { - samples = mfsk_encoder_->encode( - framed_data.data(), framed_data.size(), - modem_config_.center_freq, - (MFSKMode)config_.mfsk_mode - ); - } else { - samples = encoder_->encode( - framed_data.data(), framed_data.size(), - modem_config_.center_freq, - modem_config_.call_sign, - tx_mode - ); - } + auto samples = encoder_->encode( + framed_data.data(), framed_data.size(), + modem_config_.center_freq, + modem_config_.call_sign, + tx_mode + ); if (samples.empty()) { ui_log("TX: Encoding failed"); @@ -746,7 +726,6 @@ private: } }; - // OFDM frame callback auto frame_callback = [this, &deliver_to_clients](const uint8_t* data, size_t len) { set_tx_lockout(RX_LOCKOUT_SECONDS); @@ -775,7 +754,7 @@ private: return; } - if (reassembler_.is_fragment(payload)) { + if (config_.fragmentation_enabled && reassembler_.is_fragment(payload)) { if (g_verbose) { std::cerr << packet_visualize(payload.data(), payload.size(), false, true) << std::endl; } @@ -789,67 +768,28 @@ private: deliver_to_clients(payload, snr, ber_pct, false); } }; - - // MFSK frame callback - auto mfsk_frame_callback = [this, &deliver_to_clients](const uint8_t* data, size_t len) { - set_tx_lockout(RX_LOCKOUT_SECONDS); - - float snr = mfsk_decoder_->get_last_snr(); - float ber_pct = -1.0f; - -#ifdef WITH_UI - if (g_ui_state) { - g_ui_state->rx_frame_count++; - g_ui_state->receiving = false; - g_ui_state->last_rx_snr = snr; - } -#endif - - auto payload = unframe_length(data, len); - - if (payload.empty()) { - ui_log("MFSK RX: Empty payload after unframing"); -#ifdef WITH_UI - if (g_ui_state) g_ui_state->rx_error_count++; -#endif - return; - } - - if (reassembler_.is_fragment(payload)) { - auto reassembled = reassembler_.process(payload); - if (!reassembled.empty()) { - ui_log("MFSK RX: Reassembled " + std::to_string(reassembled.size()) + " bytes"); - deliver_to_clients(reassembled, snr, ber_pct, true); - } - } else { - deliver_to_clients(payload, snr, ber_pct, false); - } - }; - + bool was_blanking = false; - + while (rx_running_ && g_running) { int n = audio_->read(buffer.data(), buffer.size()); if (n > 0) { bool blanking = tx_blanking_active_.load(); - + if (blanking) { was_blanking = true; } else { if (was_blanking) { decoder_->reset(); - mfsk_decoder_->reset(); was_blanking = false; } - // Feed same audio to both decod,ers decoder_->process(buffer.data(), n, frame_callback); - mfsk_decoder_->process(buffer.data(), n, mfsk_frame_callback); } - + #ifdef WITH_UI if (g_ui_state && ++level_update_counter >= LEVEL_UPDATE_INTERVAL) { level_update_counter = 0; - + // Calculate RMS level in dB float sum_sq = 0.0f; for (int i = 0; i < n; i++) { @@ -857,9 +797,9 @@ private: } float rms = std::sqrt(sum_sq / n); float db = 20.0f * std::log10(rms + 1e-10f); - + g_ui_state->update_level(db); - + // Copy decoder stats if (g_ui_state->stats_reset_requested.exchange(false)) { decoder_->stats_sync_count = 0; @@ -867,20 +807,12 @@ private: decoder_->stats_symbol_errors = 0; decoder_->stats_crc_errors = 0; decoder_->reset_ber(); - mfsk_decoder_->reset_stats(); g_ui_state->last_rx_ber = -1.0f; } - if (config_.modem_type == 1) { - g_ui_state->sync_count = mfsk_decoder_->stats_sync_count; - g_ui_state->preamble_errors = mfsk_decoder_->stats_preamble_errors; - g_ui_state->symbol_errors = 0; - g_ui_state->crc_errors = mfsk_decoder_->stats_crc_errors; - } else { - g_ui_state->sync_count = decoder_->stats_sync_count; - g_ui_state->preamble_errors = decoder_->stats_preamble_errors; - g_ui_state->symbol_errors = decoder_->stats_symbol_errors; - g_ui_state->crc_errors = decoder_->stats_crc_errors; - } + g_ui_state->sync_count = decoder_->stats_sync_count; + g_ui_state->preamble_errors = decoder_->stats_preamble_errors; + g_ui_state->symbol_errors = decoder_->stats_symbol_errors; + g_ui_state->crc_errors = decoder_->stats_crc_errors; } #endif } @@ -949,9 +881,7 @@ private: std::unique_ptr encoder_; std::unique_ptr decoder_; - std::unique_ptr mfsk_encoder_; - std::unique_ptr mfsk_decoder_; - + std::unique_ptr audio_; std::unique_ptr rigctl_; std::unique_ptr serial_ptt_; @@ -1002,50 +932,31 @@ public: if (config_.center_freq != new_config.center_freq) { config_.center_freq = new_config.center_freq; modem_config_.center_freq = config_.center_freq; - // Reconfigure MFSK decoder with new center freq - mfsk_decoder_->configure((MFSKMode)config_.mfsk_mode, config_.center_freq); ui_log("Center frequency changed to " + std::to_string(config_.center_freq) + " Hz"); } - - // Update modem type and sub-mode - if (config_.modem_type != new_config.modem_type || config_.mfsk_mode != new_config.mfsk_mode) { - config_.modem_type = new_config.modem_type; - config_.mfsk_mode = new_config.mfsk_mode; - if (config_.modem_type == 1) { - MFSKMode mmode = (MFSKMode)config_.mfsk_mode; - mfsk_decoder_->configure(mmode, config_.center_freq); - payload_size_ = mfsk_encoder_->get_payload_size(mmode); - ui_log("Mode changed to " + std::string(MFSK_MODE_NAMES[(int)mmode]) + - " (" + std::to_string(MFSKParams::max_payload(mmode)) + " bytes)"); - } else { - payload_size_ = encoder_->get_payload_size(modem_config_.oper_mode); - } - } - - // Update OFDM modulation settings + + // Update modulation settings bool mode_changed = (config_.modulation != new_config.modulation || config_.code_rate != new_config.code_rate || config_.short_frame != new_config.short_frame); - + if (mode_changed) { config_.modulation = new_config.modulation; config_.code_rate = new_config.code_rate; config_.short_frame = new_config.short_frame; - + int new_mode = ModemConfig::encode_mode( config_.modulation.c_str(), config_.code_rate.c_str(), config_.short_frame ); - + if (new_mode >= 0) { modem_config_.oper_mode = new_mode; - if (config_.modem_type == 0) { - payload_size_ = encoder_->get_payload_size(modem_config_.oper_mode); - } - ui_log("OFDM mode changed to " + config_.modulation + " " + config_.code_rate + + payload_size_ = encoder_->get_payload_size(modem_config_.oper_mode); + ui_log("Mode changed to " + config_.modulation + " " + config_.code_rate + " " + (config_.short_frame ? "short" : "normal") + - " (" + std::to_string(encoder_->get_payload_size(modem_config_.oper_mode)) + " bytes)"); + " (" + std::to_string(payload_size_) + " bytes)"); } } } @@ -1060,17 +971,6 @@ public: }; DecoderStats get_decoder_stats() const { - if (config_.modem_type == 1) { - return { - mfsk_decoder_->stats_sync_count, - mfsk_decoder_->stats_preamble_errors, - 0, // MFSK has no symbol errors stat - mfsk_decoder_->stats_crc_errors, - mfsk_decoder_->get_last_snr(), - mfsk_decoder_->get_last_ber(), - mfsk_decoder_->get_ber_ema() - }; - } return { decoder_->stats_sync_count, decoder_->stats_preamble_errors, @@ -1388,11 +1288,9 @@ int main(int argc, char** argv) { // Try to load saved settings if (ui_state.load_settings()) { - // Apply loaded settings to config + // Apply loaded settings to config if (!cli_callsign) config.callsign = ui_state.callsign; - config.modem_type = ui_state.modem_type_index; - config.mfsk_mode = ui_state.mfsk_mode_index; config.center_freq = ui_state.center_freq; config.modulation = MODULATION_OPTIONS[ui_state.modulation_index]; config.code_rate = CODE_RATE_OPTIONS[ui_state.code_rate_index]; @@ -1620,14 +1518,7 @@ int main(int argc, char** argv) { auto& cfg = tnc.get_config(); cJSON_AddStringToObject(j, "callsign", cfg.callsign.c_str()); - cJSON_AddNumberToObject(j, "modem_type", cfg.modem_type); - cJSON_AddNumberToObject(j, "mfsk_mode", cfg.mfsk_mode); - if (cfg.modem_type == 1) { - cJSON_AddStringToObject(j, "modulation", - MFSK_MODE_NAMES[cfg.mfsk_mode < 4 ? cfg.mfsk_mode : 0]); - } else { - cJSON_AddStringToObject(j, "modulation", cfg.modulation.c_str()); - } + cJSON_AddStringToObject(j, "modulation", cfg.modulation.c_str()); cJSON_AddStringToObject(j, "code_rate", cfg.code_rate.c_str()); cJSON_AddBoolToObject(j, "short_frame", cfg.short_frame); cJSON_AddNumberToObject(j, "center_freq", cfg.center_freq); @@ -1645,10 +1536,6 @@ int main(int argc, char** argv) { TNCConfig new_config = tnc.get_config(); cJSON* item; - if ((item = cJSON_GetObjectItemCaseSensitive(params, "modem_type")) && cJSON_IsNumber(item)) - new_config.modem_type = item->valueint; - if ((item = cJSON_GetObjectItemCaseSensitive(params, "mfsk_mode")) && cJSON_IsNumber(item)) - new_config.mfsk_mode = item->valueint; if ((item = cJSON_GetObjectItemCaseSensitive(params, "callsign")) && cJSON_IsString(item)) new_config.callsign = item->valuestring; if ((item = cJSON_GetObjectItemCaseSensitive(params, "modulation")) && cJSON_IsString(item)) @@ -1676,8 +1563,6 @@ int main(int argc, char** argv) { // Sync config back to TUI state so the UI reflects changes if (g_ui_state) { g_ui_state->callsign = new_config.callsign; - g_ui_state->modem_type_index = new_config.modem_type; - g_ui_state->mfsk_mode_index = new_config.mfsk_mode; g_ui_state->center_freq = new_config.center_freq; g_ui_state->short_frame = new_config.short_frame; g_ui_state->csma_enabled = new_config.csma_enabled; @@ -1724,8 +1609,6 @@ int main(int argc, char** argv) { if (g_use_ui) { ui_state.on_settings_changed = [&tnc, &ctrl](TNCUIState& state) { TNCConfig new_config = tnc.get_config(); - new_config.modem_type = state.modem_type_index; - new_config.mfsk_mode = state.mfsk_mode_index; new_config.callsign = state.callsign; new_config.center_freq = state.center_freq; new_config.modulation = MODULATION_OPTIONS[state.modulation_index]; diff --git a/kiss_tnc.hh b/kiss_tnc.hh index 162a38f..283f4a5 100644 --- a/kiss_tnc.hh +++ b/kiss_tnc.hh @@ -53,13 +53,11 @@ struct TNCConfig { int sample_rate = 48000; // Modem settings - int modem_type = 0; // 0=OFDM, 1=MFSK - int mfsk_mode = 1; // 0=MFSK-8, 1=MFSK-16, 2=MFSK-32, 3=MFSK-32R int center_freq = 1500; std::string callsign = "N0CALL"; std::string modulation = "QPSK"; std::string code_rate = "1/2"; - bool short_frame = false; + bool short_frame = false; // PTT settings PTTType ptt_type = PTTType::RIGCTL; diff --git a/deps/miniaudio.c b/miniaudio.c similarity index 100% rename from deps/miniaudio.c rename to miniaudio.c diff --git a/deps/miniaudio.h b/miniaudio.h similarity index 100% rename from deps/miniaudio.h rename to miniaudio.h diff --git a/miniaudio_audio.hh b/miniaudio_audio.hh index 9a82595..6d539e9 100644 --- a/miniaudio_audio.hh +++ b/miniaudio_audio.hh @@ -5,7 +5,7 @@ #define MA_NO_GENERATION #define MA_NO_ENGINE #define MA_NO_NODE_GRAPH -#include "deps/miniaudio.h" +#include "miniaudio.h" #include #include diff --git a/phy/mfsk_modem.hh b/phy/mfsk_modem.hh deleted file mode 100644 index 45989b5..0000000 --- a/phy/mfsk_modem.hh +++ /dev/null @@ -1,731 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -enum class MFSKMode { - MFSK_8 = 0, // 8 tones, 250 Hz BW, 3 bits/sym, rate 1/2 - MFSK_16 = 1, // 16 tones, 500 Hz BW, 4 bits/sym, rate 1/2 - MFSK_32 = 2, // 32 tones, 1000 Hz BW, 5 bits/sym, rate 1/2 - MFSK_32R = 3 // 32 tones, 1000 Hz BW, 5 bits/sym, rate 3/4 -}; - - - -static const char* MFSK_MODE_NAMES[] = {"MFSK-8", "MFSK-16", "MFSK-32", "MFSK-32R"}; - - - - - -struct MFSKParams { - static constexpr int SAMPLE_RATE = 48000; - static constexpr int SYMBOL_LEN = 1536; - static constexpr float TONE_SPACING = 31.25f; - static constexpr int PREAMBLE_SYMBOLS = 8; - static constexpr int SYNC_SYMBOLS = 2; - static constexpr int OVERHEAD_SYMBOLS = PREAMBLE_SYMBOLS + SYNC_SYMBOLS; - static constexpr int DATA_SYMBOLS = 128; - static constexpr int FRAME_SYMBOLS = OVERHEAD_SYMBOLS + DATA_SYMBOLS; - static constexpr int GUARD_SAMPLES = 32; - static constexpr int SEARCH_STEP = SYMBOL_LEN / 4; - - static constexpr int CONV_K = 7; - static constexpr int CONV_STATES = 64; - static constexpr int CONV_TAIL = 6; - static constexpr uint8_t CONV_G0 = 0x79; - static constexpr uint8_t CONV_G1 = 0x5B; - - - - static int num_tones(MFSKMode mode) { - static const int t[] = {8, 16, 32, 32}; - return t[(int)mode]; - } - - static int bits_per_symbol(MFSKMode mode) { - static const int b[] = {3, 4, 5, 5}; - return b[(int)mode]; - } - - static bool is_rate34(MFSKMode mode) { - return mode == MFSKMode::MFSK_32R; - } - - static int coded_capacity(MFSKMode mode) { - return DATA_SYMBOLS * bits_per_symbol(mode); - } - - static int data_bytes(MFSKMode mode) { - int coded = coded_capacity(mode); - if (is_rate34(mode)) - return (coded * 3 / 4 - CONV_TAIL) / 8; - return (coded / 2 - CONV_TAIL) / 8; - } - - static int max_payload(MFSKMode mode) { - return data_bytes(mode) - 4; - } - - static int frame_capacity(MFSKMode mode) { - return data_bytes(mode) - 2; - } - - static int base_bin(MFSKMode mode, int center_freq) { - int center_bin = (center_freq * SYMBOL_LEN + SAMPLE_RATE / 2) / SAMPLE_RATE; - return center_bin - num_tones(mode) / 2; - } - - static float frame_duration() { - return FRAME_SYMBOLS * (float)SYMBOL_LEN / SAMPLE_RATE; - } - - static int bitrate(MFSKMode mode) { - return (int)(max_payload(mode) * 8.0f / frame_duration()); - } -}; - - -namespace mfsk_detail { - -inline uint16_t crc16_ccitt(const uint8_t* data, size_t len) { - uint16_t crc = 0xFFFF; - for (size_t i = 0; i < len; i++) { - crc ^= (uint16_t)data[i] << 8; - for (int j = 0; j < 8; j++) - crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : crc << 1; - } - return crc; -} - - - -inline int gray_encode(int n) { return n ^ (n >> 1); } - - - - - -inline int gray_decode(int n) { - int mask = n; - while (mask) { mask >>= 1; n ^= mask; } - return n; -} - - -inline int bit_reverse(int x, int bits) { - int result = 0; - for (int i = 0; i < bits; i++) { result = (result << 1) | (x & 1); x >>= 1; } - return result; -} - -inline float goertzel_mag2(const float* samples, int N, int bin) { - float w = 2.0f * (float)M_PI * bin / N; - float coeff = 2.0f * cosf(w); - float s1 = 0.0f, s2 = 0.0f; - for (int i = 0; i < N; i++) { - float s0 = samples[i] + coeff * s1 - s2; - s2 = s1; - s1 = s0; - } - return s1 * s1 + s2 * s2 - coeff * s1 * s2; -} - -inline int parity8(uint8_t x) { - x ^= x >> 4; x ^= x >> 2; x ^= x >> 1; - return x & 1; -} - -inline void interleave(int* data, int n) { - int bits = 0; - while ((1 << bits) < n) bits++; - std::vector tmp(data, data + n); - for (int i = 0; i < n; i++) - data[bit_reverse(i, bits)] = tmp[i]; -} - -inline void interleave_vectors(std::vector>& data) { - int n = data.size(); - int bits = 0; - while ((1 << bits) < n) bits++; - std::vector> tmp(data); - for (int i = 0; i < n; i++) - data[bit_reverse(i, bits)] = std::move(tmp[i]); -} - -inline std::vector conv_encode(const uint8_t* data, int data_bytes) { - int data_bits = data_bytes * 8; - int total_in = data_bits + MFSKParams::CONV_TAIL; - std::vector coded; - coded.reserve(total_in * 2); - uint8_t state = 0; - for (int i = 0; i < total_in; i++) { - int bit = (i < data_bits) ? (data[i / 8] >> (7 - (i % 8))) & 1 : 0; - uint8_t inp = ((uint8_t)bit << 6) | state; - coded.push_back(parity8(inp & MFSKParams::CONV_G0)); - coded.push_back(parity8(inp & MFSKParams::CONV_G1)); - state = (((uint8_t)bit << 5) | (state >> 1)) & 0x3F; - } - return coded; -} - -class ViterbiDecoder { -public: - static constexpr int STATES = MFSKParams::CONV_STATES; - static constexpr int MAX_STEPS = 512; - - void reset() { - for (int s = 0; s < STATES; s++) metric_[s] = -1e30f; - metric_[0] = 0.0f; - len_ = 0; - } - - void step(float s0, float s1) { - float new_metric[STATES]; - for (int s = 0; s < STATES; s++) new_metric[s] = -1e30f; - for (int prev = 0; prev < STATES; prev++) { - if (metric_[prev] < -1e29f) continue; - for (int bit = 0; bit < 2; bit++) { - uint8_t inp = ((uint8_t)bit << 6) | (uint8_t)prev; - int c0 = parity8(inp & MFSKParams::CONV_G0); - int c1 = parity8(inp & MFSKParams::CONV_G1); - float branch = s0 * (1 - 2 * c0) + s1 * (1 - 2 * c1); - int next = (((uint8_t)bit << 5) | ((uint8_t)prev >> 1)) & 0x3F; - float candidate = metric_[prev] + branch; - if (candidate > new_metric[next]) { - new_metric[next] = candidate; - survivor_[len_][next] = ((uint8_t)prev << 1) | (uint8_t)bit; - } - } - } - memcpy(metric_, new_metric, sizeof(metric_)); - len_++; - } - - std::vector finish(int data_bits) { - int best = 0; - for (int s = 1; s < STATES; s++) - if (metric_[s] > metric_[best]) best = s; - std::vector bits(len_); - int state = best; - for (int t = len_ - 1; t >= 0; t--) { - bits[t] = survivor_[t][state] & 1; - state = survivor_[t][state] >> 1; - } - int n_bytes = data_bits / 8; - std::vector result(n_bytes, 0); - for (int i = 0; i < data_bits && i < len_; i++) - result[i / 8] |= bits[i] << (7 - (i % 8)); - return result; - } - -private: - float metric_[STATES]; - uint8_t survivor_[MAX_STEPS][STATES]; - int len_ = 0; -}; - -inline void soft_demap(const float* energies, int n_tones, int bps, float* soft_bits) { - for (int j = 0; j < bps; j++) { - float e0 = 0, e1 = 0; - int bit_pos = bps - 1 - j; - for (int t = 0; t < n_tones; t++) { - int sym_val = gray_decode(t); - if ((sym_val >> bit_pos) & 1) - e1 += energies[t]; - else - e0 += energies[t]; - } - soft_bits[j] = (e0 - e1) / (e0 + e1 + 1e-20f); - } -} - -inline std::vector puncture_34(const std::vector& coded) { - std::vector out; - out.reserve(coded.size() * 2 / 3 + 4); - for (size_t i = 0; i + 5 < coded.size(); i += 6) { - out.push_back(coded[i]); - out.push_back(coded[i+1]); - out.push_back(coded[i+2]); - out.push_back(coded[i+5]); - } - size_t rem = (coded.size() / 6) * 6; - for (size_t i = rem; i < coded.size(); i++) out.push_back(coded[i]); - return out; -} - -inline std::vector depuncture_34(const float* soft, int n_soft) { - std::vector out; - out.reserve(n_soft * 3 / 2 + 8); - int si = 0; - while (si + 3 < n_soft) { - out.push_back(soft[si++]); - out.push_back(soft[si++]); - out.push_back(soft[si++]); - out.push_back(0.0f); - out.push_back(0.0f); - out.push_back(soft[si++]); - } - - - - - - while (si < n_soft) { out.push_back(soft[si++]); out.push_back(0.0f); } - return out; -} - -inline std::vector bits_to_gray_symbols(const std::vector& bits, int bps) { - std::vector symbols; - for (int i = 0; i + bps <= (int)bits.size(); i += bps) { - int sym = 0; - for (int b = 0; b < bps; b++) - sym = (sym << 1) | bits[i + b]; - symbols.push_back(gray_encode(sym)); - } - return symbols; -} - -} // namespace mfsk_detail - - -class MFSKEncoder { -public: - std::vector encode(const uint8_t* data, size_t len, - int center_freq, MFSKMode mode) { - using namespace mfsk_detail; - - int n_tones = MFSKParams::num_tones(mode); - int bps = MFSKParams::bits_per_symbol(mode); - int dbytes = MFSKParams::data_bytes(mode); - int base = MFSKParams::base_bin(mode, center_freq); - - int payload_len = dbytes - 2; - std::vector frame(payload_len, 0); - memcpy(frame.data(), data, std::min(len, (size_t)payload_len)); - - uint16_t crc = crc16_ccitt(frame.data(), frame.size()); - frame.push_back(crc >> 8); - frame.push_back(crc & 0xFF); - - auto coded_bits = conv_encode(frame.data(), dbytes); - if (MFSKParams::is_rate34(mode)) - coded_bits = puncture_34(coded_bits); - - int capacity = MFSKParams::DATA_SYMBOLS * bps; - while ((int)coded_bits.size() < capacity) - coded_bits.push_back(0); - - auto symbols = bits_to_gray_symbols(coded_bits, bps); - symbols.resize(MFSKParams::DATA_SYMBOLS, 0); - interleave(symbols.data(), symbols.size()); - - std::vector frame_tones; - frame_tones.reserve(MFSKParams::FRAME_SYMBOLS); - for (int i = 0; i < MFSKParams::PREAMBLE_SYMBOLS; i++) - frame_tones.push_back(i % 2 == 0 ? 0 : n_tones - 1); - frame_tones.push_back(n_tones / 4); - frame_tones.push_back(3 * n_tones / 4); - frame_tones.insert(frame_tones.end(), symbols.begin(), symbols.end()); - - return generate_audio(frame_tones, base); - } - - int get_payload_size(MFSKMode mode) { - return MFSKParams::frame_capacity(mode); - } - -private: - float phase_ = 0.0f; - - std::vector generate_audio(const std::vector& tones, int base_bin) { - const int N = MFSKParams::SYMBOL_LEN; - const int G = MFSKParams::GUARD_SAMPLES; - const float amp = 0.8f; - std::vector audio; - audio.reserve(tones.size() * N); - phase_ = 0.0f; - for (int tone : tones) { - float freq = (base_bin + tone) * MFSKParams::TONE_SPACING; - float phase_inc = 2.0f * (float)M_PI * freq / MFSKParams::SAMPLE_RATE; - for (int i = 0; i < N; i++) { - float env = 1.0f; - if (i < G) - env = 0.5f * (1.0f - cosf((float)M_PI * i / G)); - else if (i >= N - G) - env = 0.5f * (1.0f + cosf((float)M_PI * (i - N + G) / G)); - audio.push_back(amp * env * sinf(phase_)); - phase_ += phase_inc; - } - phase_ = fmodf(phase_, 2.0f * (float)M_PI); - } - return audio; - } -}; - - -class MFSKDecoder { -public: - using FrameCallback = std::function; - - MFSKDecoder() {} - MFSKDecoder(MFSKMode mode, int center_freq = 1500) { configure(mode, center_freq); } - - void configure(MFSKMode mode, int center_freq) { - mode_ = mode; - n_tones_ = MFSKParams::num_tones(mode); - bps_ = MFSKParams::bits_per_symbol(mode); - base_bin_ = MFSKParams::base_bin(mode, center_freq); - sync1_tone_ = n_tones_ / 4; - sync3_tone_ = 3 * n_tones_ / 4; - reset(); - } - - void process(const float* samples, size_t count, FrameCallback callback) { - buf_.insert(buf_.end(), samples, samples + count); - - while (true) { - if (buf_.size() - buf_pos_ < (size_t)MFSKParams::SYMBOL_LEN) - break; - - const float* window = buf_.data() + buf_pos_; - - switch (state_) { - case State::SEARCHING: { - float e0 = mfsk_detail::goertzel_mag2(window, MFSKParams::SYMBOL_LEN, base_bin_); - float en = mfsk_detail::goertzel_mag2(window, MFSKParams::SYMBOL_LEN, base_bin_ + n_tones_ - 1); - float eq1 = mfsk_detail::goertzel_mag2(window, MFSKParams::SYMBOL_LEN, base_bin_ + sync1_tone_); - float eq3 = mfsk_detail::goertzel_mag2(window, MFSKParams::SYMBOL_LEN, base_bin_ + sync3_tone_); - - int p = step_count_ % 4; - update_tracker(p, e0, en, eq1, eq3); - - if (trackers_[p].tstate == TState::READY) { - ++stats_sync_count; - - freq_offset_ = 0; - float best_afc_e = 0; - for (int foff = -3; foff <= 3; foff++) { - float e = mfsk_detail::goertzel_mag2(window, MFSKParams::SYMBOL_LEN, - base_bin_ + foff + sync3_tone_); - if (e > best_afc_e) { - best_afc_e = e; - freq_offset_ = foff; - } - } - - int best_off = 0; - float best_e = 0; - int sync_bin = base_bin_ + freq_offset_ + sync3_tone_; - int half_step = MFSKParams::SEARCH_STEP / 2; - for (int off = -half_step; off <= half_step; off += 16) { - int64_t pos = (int64_t)buf_pos_ + off; - if (pos < 0 || pos + MFSKParams::SYMBOL_LEN > (int64_t)buf_.size()) - continue; - float e = mfsk_detail::goertzel_mag2( - buf_.data() + pos, MFSKParams::SYMBOL_LEN, sync_bin); - if (e > best_e) { - best_e = e; - best_off = off; - } - } - if (best_off >= 0) - buf_pos_ += best_off; - else - buf_pos_ -= (size_t)(-best_off); - - std::cerr << "MFSK: Sync (phase " << p - << " t=" << best_off - << " f=" << freq_offset_ << ")" << std::endl; - - buf_pos_ += MFSKParams::SYMBOL_LEN; - state_ = State::COLLECTING; - collect_count_ = 0; - collected_.clear(); - collected_.reserve(MFSKParams::DATA_SYMBOLS); - reset_trackers(); - continue; - } - - step_count_++; - buf_pos_ += MFSKParams::SEARCH_STEP; - break; - } - - case State::COLLECTING: { - if (collect_count_ == 0) - collect_start_pos_ = buf_pos_; - - if (collect_count_ > 0 && (collect_count_ % 16) == 0) { - float center_ratio = 0, best_ratio = 0; - int best_adj = 0; - for (int adj = -32; adj <= 32; adj += 8) { - int64_t pos = (int64_t)buf_pos_ + adj; - if (pos < 0 || pos + MFSKParams::SYMBOL_LEN > (int64_t)buf_.size()) - continue; - const float* w = buf_.data() + pos; - float max_e = 0, total_e = 0; - for (int t = 0; t < n_tones_; t++) { - float e = mfsk_detail::goertzel_mag2(w, MFSKParams::SYMBOL_LEN, base_bin_ + freq_offset_ + t); - total_e += e; - if (e > max_e) max_e = e; - } - float ratio = max_e / (total_e + 1e-20f); - if (adj == 0) center_ratio = ratio; - if (ratio > best_ratio) { best_ratio = ratio; best_adj = adj; } - } - if (best_adj != 0 && best_ratio > center_ratio * 1.02f) { - if (best_adj >= 0) buf_pos_ += best_adj; - else buf_pos_ -= (size_t)(-best_adj); - window = buf_.data() + buf_pos_; - } - } - - std::vector energies(n_tones_); - for (int t = 0; t < n_tones_; t++) - energies[t] = mfsk_detail::goertzel_mag2(window, MFSKParams::SYMBOL_LEN, - base_bin_ + freq_offset_ + t); - collected_.push_back(std::move(energies)); - collect_count_++; - buf_pos_ += MFSKParams::SYMBOL_LEN; - - if (collect_count_ >= MFSKParams::DATA_SYMBOLS) { - bool decoded = try_decode_auto(callback); - if (!decoded) { - for (int retry_off : {8, -8, 16, -16}) { - if (recompute_with_offset(retry_off) && try_decode_auto(callback)) { - decoded = true; - break; - } - } - } - if (!decoded) ++stats_crc_errors; - state_ = State::SEARCHING; - step_count_ = 0; - } - break; - } - } - } - - if (buf_pos_ > 8192 && state_ == State::SEARCHING) { - buf_.erase(buf_.begin(), buf_.begin() + buf_pos_); - buf_pos_ = 0; - } - } - - void reset() { - buf_.clear(); - buf_pos_ = 0; - state_ = State::SEARCHING; - step_count_ = 0; - collect_count_ = 0; - freq_offset_ = 0; - collected_.clear(); - reset_trackers(); - } - - float get_last_snr() const { return last_snr_; } - float get_last_ber() const { return last_ber_; } - float get_ber_ema() const { return ber_ema_; } - - int stats_sync_count = 0; - int stats_preamble_errors = 0; - int stats_crc_errors = 0; - - void reset_stats() { - stats_sync_count = 0; - stats_preamble_errors = 0; - stats_crc_errors = 0; - last_snr_ = 0; - last_ber_ = -1; - ber_ema_ = -1; - } - -private: - MFSKMode mode_ = MFSKMode::MFSK_16; - int n_tones_ = 16; - int bps_ = 4; - int base_bin_ = 40; - int freq_offset_ = 0; - int sync1_tone_ = 4; - int sync3_tone_ = 12; - - std::vector buf_; - size_t buf_pos_ = 0; - - enum class State { SEARCHING, COLLECTING }; - State state_ = State::SEARCHING; - int step_count_ = 0; - - int collect_count_ = 0; - size_t collect_start_pos_ = 0; - std::vector> collected_; - - float last_snr_ = 0; - float last_ber_ = -1; - float ber_ema_ = -1; - - enum class TState { PREAMBLE, SYNC1, SYNC2, READY }; - struct Tracker { TState tstate = TState::PREAMBLE; int count = 0; int last_tone = -1; }; - Tracker trackers_[4]; - - void reset_trackers() { - for (auto& t : trackers_) { t.tstate = TState::PREAMBLE; t.count = 0; t.last_tone = -1; } - } - - - - - - void update_tracker(int p, float e0, float en, float eq1, float eq3) { - auto& t = trackers_[p]; - float total = e0 + en + eq1 + eq3 + 1e-20f; - - switch (t.tstate) { - case TState::PREAMBLE: { - bool low_dom = (e0 / total > 0.4f); - bool high_dom = (en / total > 0.4f); - if (low_dom && !high_dom) { - t.count = (t.last_tone == 1) ? t.count + 1 : 1; - t.last_tone = 0; - } else if (high_dom && !low_dom) { - t.count = (t.last_tone == 0) ? t.count + 1 : 1; - t.last_tone = 1; - } else { - t.count = 0; t.last_tone = -1; - } - if (t.count >= MFSKParams::PREAMBLE_SYMBOLS) - t.tstate = TState::SYNC1; - break; - } - case TState::SYNC1: - if (eq1 / total > 0.25f) { - t.tstate = TState::SYNC2; - } else { - bool low_dom = (e0 / total > 0.4f); - bool high_dom = (en / total > 0.4f); - if ((low_dom && t.last_tone == 1) || (high_dom && t.last_tone == 0)) - t.last_tone = low_dom ? 0 : 1; - else { ++stats_preamble_errors; t.tstate = TState::PREAMBLE; t.count = 0; t.last_tone = -1; } - } - break; - case TState::SYNC2: - if (eq3 / total > 0.25f) - t.tstate = TState::READY; - else { ++stats_preamble_errors; t.tstate = TState::PREAMBLE; t.count = 0; t.last_tone = -1; } - break; - case TState::READY: - break; - } - } - - bool try_decode_auto(FrameCallback callback) { - if (try_decode(callback, mode_)) return true; - if (n_tones_ == 32) { - MFSKMode alt = MFSKParams::is_rate34(mode_) ? MFSKMode::MFSK_32 : MFSKMode::MFSK_32R; - if (try_decode(callback, alt)) return true; - } - return false; - } - - bool recompute_with_offset(int offset) { - collected_.clear(); - collected_.reserve(MFSKParams::DATA_SYMBOLS); - int64_t pos = (int64_t)collect_start_pos_ + offset; - if (pos < 0) return false; - - for (int i = 0; i < MFSKParams::DATA_SYMBOLS; i++) { - if ((size_t)pos + MFSKParams::SYMBOL_LEN > buf_.size()) return false; - const float* w = buf_.data() + pos; - std::vector energies(n_tones_); - for (int t = 0; t < n_tones_; t++) - energies[t] = mfsk_detail::goertzel_mag2(w, MFSKParams::SYMBOL_LEN, base_bin_ + freq_offset_ + t); - collected_.push_back(std::move(energies)); - pos += MFSKParams::SYMBOL_LEN; - } - return true; - } - - bool try_decode(FrameCallback callback, MFSKMode decode_mode) { - using namespace mfsk_detail; - - auto deinterleaved = collected_; - interleave_vectors(deinterleaved); - - int total_soft = MFSKParams::DATA_SYMBOLS * bps_; - std::vector soft_wire(total_soft); - float soft_buf[12]; - for (int i = 0; i < MFSKParams::DATA_SYMBOLS; i++) { - soft_demap(deinterleaved[i].data(), n_tones_, bps_, soft_buf); - for (int b = 0; b < bps_; b++) - soft_wire[i * bps_ + b] = soft_buf[b]; - } - - std::vector soft_full; - const float* soft_ptr; - int soft_len; - if (MFSKParams::is_rate34(decode_mode)) { - soft_full = depuncture_34(soft_wire.data(), total_soft); - soft_ptr = soft_full.data(); - soft_len = (int)soft_full.size(); - } else { - soft_ptr = soft_wire.data(); - soft_len = total_soft; - } - - int dbytes = MFSKParams::data_bytes(decode_mode); - int data_bits = dbytes * 8; - int n_steps = data_bits + MFSKParams::CONV_TAIL; - - ViterbiDecoder viterbi; - viterbi.reset(); - for (int s = 0; s < n_steps && s * 2 + 1 < soft_len; s++) - viterbi.step(soft_ptr[s * 2], soft_ptr[s * 2 + 1]); - - auto bytes = viterbi.finish(data_bits); - bytes.resize(dbytes, 0); - - uint16_t computed = crc16_ccitt(bytes.data(), dbytes - 2); - uint16_t received = ((uint16_t)bytes[dbytes - 2] << 8) | bytes[dbytes - 1]; - if (computed != received) return false; - - auto re_coded = conv_encode(bytes.data(), dbytes); - if (MFSKParams::is_rate34(decode_mode)) - re_coded = puncture_34(re_coded); - int re_capacity = MFSKParams::DATA_SYMBOLS * bps_; - while ((int)re_coded.size() < re_capacity) re_coded.push_back(0); - auto expected_tones = bits_to_gray_symbols(re_coded, bps_); - expected_tones.resize(MFSKParams::DATA_SYMBOLS, 0); - - float signal_e = 0, noise_e = 0; - for (int i = 0; i < MFSKParams::DATA_SYMBOLS; i++) { - int expected = expected_tones[i]; - float total_e = 0; - for (int t = 0; t < n_tones_; t++) total_e += deinterleaved[i][t]; - signal_e += deinterleaved[i][expected]; - noise_e += (total_e - deinterleaved[i][expected]); - } - - if (noise_e > 1e-10f) { - float sig = signal_e / MFSKParams::DATA_SYMBOLS; - float noi = noise_e / (MFSKParams::DATA_SYMBOLS * (n_tones_ - 1)); - last_snr_ = 10.0f * log10f(sig / noi); - } else { - last_snr_ = 50.0f; - } - - std::cerr << "MFSK: Decoded SNR=" << (int)last_snr_ << "dB" << std::endl; - callback(bytes.data(), dbytes - 2); - return true; - } -}; diff --git a/tnc_ui.hh b/tnc_ui.hh index 4af2698..6f5e29e 100644 --- a/tnc_ui.hh +++ b/tnc_ui.hh @@ -25,13 +25,9 @@ #include #include "kiss_tnc.hh" -#include "phy/mfsk_modem.hh" constexpr size_t MAX_LOG_ENTRIES = 500; -const std::vector MODEM_TYPE_OPTIONS = {"OFDM", "MFSK"}; -const std::vector MFSK_MODE_OPTIONS = {"MFSK-8", "MFSK-16", "MFSK-32", "MFSK-32R"}; - const std::vector MODULATION_OPTIONS = { "BPSK", "QPSK", "8PSK", "QAM16", "QAM64", "QAM256", "QAM1024", "QAM4096" }; @@ -53,11 +49,9 @@ const std::vector PTT_LINE_OPTIONS = { struct TNCUIState { std::string callsign = "N0CALL"; - int modem_type_index = 0; // 0=OFDM, 1=MFSK - int mfsk_mode_index = 1; // 0=MFSK-8, 1=MFSK-16, 2=MFSK-32, 3=MFSK-32R - int modulation_index = 1; // default QPSK N 1/2 - int code_rate_index = 0; - bool short_frame = false; + int modulation_index = 1; // default QSPK N 1/2 + int code_rate_index = 0; + bool short_frame = false; int center_freq = 1500; bool csma_enabled = true; @@ -120,10 +114,7 @@ struct TNCUIState { // Presets struct Preset { std::string name; - // Modem type - int modem_type_index = 0; // 0=OFDM, 1=MFSK - int mfsk_mode_index = 1; // 0=MFSK-8, 1=MFSK-16, 2=MFSK-32, 3=MFSK-32R - // OFDM modem + // Modem int modulation_index; int code_rate_index; bool short_frame; @@ -293,17 +284,6 @@ struct TNCUIState { // TEMP modem tables void update_modem_info() { - // MFSK mode - if (modem_type_index == 1) { - MFSKMode mmode = (MFSKMode)mfsk_mode_index; - mtu_bytes = MFSKParams::max_payload(mmode); - bitrate_bps = MFSKParams::bitrate(mmode); - airtime_seconds = MFSKParams::frame_duration(); - if (random_data_size == 0 || (!fragmentation_enabled && random_data_size > mtu_bytes)) - random_data_size = mtu_bytes; - return; - } - // Modulations: BPSK=0, QPSK=1, 8PSK=2, QAM16=3, QAM64=4, QAM256=5, QAM1024=6, QAM4096=7 // Code rates: 1/2=0, 2/3=1, 3/4=2, 5/6=3, 1/4=4 // Columns: [1/2, 2/3, 3/4, 5/6, 1/4] @@ -445,8 +425,6 @@ struct TNCUIState { fprintf(f, "# MODEM73 Settings\n"); fprintf(f, "callsign=%s\n", callsign.c_str()); - fprintf(f, "modem_type=%d\n", modem_type_index); - fprintf(f, "mfsk_mode=%d\n", mfsk_mode_index); fprintf(f, "modulation=%d\n", modulation_index); fprintf(f, "code_rate=%d\n", code_rate_index); fprintf(f, "short_frame=%d\n", short_frame ? 1 : 0); @@ -496,8 +474,6 @@ struct TNCUIState { char key[64], value[192]; if (sscanf(line, "%63[^=]=%191[^\n]", key, value) == 2) { if (strcmp(key, "callsign") == 0) callsign = value; - else if (strcmp(key, "modem_type") == 0) modem_type_index = atoi(value); - else if (strcmp(key, "mfsk_mode") == 0) mfsk_mode_index = atoi(value); else if (strcmp(key, "modulation") == 0) modulation_index = atoi(value); else if (strcmp(key, "code_rate") == 0) code_rate_index = atoi(value); else if (strcmp(key, "short_frame") == 0) short_frame = atoi(value) != 0; @@ -544,8 +520,8 @@ struct TNCUIState { fprintf(f, "# MODEM73 Presets \n"); for (const auto& p : presets) { - // name,mod,rate,sf,freq,csma,thresh,slot,persist,ptt,vox_freq,vox_lead,vox_tail,modem_type,mfsk_mode - fprintf(f, "preset=%s,%d,%d,%d,%d,%d,%.1f,%d,%d,%d,%d,%d,%d,%d,%d\n", + // name,mod,rate,sf,freq,csma,thresh,slot,persist,ptt,vox_freq,vox_lead,vox_tail + fprintf(f, "preset=%s,%d,%d,%d,%d,%d,%.1f,%d,%d,%d,%d,%d,%d\n", p.name.c_str(), p.modulation_index, p.code_rate_index, @@ -558,9 +534,7 @@ struct TNCUIState { p.ptt_type_index, p.vox_tone_freq, p.vox_lead_ms, - p.vox_tail_ms, - p.modem_type_index, - p.mfsk_mode_index); + p.vox_tail_ms); } fclose(f); @@ -583,16 +557,14 @@ struct TNCUIState { char name[64]; int mod, rate, sf, freq, csma, slot, persist; - int ptt_type = 1, vox_freq = 1200, vox_lead = 150, vox_tail = 100; - int modem_type = 0, mfsk_mode = 1; + int ptt_type = 1, vox_freq = 1200, vox_lead = 150, vox_tail = 100; float thresh; - - int n = sscanf(line + 7, "%63[^,],%d,%d,%d,%d,%d,%f,%d,%d,%d,%d,%d,%d,%d,%d", + + int n = sscanf(line + 7, "%63[^,],%d,%d,%d,%d,%d,%f,%d,%d,%d,%d,%d,%d", name, &mod, &rate, &sf, &freq, &csma, &thresh, &slot, &persist, - &ptt_type, &vox_freq, &vox_lead, &vox_tail, - &modem_type, &mfsk_mode); - - if (n >= 9) { + &ptt_type, &vox_freq, &vox_lead, &vox_tail); + + if (n >= 9) { Preset p; p.name = name; p.modulation_index = mod; @@ -608,10 +580,6 @@ struct TNCUIState { p.vox_tone_freq = (n >= 11) ? vox_freq : 1200; p.vox_lead_ms = (n >= 12) ? vox_lead : 150; p.vox_tail_ms = (n >= 13) ? vox_tail : 100; - - - p.modem_type_index = (n >= 14) ? modem_type : 0; - p.mfsk_mode_index = (n >= 15) ? mfsk_mode : 1; presets.push_back(p); } } @@ -632,8 +600,6 @@ struct TNCUIState { Preset p; p.name = name; - p.modem_type_index = modem_type_index; - p.mfsk_mode_index = mfsk_mode_index; p.modulation_index = modulation_index; p.code_rate_index = code_rate_index; p.short_frame = short_frame; @@ -657,8 +623,6 @@ struct TNCUIState { if (index < 0 || index >= (int)presets.size()) return false; const Preset& p = presets[index]; - modem_type_index = p.modem_type_index; - mfsk_mode_index = p.mfsk_mode_index; modulation_index = p.modulation_index; code_rate_index = p.code_rate_index; short_frame = p.short_frame; @@ -789,11 +753,9 @@ public: private: enum Field { FIELD_CALLSIGN = 0, - FIELD_MODEM_TYPE, FIELD_MODULATION, FIELD_CODERATE, FIELD_FRAMESIZE, - FIELD_MFSK_MODE, FIELD_FREQ, FIELD_CSMA, FIELD_THRESHOLD, @@ -889,7 +851,7 @@ private: state_.selected_preset = state_.presets.size() - 1; } } - } else if (current_field_ >= FIELD_MODEM_TYPE && current_field_ != FIELD_PRESET) { + } else if (current_field_ >= FIELD_MODULATION && current_field_ != FIELD_PRESET) { adjust_field(-1); } } else if (current_tab_ == 3 && (utils_selection_ == 0 || utils_selection_ == 1)) { @@ -910,7 +872,7 @@ private: state_.selected_preset = 0; } } - } else if (current_field_ >= FIELD_MODEM_TYPE && current_field_ != FIELD_PRESET) { + } else if (current_field_ >= FIELD_MODULATION && current_field_ != FIELD_PRESET) { adjust_field(1); } } else if (current_tab_ == 3 && (utils_selection_ == 0 || utils_selection_ == 1)) { @@ -1254,15 +1216,6 @@ private: } bool should_skip_field(int field) { - // Hide OFDM-only fields when in MFSK mode - if (state_.modem_type_index == 1) { - if (field == FIELD_MODULATION || field == FIELD_CODERATE || field == FIELD_FRAMESIZE) - return true; - } - // Hide MFSK-only fields when in OFDM mode - if (state_.modem_type_index == 0) { - if (field == FIELD_MFSK_MODE) return true; - } if (state_.ptt_type_index != 2) { // not VOX if (field == FIELD_VOX_FREQ || field == FIELD_VOX_LEAD || field == FIELD_VOX_TAIL) { return true; @@ -1285,14 +1238,6 @@ private: void adjust_field(int delta) { switch (current_field_) { - case FIELD_MODEM_TYPE: - state_.modem_type_index = (state_.modem_type_index + delta + 2) % 2; - state_.update_modem_info(); - break; - case FIELD_MFSK_MODE: - state_.mfsk_mode_index = (state_.mfsk_mode_index + delta + 4) % 4; - state_.update_modem_info(); - break; case FIELD_MODULATION: state_.modulation_index = (state_.modulation_index + delta + 8) % 8; break; @@ -1684,17 +1629,11 @@ private: attron(A_BOLD); addstr(state_.callsign.c_str()); attroff(A_BOLD); - if (state_.modem_type_index == 1) { - printw(" %s %dHz", - MFSK_MODE_OPTIONS[state_.mfsk_mode_index].c_str(), - state_.center_freq); - } else { - printw(" %s %s %s %dHz", - MODULATION_OPTIONS[state_.modulation_index].c_str(), - CODE_RATE_OPTIONS[state_.code_rate_index].c_str(), - state_.short_frame ? "S" : "N", - state_.center_freq); - } + printw(" %s %s %s %dHz", + MODULATION_OPTIONS[state_.modulation_index].c_str(), + CODE_RATE_OPTIONS[state_.code_rate_index].c_str(), + state_.short_frame ? "S" : "N", + state_.center_freq); // Stats int rx = cols - 20; @@ -2231,20 +2170,12 @@ private: row++; // header if (field == FIELD_CALLSIGN) return row; row++; - if (field == FIELD_MODEM_TYPE) return row; + if (field == FIELD_MODULATION) return row; + row++; + if (field == FIELD_CODERATE) return row; + row++; + if (field == FIELD_FRAMESIZE) return row; row++; - if (state_.modem_type_index == 0) { - if (field == FIELD_MODULATION) return row; - row++; - if (field == FIELD_CODERATE) return row; - row++; - if (field == FIELD_FRAMESIZE) return row; - row++; - } else { - // MFSK field - if (field == FIELD_MFSK_MODE) return row; - row++; - } if (field == FIELD_FREQ) return row; row += 2; // CSMA section @@ -2338,36 +2269,22 @@ private: dy = visible_y(row); if (dy >= 0) draw_field(dy, c1, c2, "Callsign", FIELD_CALLSIGN, state_.callsign, true); row++; - + dy = visible_y(row); - if (dy >= 0) draw_selector_field(dy, c1, c2, "Modem", FIELD_MODEM_TYPE, - MODEM_TYPE_OPTIONS[state_.modem_type_index]); + if (dy >= 0) draw_selector_field(dy, c1, c2, "Modulation", FIELD_MODULATION, + MODULATION_OPTIONS[state_.modulation_index]); row++; - - if (state_.modem_type_index == 0) { - // OFDM fields - dy = visible_y(row); - if (dy >= 0) draw_selector_field(dy, c1, c2, "Modulation", FIELD_MODULATION, - MODULATION_OPTIONS[state_.modulation_index]); - row++; - - dy = visible_y(row); - if (dy >= 0) draw_selector_field(dy, c1, c2, "Code Rate", FIELD_CODERATE, - CODE_RATE_OPTIONS[state_.code_rate_index]); - row++; - - dy = visible_y(row); - if (dy >= 0) draw_selector_field(dy, c1, c2, "Frame Size", FIELD_FRAMESIZE, - state_.short_frame ? "SHORT" : "NORMAL"); - row++; - } else { - // MFSK field - dy = visible_y(row); - if (dy >= 0) draw_selector_field(dy, c1, c2, "MFSK Mode", FIELD_MFSK_MODE, - MFSK_MODE_OPTIONS[state_.mfsk_mode_index]); - row++; - } - + + dy = visible_y(row); + if (dy >= 0) draw_selector_field(dy, c1, c2, "Code Rate", FIELD_CODERATE, + CODE_RATE_OPTIONS[state_.code_rate_index]); + row++; + + dy = visible_y(row); + if (dy >= 0) draw_selector_field(dy, c1, c2, "Frame Size", FIELD_FRAMESIZE, + state_.short_frame ? "SHORT" : "NORMAL"); + row++; + dy = visible_y(row); if (dy >= 0) { char freq_buf[32]; @@ -2436,6 +2353,12 @@ private: } row++; + dy = visible_y(row); + if (dy >= 0) { + attron(A_DIM); + mvaddstr(dy, c1, "Both sides must have frag enabled"); + attroff(A_DIM); + } row++; dy = visible_y(row); @@ -2646,35 +2569,6 @@ private: printw(" TX "); if (tx_time < 60) printw("%.0fs", tx_time); else printw("%.1fm", tx_time / 60.0f); - y++; - - - - - { - bool hf_ok = (state_.modem_type_index == 1) || - (state_.modulation_index <= 2); // BPSK, QPSK, 8PSK - mvaddstr(y, c3, "Band "); - if (hf_ok) { - attron(COLOR_PAIR(3) | A_BOLD); - addstr("HF/VHF"); - attroff(COLOR_PAIR(3) | A_BOLD); - } else { - attron(A_DIM); - addstr("HF/VHF"); - attroff(A_DIM); - } - addstr(" "); - if (!hf_ok) { - attron(COLOR_PAIR(3) | A_BOLD); - addstr("VHF/UHF"); - attroff(COLOR_PAIR(3) | A_BOLD); - } else { - attron(A_DIM); - addstr("VHF/UHF"); - attroff(A_DIM); - } - } y += 2; // Right side, for audio / ptt status