@@ -883,142 +883,145 @@ soft_symbol_frame_begin(dsd_state* state) {
883883}
884884
885885/**
886- * \brief Convert a soft symbol to Viterbi cost metric.
886+ * \brief Map log-likelihood ratio proxy to 16-bit Viterbi metric.
887887 *
888- * For 4-level modulations (C4FM, GFSK, QPSK), each dibit encodes 2 bits.
889- * The MSB (bit_position=0) determines upper/lower half (+3/+1 vs -1/-3).
890- * The LSB (bit_position=1) determines inner/outer level (+3/-3 vs +1/-1).
888+ * This decoder family expects costs in [0, 65535], where:
889+ * - 0 = strong bit 0
890+ * - 65535 = strong bit 1
891+ * - ~32768 = uncertain
892+ */
893+ static inline uint16_t
894+ llr_to_viterbi_cost (float llr_log_p0_over_p1 ) {
895+ /* Clamp LLR to keep expf numerically stable and preserve monotonicity. */
896+ if (llr_log_p0_over_p1 >= 16.0f ) {
897+ return 0 ;
898+ }
899+ if (llr_log_p0_over_p1 <= -16.0f ) {
900+ return 65535 ;
901+ }
902+ float p1 = 1.0f / (1.0f + expf (llr_log_p0_over_p1 ));
903+ long q = lrintf (p1 * 65535.0f );
904+ if (q < 0L ) {
905+ q = 0L ;
906+ }
907+ if (q > 65535L ) {
908+ q = 65535L ;
909+ }
910+ return (uint16_t )q ;
911+ }
912+
913+ static inline float
914+ min_sq_dist2 (float x , float a , float b ) {
915+ float da = x - a ;
916+ float db = x - b ;
917+ float d2a = da * da ;
918+ float d2b = db * db ;
919+ return (d2a < d2b ) ? d2a : d2b ;
920+ }
921+
922+ /**
923+ * \brief Convert a soft 4-level symbol to a Viterbi cost metric.
891924 *
892- * Returns 0x0000 for confident '0', 0xFFFF for confident '1', ~0x7FFF for uncertain.
925+ * For 4-level modulations (C4FM, GFSK, QPSK), each dibit encodes 2 bits.
926+ * We use a max-log MAP proxy:
927+ * LLR ~= (d1^2 - d0^2) / (2*sigma^2)
928+ * where d0/d1 are distances to nearest ideal levels for each bit hypothesis.
893929 *
894- * M17 uses GFSK with dibit mapping: 01->+3, 00->+1, 10->-1, 11->-3
895- * So for M17: MSB=0 means positive (>center), MSB=1 means negative (<center)
896- * LSB=0 means inner (±1), LSB=1 means outer (±3)
930+ * This is more mathematically consistent than direct linear confidence maps,
931+ * while preserving the decoder's expected [0..65535] metric domain.
897932 */
898933uint16_t
899934soft_symbol_to_viterbi_cost (float symbol , const dsd_state * state , int bit_position ) {
935+ if (!state ) {
936+ return 32768U ;
937+ }
938+
939+ float min_val = state -> min ;
940+ float lmid = state -> lmid ;
900941 float center = state -> center ;
901942 float umid = state -> umid ;
902- float lmid = state -> lmid ;
903943 float max_val = state -> max ;
904- float min_val = state -> min ;
905- float confidence ;
906- int bit_value ;
907944
908- // Compute span for normalization, guard against zero
909- float span = max_val - min_val ;
910- if (span < 1e-6f ) {
911- span = 1.0f ;
912- }
913-
914- if (bit_position == 0 ) {
915- // MSB: determines if symbol is in upper half (0) or lower half (1)
916- // For M17 GFSK: positive symbols -> bit 0, negative -> bit 1
917- if (symbol > center ) {
918- bit_value = 0 ;
919- // Confidence based on distance from center toward max
920- confidence = (symbol - center ) / (max_val - center + 1e-6f );
921- } else {
922- bit_value = 1 ;
923- // Confidence based on distance from center toward min
924- confidence = (center - symbol ) / (center - min_val + 1e-6f );
925- }
926- } else {
927- // LSB: determines inner (0) or outer (1) level
928- // For M17 GFSK: ±1 -> bit 0, ±3 -> bit 1
929- float abs_sym = fabsf (symbol - center );
930- float mid_threshold = (fabsf (umid - center ) + fabsf (lmid - center )) / 2.0f ;
931-
932- if (abs_sym < mid_threshold ) {
933- // Inner level (±1)
934- bit_value = 0 ;
935- confidence = (mid_threshold - abs_sym ) / (mid_threshold + 1e-6f );
936- } else {
937- // Outer level (±3)
938- bit_value = 1 ;
939- confidence = (abs_sym - mid_threshold ) / (span / 2.0f - mid_threshold + 1e-6f );
945+ /* Guard against degenerate/unsorted thresholds by synthesizing a symmetric set. */
946+ if (!(min_val < lmid && lmid < center && center < umid && umid < max_val )) {
947+ float span = max_val - min_val ;
948+ if (span < 1e-3f ) {
949+ span = 2.0f ;
940950 }
951+ float half = span * 0.5f ;
952+ min_val = center - half ;
953+ max_val = center + half ;
954+ lmid = center - (span / 6.0f );
955+ umid = center + (span / 6.0f );
941956 }
942957
943- // Clamp confidence to [0, 1]
944- if (confidence < 0.0f ) {
945- confidence = 0.0f ;
946- }
947- if (confidence > 1.0f ) {
948- confidence = 1.0f ;
958+ /* Estimate ideal 4-PAM levels from decision-region midpoints. */
959+ float n3 = 0.5f * (min_val + lmid );
960+ float n1 = 0.5f * (lmid + center );
961+ float p1 = 0.5f * (center + umid );
962+ float p3 = 0.5f * (umid + max_val );
963+
964+ /* Noise scale proxy: 4-PAM span is roughly 6*sigma in this normalized model. */
965+ float sigma = (max_val - min_val ) / 6.0f ;
966+ if (sigma < 1e-3f ) {
967+ sigma = 1e-3f ;
949968 }
969+ float inv_2sigma2 = 0.5f / (sigma * sigma );
950970
951- // Map to Viterbi cost:
952- // bit_value=0: confident -> 0x0000, uncertain -> 0x7FFF
953- // bit_value=1: confident -> 0xFFFF, uncertain -> 0x7FFF
954- uint16_t cost ;
955- if ( bit_value == 0 ) {
956- // Strong 0 = 0x0000, weak 0 = 0x7FFF
957- cost = ( uint16_t )(( 1.0f - confidence ) * 32767.0f );
971+ bit_position &= 1 ;
972+ float d0 = 0.0f ;
973+ float d1 = 0.0f ;
974+ if ( bit_position == 0 ) {
975+ /* MSB: positive cluster ( 0) vs negative cluster (1). */
976+ d0 = min_sq_dist2 ( symbol , p1 , p3 );
977+ d1 = min_sq_dist2 ( symbol , n1 , n3 );
958978 } else {
959- // Strong 1 = 0xFFFF, weak 1 = 0x7FFF
960- cost = (uint16_t )(32767.0f + confidence * 32768.0f );
979+ /* LSB: inner levels (0) vs outer levels (1). */
980+ d0 = min_sq_dist2 (symbol , n1 , p1 );
981+ d1 = min_sq_dist2 (symbol , n3 , p3 );
961982 }
962983
963- return cost ;
984+ float llr = (d1 - d0 ) * inv_2sigma2 ; /* log(P(bit=0)/P(bit=1)) proxy */
985+ return llr_to_viterbi_cost (llr );
964986}
965987
966988/**
967989 * GMSK (binary) soft symbol to Viterbi cost.
968990 *
969991 * For GMSK modulation where each symbol represents a single bit.
970- * D-STAR uses GMSK where: symbol > center -> bit 1, symbol < center -> bit 0
971- * Distance from center indicates confidence in the decision.
992+ * Uses the same max-log MAP proxy from distances to the two class means.
972993 */
973994uint16_t
974995gmsk_soft_symbol_to_viterbi_cost (float symbol , const dsd_state * state ) {
975- float center = state -> center ;
976- float max_val = state -> max ;
977- float min_val = state -> min ;
978- float confidence ;
979- int bit_value ;
980-
981- float upper_span = max_val - center ;
982- if (upper_span < 1e-6f ) {
983- upper_span = 1e-6f ;
984- }
985- float lower_span = center - min_val ;
986- if (lower_span < 1e-6f ) {
987- lower_span = 1e-6f ;
996+ if (!state ) {
997+ return 32768U ;
988998 }
989999
990- // GMSK: above center = 1, below center = 0
991- if (symbol > center ) {
992- bit_value = 1 ;
993- // Confidence based on distance from center toward max
994- confidence = (symbol - center ) / upper_span ;
995- } else {
996- bit_value = 0 ;
997- // Confidence based on distance from center toward min
998- confidence = (center - symbol ) / lower_span ;
999- }
1000-
1001- // Clamp confidence to [0, 1]
1002- if (confidence < 0.0f ) {
1003- confidence = 0.0f ;
1004- }
1005- if (confidence > 1.0f ) {
1006- confidence = 1.0f ;
1000+ float center = state -> center ;
1001+ float min_val = state -> min ;
1002+ float max_val = state -> max ;
1003+ if (!(min_val < center && center < max_val )) {
1004+ float span = max_val - min_val ;
1005+ if (span < 1e-3f ) {
1006+ span = 2.0f ;
1007+ }
1008+ float half = span * 0.5f ;
1009+ min_val = center - half ;
1010+ max_val = center + half ;
10071011 }
10081012
1009- // Map to Viterbi cost:
1010- // bit_value=0: confident -> 0x0000, uncertain -> 0x7FFF
1011- // bit_value=1: confident -> 0xFFFF, uncertain -> 0x7FFF
1012- uint16_t cost ;
1013- if (bit_value == 0 ) {
1014- // Strong 0 = 0x0000, weak 0 = 0x7FFF
1015- cost = (uint16_t )((1.0f - confidence ) * 32767.0f );
1016- } else {
1017- // Strong 1 = 0xFFFF, weak 1 = 0x7FFF
1018- cost = (uint16_t )(32767.0f + confidence * 32768.0f );
1013+ float mu0 = 0.5f * (min_val + center ); /* bit 0 hypothesis */
1014+ float mu1 = 0.5f * (center + max_val ); /* bit 1 hypothesis */
1015+ float sigma = (max_val - min_val ) / 4.0f ;
1016+ if (sigma < 1e-3f ) {
1017+ sigma = 1e-3f ;
10191018 }
1019+ float inv_2sigma2 = 0.5f / (sigma * sigma );
10201020
1021- return cost ;
1021+ float d0 = symbol - mu0 ;
1022+ float d1 = symbol - mu1 ;
1023+ float llr = ((d1 * d1 ) - (d0 * d0 )) * inv_2sigma2 ;
1024+ return llr_to_viterbi_cost (llr );
10221025}
10231026
10241027void
0 commit comments