only copy data tones for constellation diagram, density fix

This commit is contained in:
zenith 2026-02-17 13:23:46 -05:00
commit 042b6ce6c6
2 changed files with 42 additions and 26 deletions

View file

@ -140,13 +140,14 @@ public:
// Set up constellation callback for UI display // Set up constellation callback for UI display
#ifdef WITH_UI #ifdef WITH_UI
decoder_->constellation_callback = [](const DSP::Complex<float>* symbols, int count, int mod_bits) { decoder_->constellation_callback = [this](const DSP::Complex<float>* symbols, int count, int mod_bits) {
if (g_ui_state) { if (g_ui_state) {
// DSP::Complex<float> is layout-compatible with std::complex<float> // DSP::Complex<float> is layout-compatible with std::complex<float>
g_ui_state->update_constellation( g_ui_state->update_constellation(
reinterpret_cast<const std::complex<float>*>(symbols), reinterpret_cast<const std::complex<float>*>(symbols),
count, count,
mod_bits mod_bits,
decoder_->seed_off
); );
} }
}; };
@ -890,8 +891,7 @@ public:
config_.carrier_threshold_db = new_config.carrier_threshold_db; config_.carrier_threshold_db = new_config.carrier_threshold_db;
config_.p_persistence = new_config.p_persistence; config_.p_persistence = new_config.p_persistence;
config_.slot_time_ms = new_config.slot_time_ms; config_.slot_time_ms = new_config.slot_time_ms;
// TX blanking // TX blanking
config_.tx_blanking_enabled = new_config.tx_blanking_enabled; config_.tx_blanking_enabled = new_config.tx_blanking_enabled;

View file

@ -176,23 +176,33 @@ struct TNCUIState {
std::atomic<bool> constellation_valid{false}; std::atomic<bool> constellation_valid{false};
std::atomic<int64_t> constellation_update_time{0}; std::atomic<int64_t> constellation_update_time{0};
void update_constellation(const std::complex<float>* points, int count, int mod_bits) { void update_constellation(const std::complex<float>* points, int count, int mod_bits, int seed_off = -1) {
std::lock_guard<std::mutex> lock(constellation_mutex); std::lock_guard<std::mutex> lock(constellation_mutex);
// Copy points // copy data tones only
int n = std::min(count, CONSTELLATION_SIZE); static const int BLOCK_LEN = 5; // from Common::block_length
for (int i = 0; i < n; ++i) { int n = 0;
constellation_points[i] = points[i]; for (int i = 0; i < count && n < CONSTELLATION_SIZE; ++i) {
if (seed_off >= 0 && (i % BLOCK_LEN) == seed_off) continue;
constellation_points[n++] = points[i];
} }
// Build density map // Build density map
constellation_density.fill(0); constellation_density.fill(0);
// Scale factor based on modulation (higher order = larger spread) // Scale factor matched to actual constellation extents + headroom for noise
float scale = 1.5f; float scale;
if (mod_bits >= 4) scale = 2.0f; // QAM16+ switch (mod_bits) {
if (mod_bits >= 6) scale = 2.5f; // QAM64+ case 1: scale = 1.5f; break; // BPSK (extent 1.00)
if (mod_bits >= 8) scale = 3.0f; // QAM256+ case 2: scale = 1.3f; break; // QPSK (extent 0.71)
case 3: scale = 1.5f; break; // 8PSK (extent 0.92)
case 4: scale = 1.7f; break; // QAM16 (extent 0.95)
case 6: scale = 2.0f; break; // QAM64 (extent 1.08)
case 8: scale = 2.3f; break; // QAM256 (extent 1.15)
case 10: scale = 2.5f; break; // QAM1024 (extent 1.19)
case 12: scale = 2.5f; break; // QAM4096 (extent 1.21)
default: scale = 1.5f; break;
}
int half = CONSTELLATION_GRID / 2; int half = CONSTELLATION_GRID / 2;
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
@ -2880,18 +2890,17 @@ private:
int gx = (int)(dx * scale_x); int gx = (int)(dx * scale_x);
int gy = (int)(dy * scale_y); int gy = (int)(dy * scale_y);
// Accumulate density (handles downscaling) // Ensure at least 1 grid cell per display cell (prevents striping)
int gx_end = std::max(gx + 1, std::min((int)((dx + 1) * scale_x), grid_size));
int gy_end = std::max(gy + 1, std::min((int)((dy + 1) * scale_y), grid_size));
int density = 0; int density = 0;
int samples = 0;
int gx_end = std::min((int)((dx + 1) * scale_x), grid_size);
int gy_end = std::min((int)((dy + 1) * scale_y), grid_size);
for (int sy = gy; sy < gy_end; ++sy) { for (int sy = gy; sy < gy_end; ++sy) {
for (int sx = gx; sx < gx_end; ++sx) { for (int sx = gx; sx < gx_end; ++sx) {
density += state_.constellation_density[sy * grid_size + sx]; int d = state_.constellation_density[sy * grid_size + sx];
samples++; if (d > density) density = d;
} }
} }
if (samples > 0) density /= samples;
// Map density to character // Map density to character
int char_idx = (density * (num_chars - 1)) / peak; int char_idx = (density * (num_chars - 1)) / peak;
@ -2915,12 +2924,18 @@ private:
} }
} }
// Draw center crosshair // Draw center crosshair (only if cell is empty)
int mid_y = height / 2; int mid_y = height / 2;
int mid_x = width / 2; int mid_x = width / 2;
attron(A_DIM); int mid_gx = (int)(mid_x * scale_x);
mvaddch(y + 1 + mid_y, x + 1 + mid_x, '+'); int mid_gy = (int)(mid_y * scale_y);
attroff(A_DIM); bool center_empty = (mid_gx < grid_size && mid_gy < grid_size &&
state_.constellation_density[mid_gy * grid_size + mid_gx] == 0);
if (center_empty) {
attron(A_DIM);
mvaddch(y + 1 + mid_y, x + 1 + mid_x, '+');
attroff(A_DIM);
}
// Show modulation name in top-right of box // Show modulation name in top-right of box
const char* mod_name = ""; const char* mod_name = "";
@ -2935,8 +2950,9 @@ private:
case 12: mod_name = "QAM4096"; break; case 12: mod_name = "QAM4096"; break;
} }
if (mod_name[0]) { if (mod_name[0]) {
int name_len = strlen(mod_name);
attron(A_DIM); attron(A_DIM);
mvaddstr(y, x + width - 6, mod_name); mvaddstr(y, x + width + 1 - name_len, mod_name);
attroff(A_DIM); attroff(A_DIM);
} }
} }