From b6faec2ac3a527cad63dd0f19cc1a3347d199dc3 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 27 Mar 2024 19:07:07 +1030 Subject: [PATCH 01/26] first pass at datac14, 5 byte, < 1s signalling mode --- doc/modem_codec_frame_design.ods | Bin 29872 -> 44305 bytes octave/ofdm_lib.m | 2 +- octave/ofdm_mode.m | 22 +++++++++++++++++++++- octave/ofdm_rx.m | 4 +++- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/doc/modem_codec_frame_design.ods b/doc/modem_codec_frame_design.ods index e6cf1c746f10c636d1db7169125bbeb68d958110..ab51e225c0bf83afa58f212e33f4c00eeabf5b6e 100644 GIT binary patch literal 44305 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 smVzwDky0y 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 E05Y|8!~g&Q delta 28500 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 Date: Thu, 28 Mar 2024 06:52:42 +1030 Subject: [PATCH 02/26] tweaks to reduce duration to 0.69s including pre/postamble --- octave/ofdm_mode.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/octave/ofdm_mode.m b/octave/ofdm_mode.m index da08bcd4..a56edace 100644 --- a/octave/ofdm_mode.m +++ b/octave/ofdm_mode.m @@ -135,9 +135,9 @@ config.amp_scale = 2.5*300E3; config.clip_gain1 = 1.2; config.clip_gain2 = 1.0; config.txbpf_width_Hz = 400; elseif strcmp(mode,"datac14") - Ns=5; config.Np=6; Tcp = 0.006; Ts = 0.020; Nc = 3; config.data_mode = "streaming"; + Ns=5; config.Np=4; Tcp = 0.005; Ts = 0.018; Nc = 4; config.data_mode = "streaming"; config.edge_pilots = 0; - config.Ntxtbits = 0; config.Nuwbits = 48; config.bad_uw_errors = 18; + config.Ntxtbits = 0; config.Nuwbits = 32; config.bad_uw_errors = 12; config.ftwindow_width = 80; config.timing_mx_thresh = 0.45; config.tx_uw = zeros(1,config.Nuwbits); config.tx_uw(1:24) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; From 3c761467659fb6d4776fc59075d7d32e99b9b156 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 28 Mar 2024 07:39:00 +1030 Subject: [PATCH 03/26] work out packet duration including pre/postamble --- doc/modem_codec_frame_design.ods | Bin 44305 -> 44489 bytes octave/ofdm_helper.m | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/modem_codec_frame_design.ods b/doc/modem_codec_frame_design.ods index ab51e225c0bf83afa58f212e33f4c00eeabf5b6e..040ba0160adc96327f3487de7676e81b74d8b332 100644 GIT binary patch delta 3136 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 delta 3028 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 diff --git a/octave/ofdm_helper.m b/octave/ofdm_helper.m index b34ec239..38c02486 100644 --- a/octave/ofdm_helper.m +++ b/octave/ofdm_helper.m @@ -44,7 +44,8 @@ function print_config(states) printf("Nc=%d Ts=%4.3f Tcp=%4.3f Ns: %d Np: %d\n", Nc, 1/Rs, Tcp, Ns, Np); printf("Nsymperframe: %d Nbitsperpacket: %d Nsamperframe: %d Ntxtbits: %d Nuwbits: %d Nuwframes: %d\n", Ns*Nc, Nbitsperpacket, Nsamperframe, Ntxtbits, Nuwbits, Nuwframes); - printf("uncoded bits/s: %4.1f\n", Nbitsperpacket*Fs/(Np*Nsamperframe)); + printf("uncoded bits/s: %4.1f Duration (incl post/preamble): %4.2f s\n", + Nbitsperpacket*Fs/(Np*Nsamperframe), (Np+2)*Ns*(Tcp+1/Rs)); end %----------------------------------------------------------------------- From 2ee2989c9cc54dd30b94b1a2cfda0db5975d3cc8 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 29 Mar 2024 07:18:31 +1030 Subject: [PATCH 04/26] ofdm_mod/ofdm_demod working with datac14 --- CMakeLists.txt | 17 ++++++++++++++++- src/gp_interleaver.c | 1 + src/interldpc.c | 1 + src/ofdm.c | 6 ++++-- src/ofdm_mode.c | 25 +++++++++++++++++++++++++ 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ff1b2959..f6b22019 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -662,6 +662,13 @@ endif() cd ${CMAKE_CURRENT_BINARY_DIR}/src; cat test.raw | ./ofdm_demod --mode datac13 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + # DATAC14 Octave Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_datac14_octave + COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; + DISPLAY=\"\" octave-cli -qf --eval 'ofdm_ldpc_tx(\"${CMAKE_CURRENT_BINARY_DIR}/src/test.raw\",\"datac14\",1,3,\"awgn\",\"bursts\",5)'; + cd ${CMAKE_CURRENT_BINARY_DIR}/src; + cat test.raw | ./ofdm_demod --mode datac14 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + # DATAC4 C Tx, C Rx, burst mode add_test(NAME test_OFDM_modem_datac4_ldpc_burst COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; @@ -676,7 +683,14 @@ endif() ./ch - - --No -17 | ./ofdm_demod --mode datac13 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") - # ------------------------------------------------------------------------- + # DATAC14 C Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_datac14_ldpc_burst + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + ./ofdm_mod --mode datac14 --in /dev/zero --testframes 1 --verbose 1 --ldpc --bursts 10 | + ./ch - - --No -17 | + ./ofdm_demod --mode datac14 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + + # ------------------------------------------------------------------------- # LDPC # ------------------------------------------------------------------------- @@ -1392,6 +1406,7 @@ endif(NOT APPLE) test_OFDM_modem_datac3_octave test_OFDM_modem_datac4_octave test_OFDM_modem_datac13_octave + test_OFDM_modem_datac14_octave test_fsk_lib_4fsk_ldpc test_OFDM_modem_datac0_compression PROPERTIES diff --git a/src/gp_interleaver.c b/src/gp_interleaver.c index 61678f8c..6710b0c3 100644 --- a/src/gp_interleaver.c +++ b/src/gp_interleaver.c @@ -44,6 +44,7 @@ */ static const int b_table[] = { + 48, 31, /* datac14: HRA_56_56, 40 data bits used */ 56, 37, /* 700E: HRA_56_56 */ 106, 67, /* 2020B: (112,56) partial protection */ 112, 71, /* 700D: HRA_112_112 */ diff --git a/src/interldpc.c b/src/interldpc.c index 24f4b2ab..8cec6477 100644 --- a/src/interldpc.c +++ b/src/interldpc.c @@ -78,6 +78,7 @@ void ldpc_mode_specific_setup(struct OFDM *ofdm, struct LDPC *ldpc) { } if (!strcmp(ofdm->mode, "datac4")) set_data_bits_per_frame(ldpc, 448); if (!strcmp(ofdm->mode, "datac13")) set_data_bits_per_frame(ldpc, 128); + if (!strcmp(ofdm->mode, "datac14")) set_data_bits_per_frame(ldpc, 40); } /* LDPC encode frame - generate parity bits and a codeword, applying the diff --git a/src/ofdm.c b/src/ofdm.c index 28d6dba4..ba22f3f9 100644 --- a/src/ofdm.c +++ b/src/ofdm.c @@ -559,7 +559,8 @@ static void allocate_tx_bpf(struct OFDM *ofdm) { quisk_filt_cfInit(ofdm->tx_bpf, filtP400S600, sizeof(filtP400S600) / sizeof(float)); quisk_cfTune(ofdm->tx_bpf, ofdm->tx_centre / ofdm->fs); - } else if (!strcmp(ofdm->mode, "datac4") || !strcmp(ofdm->mode, "datac13")) { + } else if (!strcmp(ofdm->mode, "datac4") || !strcmp(ofdm->mode, "datac13") || + !strcmp(ofdm->mode, "datac14")) { quisk_filt_cfInit(ofdm->tx_bpf, filtP200S400, sizeof(filtP200S400) / sizeof(float)); // centre the filter on the mean carrier freq, allows a narrower filter to @@ -590,7 +591,8 @@ static void allocate_rx_bpf(struct OFDM *ofdm) { /* Receive bandpass filter; complex coefficients, center frequency */ - if (!strcmp(ofdm->mode, "datac4") || !strcmp(ofdm->mode, "datac13")) { + if (!strcmp(ofdm->mode, "datac4") || !strcmp(ofdm->mode, "datac13") || + !strcmp(ofdm->mode, "datac14")) { quisk_filt_cfInit(ofdm->rx_bpf, filtP200S400, sizeof(filtP200S400) / sizeof(float)); // centre the filter on the mean carrier freq, allows a narrower filter to diff --git a/src/ofdm_mode.c b/src/ofdm_mode.c index 1aabb1af..f92d2884 100644 --- a/src/ofdm_mode.c +++ b/src/ofdm_mode.c @@ -226,6 +226,31 @@ void ofdm_init_mode(char mode[], struct OFDM_CONFIG *config) { config->clip_gain1 = 1.2; config->clip_gain2 = 1.0; config->rx_bpf_en = true; + } else if (strcmp(mode, "datac14") == 0) { + config->ns = 5; + config->np = 4; + config->tcp = 0.005; + config->ts = 0.018; + config->nc = 4; + config->edge_pilots = 0; + config->txtbits = 0; + config->state_machine = "data"; + config->ftwindowwidth = 80; + config->timing_mx_thresh = 0.45; + config->codename = "HRA_56_56"; + config->amp_est_mode = 1; + config->nuwbits = 32; + config->bad_uw_errors = 12; + uint8_t uw[] = {1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0}; + assert(sizeof(uw) <= MAX_UW_BITS); + memcpy(config->tx_uw, uw, sizeof(uw)); + memcpy(&config->tx_uw[config->nuwbits - sizeof(uw)], uw, sizeof(uw)); + config->data_mode = "streaming"; + config->amp_scale = 2.5 * 300E3; + config->clip_gain1 = 1.2; + config->clip_gain2 = 1.0; + config->rx_bpf_en = true; } else { assert(0); } From 3b9037c9c7879ccd19db14464da26a335cdeba36 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 29 Mar 2024 07:40:38 +1030 Subject: [PATCH 05/26] datac14 freedv_api port --- CMakeLists.txt | 13 +++++++++++++ src/freedv_700.c | 1 + src/freedv_api.c | 19 +++++++++++++------ src/freedv_api.h | 4 ++++ src/freedv_data_raw_rx.c | 2 ++ src/freedv_data_raw_tx.c | 2 ++ 6 files changed, 35 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f6b22019..aa518d87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1078,6 +1078,12 @@ if (NOT APPLE) ./freedv_data_raw_tx --testframes 10 DATAC13 /dev/zero /dev/null") set_tests_properties(test_memory_leak_FreeDV_DATAC13_tx PROPERTIES PASS_REGULAR_EXPRESSION "ERROR SUMMARY: 0 errors") + add_test(NAME test_memory_leak_FreeDV_DATAC14_tx + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes \ + ./freedv_data_raw_tx --testframes 10 DATAC14 /dev/zero /dev/null") + set_tests_properties(test_memory_leak_FreeDV_DATAC14_tx PROPERTIES PASS_REGULAR_EXPRESSION "ERROR SUMMARY: 0 errors") + add_test(NAME test_memory_leak_FreeDV_700E_tx COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes \ @@ -1321,6 +1327,13 @@ endif(NOT APPLE) ./freedv_data_raw_rx DATAC13 - binaryOut.bin -v; diff binaryIn.bin binaryOut.bin") + add_test(NAME test_freedv_data_raw_ofdm_datac14_burst_file + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + head -c $((3*10)) binaryIn.bin; + ./freedv_data_raw_tx DATAC14 binaryIn.bin - --bursts 10 | + ./freedv_data_raw_rx DATAC14 - binaryOut.bin -v; + diff binaryIn.bin binaryOut.bin") + # FSK LDPC default 100 bit/s 2FSK, enough noise for several % raw BER to give # FEC/acquisition a work out, bursts of 1 frame as that stresses acquisition add_test(NAME test_freedv_data_raw_fsk_ldpc_100 diff --git a/src/freedv_700.c b/src/freedv_700.c index 3dc4329f..7c8536c3 100644 --- a/src/freedv_700.c +++ b/src/freedv_700.c @@ -197,6 +197,7 @@ void freedv_ofdm_data_open(struct freedv *f) { if (f->mode == FREEDV_MODE_DATAC3) strcpy(mode, "datac3"); if (f->mode == FREEDV_MODE_DATAC4) strcpy(mode, "datac4"); if (f->mode == FREEDV_MODE_DATAC13) strcpy(mode, "datac13"); + if (f->mode == FREEDV_MODE_DATAC14) strcpy(mode, "datac14"); ofdm_init_mode(mode, &ofdm_config); f->ofdm = ofdm_create(&ofdm_config); diff --git a/src/freedv_api.c b/src/freedv_api.c index 5172959d..3ccf63da 100644 --- a/src/freedv_api.c +++ b/src/freedv_api.c @@ -125,7 +125,8 @@ struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode)) == false) + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, mode)) == false) return NULL; /* set everything to zero just in case */ @@ -154,6 +155,7 @@ struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) { if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, mode)) freedv_ofdm_data_open(f); if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode)) freedv_ofdm_data_open(f); if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode)) freedv_ofdm_data_open(f); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, mode)) freedv_ofdm_data_open(f); varicode_decode_init(&f->varicode_dec_states, 1); @@ -235,7 +237,8 @@ void freedv_close(struct freedv *freedv) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, freedv->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, freedv->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, freedv->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, freedv->mode)) { + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, freedv->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, freedv->mode)) { FREE(freedv->rx_syms); FREE(freedv->rx_amps); FREE(freedv->ldpc); @@ -266,7 +269,8 @@ static int is_ofdm_mode(struct freedv *f) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode); + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode); } static int is_ofdm_data_mode(struct freedv *f) { @@ -274,7 +278,8 @@ static int is_ofdm_data_mode(struct freedv *f) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode); + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode); } /*---------------------------------------------------------------------------*\ @@ -464,7 +469,8 @@ void freedv_rawdatacomptx(struct freedv *f, COMP mod_out[], FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode)) + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode)) freedv_comptx_ofdm(f, mod_out); if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, f->mode)) { @@ -1063,7 +1069,8 @@ int freedv_rawdatacomprx(struct freedv *f, unsigned char *packed_payload_bits, FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode)) + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode)) rx_status = freedv_comp_short_rx_ofdm(f, (void *)demod_in, 0, 1.0f); if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, f->mode)) { rx_status = freedv_rx_fsk_ldpc_data(f, demod_in); diff --git a/src/freedv_api.h b/src/freedv_api.h index ce95a559..e4ca468b 100644 --- a/src/freedv_api.h +++ b/src/freedv_api.h @@ -61,6 +61,7 @@ extern "C" { #define FREEDV_MODE_DATAC0 14 #define FREEDV_MODE_DATAC4 18 #define FREEDV_MODE_DATAC13 19 +#define FREEDV_MODE_DATAC14 20 // Sample rates used #define FREEDV_FS_8000 8000 @@ -140,6 +141,9 @@ extern "C" { #if !defined(FREEDV_MODE_DATAC13_EN) #define FREEDV_MODE_DATAC13_EN FREEDV_MODE_EN_DEFAULT #endif +#if !defined(FREEDV_MODE_DATAC14_EN) +#define FREEDV_MODE_DATAC14_EN FREEDV_MODE_EN_DEFAULT +#endif #define FDV_MODE_ACTIVE(mode_name, var) \ ((mode_name##_EN) == 0 ? 0 : (var) == mode_name) diff --git a/src/freedv_data_raw_rx.c b/src/freedv_data_raw_rx.c index 1957e103..0353ae54 100644 --- a/src/freedv_data_raw_rx.c +++ b/src/freedv_data_raw_rx.c @@ -214,6 +214,8 @@ int main(int argc, char *argv[]) { mode = FREEDV_MODE_DATAC4; if (!strcmp(argv[dx], "DATAC13") || !strcmp(argv[dx], "datac13")) mode = FREEDV_MODE_DATAC13; + if (!strcmp(argv[dx], "DATAC14") || !strcmp(argv[dx], "datac14")) + mode = FREEDV_MODE_DATAC14; if (mode == -1) { fprintf(stderr, "Error in mode: %s\n", argv[dx]); exit(1); diff --git a/src/freedv_data_raw_tx.c b/src/freedv_data_raw_tx.c index 44d53e9f..cb741077 100644 --- a/src/freedv_data_raw_tx.c +++ b/src/freedv_data_raw_tx.c @@ -236,6 +236,8 @@ int main(int argc, char *argv[]) { mode = FREEDV_MODE_DATAC4; if (!strcmp(argv[dx], "DATAC13") || !strcmp(argv[dx], "datac13")) mode = FREEDV_MODE_DATAC13; + if (!strcmp(argv[dx], "DATAC14") || !strcmp(argv[dx], "datac14")) + mode = FREEDV_MODE_DATAC14; if (mode == -1) { fprintf(stderr, "Error: in mode: %s", argv[dx]); exit(1); From 23a7d60bf8269db6d11d106dc70df001e26f9933 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Sat, 30 Mar 2024 08:08:09 +1030 Subject: [PATCH 06/26] put snr_curve.sh system back in from codec2-dev, curves for datac14 --- README.md | 2 +- README_data.md | 1 + octave/snr_curves_plot.m | 267 +++++++++++++++++++++++++ src/ch.c | 2 +- unittest/check_peak.sh | 1 + unittest/raw_data_curves/Makefile | 149 ++++++++++++++ unittest/raw_data_curves/snr_curves.sh | 191 ++++++++++++++++++ 7 files changed, 611 insertions(+), 2 deletions(-) create mode 100644 octave/snr_curves_plot.m create mode 100644 unittest/raw_data_curves/Makefile create mode 100755 unittest/raw_data_curves/snr_curves.sh diff --git a/README.md b/README.md index 726152cc..2247afb3 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Feature Requests can be submitted via GitHub Issues. Before writing any code or submitting a PR - **please discuss** the PR with developers by raising a GitHub Issue. We have many years of experience and a carefully considered plan for Codec 2 development, and can guide you on work that will most benefit this project. Some key guidelines about the code in the `codec2` repo: -1. Code that is required to build libcodec2, or to test libcodec2 goes in codec2. +1. Only code that is required to build, test, or document libcodec2 goes in codec2. 2. Experimental work, code used for algorithm development, should probably go into some other repo. 3. Only widely used “production” code goes in codec2. If it has an user base of < 2 (e.g. personal projects, early R&D) - it should probably be application code or a fork. diff --git a/README_data.md b/README_data.md index 17481f39..3ceccb46 100644 --- a/README_data.md +++ b/README_data.md @@ -146,6 +146,7 @@ These modes use an OFDM modem with powerful LDPC codes and are designed for send | DATAC3 | 500 | 321 | 126 | (2048,1024) | 3.19 | 74/100 at 0dB | Forward link data (low SNR) | | DATAC4 | 250 | 87 | 56 | (1472,448) | 5.17 | 90/100 at -4dB | Forward link data (low SNR) | | DATAC13 | 200 | 64 | 14 | (384,128) | 2.0 | 90/100 at -4dB | Reverse link ACK packets (low SNR) | +| DATAC14 | 250 | 58 | 3 | (112,56) | 0.69 | 90/100 at -4dB | Reverse link ACK packets (low SNR) | Notes: 1. 16 bits (2 bytes) per frame are reserved for a 16 bit CRC, e.g. for `datac3` we have 128 byte frames, and 128-2=126 bytes/frame of payload data. diff --git a/octave/snr_curves_plot.m b/octave/snr_curves_plot.m new file mode 100644 index 00000000..8baafe0d --- /dev/null +++ b/octave/snr_curves_plot.m @@ -0,0 +1,267 @@ +% snr_curves_plot.m +% +% Companion script for unittest/raw_data_curves + +1; + +function state_vec = set_graphics_state_print() + textfontsize = get(0,"defaulttextfontsize"); + linewidth = get(0,"defaultlinelinewidth"); + markersize = get(0, "defaultlinemarkersize"); + set(0, "defaulttextfontsize", 16); + set(0, "defaultaxesfontsize", 16); + set(0, "defaultlinelinewidth", 1); + state_vec = [textfontsize linewidth markersize]; +endfunction + +function set_graphics_state_screen(state_vec) + textfontsize = state_vec(1); + linewidth = state_vec(2); + markersize = state_vec(3); + set(0, "defaulttextfontsize", textfontsize); + set(0, "defaultaxesfontsize", textfontsize); + set(0, "defaultlinelinewidth", linewidth); + set(0, "defaultlinemarkersize", markersize); +endfunction + +function [snr_ch per] = snr_scatter(source, mode, channel, colour) + suffix = sprintf("_%s_%s_%s",source, mode, channel); + snr = load(sprintf("snr%s.txt",suffix)); + offset = load(sprintf("offset%s.txt",suffix)); + snr -= offset; + snr_x = []; snrest_y = []; + for i=1:length(snr) + fn = sprintf('snrest%s_%d.txt',suffix,i); + if exist(fn,'file') == 2 + snrest=load(fn); + if i == length(snr) + plot(snr(i)*ones(1,length(snrest)), snrest, sprintf('%s;%s %s;',colour,source,mode)); + else + plot(snr(i)*ones(1,length(snrest)), snrest, sprintf('%s',colour)); + end + snr_x = [snr_x snr(i)]; snrest_y = [snrest_y mean(snrest)]; + end + end + plot(snr_x, snrest_y, sprintf('%s', colour)); +endfunction + +function [snr_ch per] = per_snr(mode, colour) + snrch = load(sprintf("snrch_%s.txt",mode)); + snroffset = load(sprintf("snroffset_%s.txt",mode)); + snrch -= snroffset; + per = load(sprintf("per_%s.txt",mode)); + plot(snrch, per, sprintf('%so-;%s;', colour, mode)); +endfunction + +function snrest_snr_screen(source, channel) + clf; hold on; + snr_scatter(source, 'datac0', channel,'b+-') + snr_scatter(source, 'datac1', channel,'g+-') + snr_scatter(source, 'datac3', channel,'r+-') + snr_scatter(source, 'datac4', channel,'c+-') + snr_scatter(source, 'datac13', channel,'m+-') + xlabel('SNR (dB)'); ylabel('SNRest (dB)'); grid('minor'); + axis([-12 12 -12 12]); + a = axis; + plot([a(1) a(2)],[a(1) a(2)],'bk-'); + hold off; grid; + if strcmp(source,'ctx') + title(sprintf('SNR estimate versus SNR (%s) (no compression)', channel)); + else + title(sprintf('SNR estimate versus SNR (%s) (with compression)', channel)); + end + legend('location','northwest'); +endfunction + +function snrest_snr_print(source, channel) + state_vec = set_graphics_state_print(); + snrest_snr_screen(source, channel); + print(sprintf("snrest_snr_%s.png", source), "-dpng", "-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +function ber_per_v_snr(source, mode, channel, colour) + suffix = sprintf("_%s_%s_%s.txt",source, mode, channel); + snr = load(sprintf("snr%s",suffix)); + offset = load(sprintf("offset%s",suffix)); + snr -= offset; + ber = load(sprintf("ber%s",suffix)) + 1E-6; + per = load(sprintf("per%s",suffix)) + 1E-6; + semilogy(snr, ber, sprintf('%s;%s %s ber;', colour, source, mode)); + semilogy(snr, per, sprintf('%s;%s %s per;', colour, source, mode),'linewidth',3,'markersize',10); +endfunction + +function per_v_snr(source, mode, channel, colour) + suffix = sprintf("_%s_%s_%s.txt",source, mode, channel); + snr = load(sprintf("snr%s",suffix)); + offset = load(sprintf("offset%s",suffix)); + snr -= offset; + per = load(sprintf("per%s",suffix)) + 1E-6; + if strcmp(channel,"awgn") + semilogy(snr, per, sprintf('%s;%s %s;', colour, mode, channel)); + else + semilogy(snr, per, sprintf('%s;%s %s;', colour, mode, channel),'linewidth',3,'markersize',10); + end +endfunction + +function thruput_v_snr(source, mode, channel, colour) + suffix = sprintf("_%s_%s_%s.txt",source, mode, channel); + snr = load(sprintf("snr%s",suffix)); + offset = load(sprintf("offset%s",suffix)); + snr -= offset; + per = load(sprintf("per%s",suffix)) + 1E-6; + if strcmp(mode,"datac0") Rb=291; end; + if strcmp(mode,"datac1") Rb=980; end; + if strcmp(mode,"datac3") Rb=321; end; + if strcmp(mode,"datac4") Rb=87; end; + if strcmp(mode,"datac13") Rb=65; end; + if strcmp(mode,"datac14") Rb=58; end; + if strcmp(channel,"awgn") + plot(snr, Rb*(1-per), sprintf('%s;%s %s;', colour, mode, channel)); + else + plot(snr, Rb*(1-per), sprintf('%s;%s %s;', colour, mode, channel),'linewidth',3,'markersize',10); + end +endfunction + +function octave_ch_noise_screen(channel) + clf; hold on; + ber_per_v_snr('oct','datac0',channel,'bo-') + ber_per_v_snr('ch' ,'datac0',channel,'bx-') + ber_per_v_snr('oct','datac1',channel,'go-') + ber_per_v_snr('ch' ,'datac1',channel,'gx-') + ber_per_v_snr('oct','datac3',channel,'ro-') + ber_per_v_snr('ch' ,'datac3',channel,'rx-') + xlabel('SNR (dB)'); grid; + hold off; + if strcmp(channel,"awgn") + axis([-6 8 1E-3 1]); + else + axis([-2 12 1E-3 1]); + end + title(sprintf('Comparsion of Measuring SNR from Octave and ch tool (%s)', channel)); +endfunction + +function octave_ch_noise_print(channel) + state_vec = set_graphics_state_print(); + octave_ch_noise_screen(channel); + print(sprintf("octave_ch_noise_%s.png", channel), "-dpng","-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +function octave_c_tx_screen(channel) + clf; hold on; + ber_per_v_snr('oct','datac0',channel,'bo-') + ber_per_v_snr('ctx','datac0',channel,'bx-') + ber_per_v_snr('oct','datac1',channel,'go-') + ber_per_v_snr('ctx','datac1',channel,'gx-') + ber_per_v_snr('oct','datac3',channel,'ro-') + ber_per_v_snr('ctx','datac3',channel,'rx-') + xlabel('SNR (dB)'); grid; + hold off; + if strcmp(channel,"awgn") + axis([-6 8 1E-3 1]); + else + axis([-2 12 1E-3 1]); + end + title(sprintf('Comparsion of Octave Tx and C Tx (no compression) (%s)', channel)); +endfunction + +function octave_c_tx_print(channel) + state_vec = set_graphics_state_print(); + octave_c_tx_screen(channel); + print(sprintf("octave_c_tx_%s.png", channel), "-dpng","-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +function octave_c_tx_comp_screen(channel) + clf; hold on; + ber_per_v_snr('oct','datac0',channel,'bo-') + ber_per_v_snr('ctxc','datac0',channel,'bx-') + ber_per_v_snr('oct','datac1',channel,'go-') + ber_per_v_snr('ctxc','datac1',channel,'gx-') + ber_per_v_snr('oct','datac3',channel,'ro-') + ber_per_v_snr('ctxc','datac3',channel,'rx-') + xlabel('SNR (dB)'); grid; + hold off; + if strcmp(channel,"awgn") + axis([-6 8 1E-3 1]); + else + axis([-2 12 1E-3 1]); + end + title(sprintf('Comparsion of Octave Tx and C Tx (with compression) (%s)', channel)); +endfunction + +function octave_c_tx_comp_print(channel) + state_vec = set_graphics_state_print(); + octave_c_tx_comp_screen(channel); + print(sprintf("octave_c_tx_comp_%s.png", channel), "-dpng","-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +% composite AWGN and MPP for compressed +function c_tx_comp_screen + clf; hold on; + per_v_snr('ctxc','datac0','awgn','bo-') + per_v_snr('ctxc','datac1','awgn','go-') + per_v_snr('ctxc','datac3','awgn','ro-') + per_v_snr('ctxc','datac4','awgn','co-') + per_v_snr('ctxc','datac13','awgn','mo-') + per_v_snr('ctxc','datac14','awgn','ko-') + per_v_snr('ctxc','datac0','mpp','bx-') + per_v_snr('ctxc','datac1','mpp','gx-') + per_v_snr('ctxc','datac3','mpp','rx-') + per_v_snr('ctxc','datac4','mpp','cx-') + per_v_snr('ctxc','datac13','mpp','mx-') + per_v_snr('ctxc','datac14','mpp','kx-') + xlabel('SNR (dB)'); ylabel('PER'); grid; + hold off; + axis([-10 14 1E-3 1]); + title('PER of C Raw Data Modes (with compression)'); +endfunction + +function c_tx_comp_print; + state_vec = set_graphics_state_print(); + c_tx_comp_screen; + print("c_tx_comp.png", "-dpng","-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +function c_tx_comp_thruput_screen + clf; hold on; + thruput_v_snr('ctxc','datac0','awgn','bo-') + thruput_v_snr('ctxc','datac1','awgn','go-') + thruput_v_snr('ctxc','datac3','awgn','ro-') + thruput_v_snr('ctxc','datac4','awgn','co-') + thruput_v_snr('ctxc','datac13','awgn','mo-') + thruput_v_snr('ctxc','datac14','awgn','ko-') + thruput_v_snr('ctxc','datac0','mpp','bx-') + thruput_v_snr('ctxc','datac1','mpp','gx-') + thruput_v_snr('ctxc','datac3','mpp','rx-') + thruput_v_snr('ctxc','datac4','mpp','cx-') + thruput_v_snr('ctxc','datac13','mpp','mx-') + thruput_v_snr('ctxc','datac14','mpp','kx-') + xlabel('SNR (dB)'); ylabel('bits/s'); grid; + hold off; + axis([-10 10 0 1000]); + title(' Throughput for C Tx (with compression)'); + legend('location','west'); +endfunction + +function c_tx_comp_thruput_print; + state_vec = set_graphics_state_print; + c_tx_comp_thruput_screen; + print("c_tx_comp_thruput.png", "-dpng","-S1000,800"); + set_graphics_state_screen(state_vec); +endfunction + +#{ +figure(1); octave_ch_noise_screen; +figure(2); octave_c_tx_screen; +figure(3); octave_c_tx_comp_screen +figure(4); snrest_snr_screen; + +figure(5); octave_ch_noise_print; +figure(6); octave_c_tx_print; +figure(7); octave_c_tx_comp_print; +figure(8); snrest_snr_print; +#} diff --git a/src/ch.c b/src/ch.c index 9d8d8957..45e1b496 100644 --- a/src/ch.c +++ b/src/ch.c @@ -256,7 +256,7 @@ int main(int argc, char *argv[]) { stderr, "\nAdjust path --fading_dir or use GNU Octave to generate:\n\n"); gen_fading_file: - fprintf(stderr, "$ octave --no-gui\n"); + fprintf(stderr, "$ octave-cli\n"); fprintf(stderr, "octave:24> pkg load signal\n"); fprintf(stderr, "octave:24> time_secs=60\n"); fprintf(stderr, diff --git a/unittest/check_peak.sh b/unittest/check_peak.sh index 8f41c2a2..b3575344 100755 --- a/unittest/check_peak.sh +++ b/unittest/check_peak.sh @@ -51,6 +51,7 @@ if [ "$1" == "LPCNet" ]; then data_test "datac3" data_test "datac4" data_test "datac13" + data_test "datac14" fi exit 0 diff --git a/unittest/raw_data_curves/Makefile b/unittest/raw_data_curves/Makefile new file mode 100644 index 00000000..09aa7cac --- /dev/null +++ b/unittest/raw_data_curves/Makefile @@ -0,0 +1,149 @@ +# Makefile +# Dec 2022 +# +# Automates PER/BER curve generation for raw data mode: +# +# 1. Compare "ch" noise injection/SNR measurement against reference Octave Tx +# 2. Compare C Tx against Octave Tx (with and without compression) +# 3. Plot curves for SNR estimated from C Rx against actual SNR +# 4. Plot AWGN/PER C Tx curves for end user documentation +# +# usage: +# +# 1. Create 20 minutes of MPP fading samples: +# $ cd codec2/octave/ +# $ octave-cli +# octave:24> pkg load signal +# octave:24> time_secs=60*20 +# octave:26> ch_fading("~/codec2/build_linux/unittest/fast_fading_samples.float", 8000, 1.0, 8000*time_secs) +# +# 2. Run scripts: +# +# $ make + +SHELL := /bin/bash +CODEC2 := $(HOME)/codec2 + +all: test \ + octave_ch_noise_awgn.png octave_c_tx_awgn.png octave_c_tx_comp_awgn.png \ + octave_ch_noise_mpp.png octave_c_tx_mpp.png octave_c_tx_comp_mpp.png \ + snrest_snr_ctx.png snrest_snr_ctxc.png \ + c_tx_comp.png c_tx_comp_thruput.png + +clean: + rm -f *.txt *.png *.raw + +# run this first, traps common CML setup error +test: + source snr_curves.sh; test_ldpc + +# subset of files generated, but enough to set up Makefile dependencies +snr_oct = snr_oct_datac0_awgn.txt snr_oct_datac1_awgn.txt snr_oct_datac3_awgn.txt +snr_ch = snr_ch_datac0_awgn.txt snr_ch_datac1_awgn.txt snr_ch_datac3_awgn.txt +snr_ctx = snr_ctx_datac0_awgn.txt snr_ctx_datac1_awgn.txt snr_ctx_datac3_awgn.txt +snr_ctxc = snr_ctxc_datac0_awgn.txt snr_ctxc_datac3_awgn.txt + +snr_oct_mpp = snr_oct_datac0_mpp.txt snr_oct_datac1_mpp.txt snr_oct_datac3_mpp.txt +snr_ch_mpp = snr_ch_datac0_mpp.txt snr_ch_datac1_mpp.txt snr_ch_datac3_mpp.txt +snr_ctx_mpp = snr_ctx_datac0_mpp.txt snr_ctx_datac1_mpp.txt snr_ctx_datac3_mpp.txt +snr_ctxc_mpp = snr_ctxc_datac0_mpp.txt snr_ctxc_datac3_mpp.txt + +$(snr_oct): + source snr_curves.sh; generate_octave_tx_data datac0 awgn + source snr_curves.sh; generate_octave_tx_data datac1 awgn + source snr_curves.sh; generate_octave_tx_data datac3 awgn + +$(snr_oct_mpp): + source snr_curves.sh; generate_octave_tx_data datac0 mpp + source snr_curves.sh; generate_octave_tx_data datac1 mpp + source snr_curves.sh; generate_octave_tx_data datac3 mpp + +$(snr_ch): + source snr_curves.sh; generate_ch_data datac0 awgn + source snr_curves.sh; generate_ch_data datac1 awgn + source snr_curves.sh; generate_ch_data datac3 awgn + +$(snr_ch_mpp): + source snr_curves.sh; generate_ch_data datac0 mpp + source snr_curves.sh; generate_ch_data datac1 mpp + source snr_curves.sh; generate_ch_data datac3 mpp + +# C without compression + +$(snr_ctx): + source snr_curves.sh; generate_snrest_v_snr_data datac0 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac1 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac3 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac4 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac13 awgn + source snr_curves.sh; generate_snrest_v_snr_data datac14 awgn + +$(snr_ctx_mpp): + source snr_curves.sh; generate_snrest_v_snr_data datac0 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac1 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac3 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac4 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac13 mpp + source snr_curves.sh; generate_snrest_v_snr_data datac14 mpp + +# C with compression + +$(snr_ctxc): + source snr_curves.sh; generate_snrest_v_snr_data datac0 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac1 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac3 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac4 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac13 awgn 1 + source snr_curves.sh; generate_snrest_v_snr_data datac14 awgn 1 + +$(snr_ctxc_mpp): + source snr_curves.sh; generate_snrest_v_snr_data datac0 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac1 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac3 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac4 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac13 mpp 1 + source snr_curves.sh; generate_snrest_v_snr_data datac14 mpp 1 + +# Octave and C curves should be on top of each other, indicating Octave +# and ch noise injection/SNR measurement are equivalent +octave_ch_noise_awgn.png: $(snr_oct) $(snr_ch) + echo "snr_curves_plot; octave_ch_noise_print('awgn'); quit" | \ + octave-cli -p $(CODEC2)/octave +octave_ch_noise_mpp.png: $(snr_oct_mpp) $(snr_ch_mpp) + echo "snr_curves_plot; octave_ch_noise_print('mpp'); quit" | \ + octave-cli -p $(CODEC2)/octave + +# Octave Tx and C Tx curves should be on top of each other +octave_c_tx_awgn.png: $(snr_oct) $(snr_ctx) + echo "snr_curves_plot; octave_c_tx_print('awgn'); quit" | \ + octave-cli -p $(CODEC2)/octave +octave_c_tx_mpp.png: $(snr_oct_mpp) $(snr_ctx_mpp) + echo "snr_curves_plot; octave_c_tx_print('mpp'); quit" | \ + octave-cli -p $(CODEC2)/octave + +# Octave Tx and C Tx (compressed) curves should be close, but C may be 1dB +# poorer +octave_c_tx_comp_awgn.png: $(snr_oc) $(snr_ctxc) + echo "snr_curves_plot; octave_c_tx_comp_print('awgn'); quit" | \ + octave-cli -p $(CODEC2)/octave +octave_c_tx_comp_mpp.png: $(snr_oct_mpp) $(snr_ctxc_mpp) + echo "snr_curves_plot; octave_c_tx_comp_print('mpp'); quit" | \ + octave-cli -p $(CODEC2)/octave + +# combined AWGN and MPP from C Tx (compressed) - what end users would run +c_tx_comp.png: $(snr_ctxc) $(snr_ctxc_mpp) + echo "snr_curves_plot; c_tx_comp_print; quit" | \ + octave-cli -p $(CODEC2)/octave + +# Curves of SNR estimates from C Rx compared to actual SNR, useful for "gear shifting" +snrest_snr_ctx.png: $(snr_ctx) + echo "snr_curves_plot; snrest_snr_print('ctx', 'awgn'); quit" | \ + octave-cli -p $(CODEC2)/octave +snrest_snr_ctxc.png: $(snr_ctxc) + echo "snr_curves_plot; snrest_snr_print('ctxc', 'awgn'); quit" | \ + octave-cli -p $(CODEC2)/octave + +# Throughput of payload data in bits/s of modes against SNR +c_tx_comp_thruput.png: $(snr_ctxc) $(snr_ctxc_mpp) + echo "snr_curves_plot; c_tx_comp_thruput_print; quit" | \ + octave-cli -p $(CODEC2)/octave diff --git a/unittest/raw_data_curves/snr_curves.sh b/unittest/raw_data_curves/snr_curves.sh new file mode 100755 index 00000000..62f0a52f --- /dev/null +++ b/unittest/raw_data_curves/snr_curves.sh @@ -0,0 +1,191 @@ +# snr_curves.sh +# +# Library of bash functions to generate data for SNR curves. +# +# testing a function example: +# $ bash -c "source ./snr_curves.sh; generate_octave_tx_data datac0 awgn" + +set -x + +PATH=${PATH}:${HOME}/codec2/build_linux/src +CODEC2=${HOME}/codec2 +FADING_DIR=${CODEC2}/build_linux/unittest + +snr_list='-5 -4 -3 -2 0 1 2 4' +No_list='-13 -14 -15 -16 -18 -20 -22 -24 -26' +Nbursts_awgn=20 +Nbursts_mpp=100 + +# Octave Tx injects noise and is source of truth for SNR, measure BER/PER v SNR +function generate_octave_tx_data { + mode=$1 + channel=$2 + + Nbursts=$Nbursts_awgn + snr_nudge=0 + if [ "$channel" == "mpp" ]; then + Nbursts=$Nbursts_mpp + snr_nudge=4 + fi + + rx_log=$(mktemp) + + i=1 + rm -f snr_oct_${mode}_${channel}*.txt + rm -f ber_oct_${mode}_${channel}*.txt + rm -f per_oct_${mode}_${channel}*.txt + for snr in $snr_list + do + snr_adj=$((${snr}+${snr_nudge})) + echo "warning ('off', 'Octave:data-file-in-path'); + ofdm_ldpc_tx('test_${mode}.raw','${mode}',1,${snr_adj},'${channel}','bursts',${Nbursts},'crc'); + quit" | DISPLAY="" octave-cli -p ${CODEC2}/octave + freedv_data_raw_rx --testframes $mode test_${mode}.raw /dev/null 2>${rx_log} -v + BERmeas=$(cat ${rx_log} | grep 'BER......:' | cut -d' ' -f2) + PERmeas=$(cat ${rx_log} | grep 'Coded FER' | cut -d' ' -f3) + + echo ${snr_adj} >> snr_oct_${mode}_${channel}.txt + echo ${BERmeas} >> ber_oct_${mode}_${channel}.txt + echo ${PERmeas} >> per_oct_${mode}_${channel}.txt + i=$((i+1)) + done + echo 0 > offset_oct_${mode}_${channel}.txt +} + +# ch injects noise and is source of truth for SNR, measure BER/PER v SNR +# Octave Tx +function generate_ch_data { + mode=$1 + channel=$2 + + ch_multipath='' + Nbursts=$Nbursts_awgn + snr_nudge=0 + if [ "$channel" == "mpp" ]; then + ch_multipath='--mpp' + Nbursts=$Nbursts_mpp + snr_nudge=4 + fi + + octave_log=$(mktemp) + ch_log=$(mktemp) + rx_log=$(mktemp) + + i=1 + rm -f snr_ch_${mode}_${channel}*.txt + rm -f ber_ch_${mode}_${channel}*.txt + rm -f per_ch_${mode}_${channel}*.txt + for No in $No_list + do + No_adj=$((${No}-${snr_nudge})) + echo "warning ('off', 'Octave:data-file-in-path'); + ofdm_ldpc_tx('test_${mode}.raw','${mode}',1,100,'awgn','bursts',${Nbursts},'crc'); + quit" | DISPLAY="" octave-cli -p ${CODEC2}/octave 1>${octave_log} + SNRoffset=$(cat ${octave_log} | grep 'Burst offset:' | cut -d' ' -f5) + + ch test_${mode}.raw - --No $No_adj ${ch_multipath} --fading_dir ${FADING_DIR} 2>>${ch_log} | \ + freedv_data_raw_rx --testframes $mode - /dev/null -v 2>${rx_log} + BERmeas=$(cat ${rx_log} | grep 'BER......:' | cut -d' ' -f2) + PERmeas=$(cat ${rx_log} | grep 'Coded FER' | cut -d' ' -f3) + + echo ${BERmeas} >> ber_ch_${mode}_${channel}.txt + echo ${PERmeas} >> per_ch_${mode}_${channel}.txt + i=$((i+1)) + + # trap not enough fading file samples (with mpp) + grep "Fading file finished" ${ch_log} + if [ $? -eq 0 ]; then + cat ${ch_log} + exit 1 + fi + done + + echo ${SNRoffset} > offset_ch_${mode}_${channel}.txt + SNRch=$(cat ${ch_log} | grep SNR3k | tr -s ' ' | cut -d' ' -f3) + echo ${SNRch} > snr_ch_${mode}_${channel}.txt +} + +# ch injects noise and is source of truth for SNR, measure BER/PER v SNR and +# SNR estimates v SNR from rx, C Tx +function generate_snrest_v_snr_data { + mode=$1 + channel=$2 + + snr_nudge=0 + aNo_list=$No_list + + # nudge SNR test range to get meaningful results for these tests + if [ "$mode" == "datac1" ]; then + snr_nudge=4 + fi + if [[ "$mode" == "datac4" || "$mode" == "datac13" || "$mode" == "datac14" ]]; then + snr_nudge=-6 + fi + + ch_multipath='' + Nbursts=$Nbursts_awgn + if [ "$channel" == "mpp" ]; then + ch_multipath='--mpp' + Nbursts=$Nbursts_mpp + snr_nudge=$((${snr_nudge}+4)) + fi + + clip=0 + id='ctx' + if [ "$#" -eq 3 ]; then + clip=$3 + id='ctxc' + snr_nudge=$((${snr_nudge}-4)) + fi + + tx_log=$(mktemp) + ch_log=$(mktemp) + rx_log=$(mktemp) + + i=1 + rm -f snrest_${id}_${mode}_${channel}*.txt + rm -f ber_${id}_${mode}_${channel}*.txt + rm -f per_${id}_${mode}_${channel}*.txt + for No in $aNo_list + do + No_adj=$((${No}-${snr_nudge})) + freedv_data_raw_tx --clip ${clip} --delay 1000 --txbpf ${clip} --bursts $Nbursts --testframes $Nbursts $mode /dev/zero - 2>${tx_log} | \ + ch - - --No $No_adj ${ch_multipath} --fading_dir ${FADING_DIR} 2>>${ch_log} | \ + freedv_data_raw_rx --testframes $mode - /dev/null 2>${rx_log} -v + SNRoffset=$(cat ${tx_log} | grep "mark:space" | tr -s ' ' | cut -d' ' -f 5) + + SNRest=$(cat ${rx_log} | grep '\-BS\-' | tr -s ' ' | cut -d' ' -f17) + if [ ! -z "$SNRest" ]; then + echo ${SNRest} > snrest_${id}_${mode}_${channel}_${i}.txt + fi + BERmeas=$(cat ${rx_log} | grep 'BER......:' | cut -d' ' -f2) + PERmeas=$(cat ${rx_log} | grep 'Coded FER' | cut -d' ' -f3) + echo ${BERmeas} >> ber_${id}_${mode}_${channel}.txt + echo ${PERmeas} >> per_${id}_${mode}_${channel}.txt + i=$((i+1)) + done + + echo ${SNRoffset} > offset_${id}_${mode}_${channel}.txt + + # trap not enough fading file samples (with mpp) + grep "Fading file finished" ${ch_log} + if [ $? -eq 0 ]; then + cat ${ch_log} + exit 1 + fi + SNRch=$(cat ${ch_log} | grep SNR3k | tr -s ' ' | cut -d' ' -f3) + echo ${SNRch} > snr_${id}_${mode}_${channel}.txt +} + +# Sanity check to make sure Octave/CML is set up OK +function test_ldpc { + echo "ldpcut; quit" | DISPLAY="" octave-cli -p ${CODEC2}/octave + if [ "$?" -ne 0 ]; then + echo "basic octave test failed, you may need to" + echo "(a) run ctests to create build_xxx/cml" + echo "(b) set up ~/.octaverc as per octave/ldpc.m" + exit 1 + else + echo "OK" + fi +} From 735d7882764134f6982586683a42ed79f5e95951 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 3 Apr 2024 17:16:58 +1030 Subject: [PATCH 07/26] WIP freedv API support for custom raw data modes --- src/freedv_700.c | 31 +++++++++++++++++++------------ src/freedv_api.c | 27 ++++++++++++++++++--------- src/freedv_api.h | 7 +++++++ src/freedv_api_internal.h | 2 +- 4 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/freedv_700.c b/src/freedv_700.c index 7c8536c3..a7d7aac1 100644 --- a/src/freedv_700.c +++ b/src/freedv_700.c @@ -189,7 +189,7 @@ void freedv_ofdm_voice_open(struct freedv *f, char *mode) { // open function for OFDM data modes, TODO consider moving to a new // (freedv_ofdm_data.c) file -void freedv_ofdm_data_open(struct freedv *f) { +void freedv_ofdm_data_open(struct freedv *f, struct freedv_advanced *adv) { struct OFDM_CONFIG ofdm_config; char mode[32]; if (f->mode == FREEDV_MODE_DATAC0) strcpy(mode, "datac0"); @@ -198,8 +198,14 @@ void freedv_ofdm_data_open(struct freedv *f) { if (f->mode == FREEDV_MODE_DATAC4) strcpy(mode, "datac4"); if (f->mode == FREEDV_MODE_DATAC13) strcpy(mode, "datac13"); if (f->mode == FREEDV_MODE_DATAC14) strcpy(mode, "datac14"); - - ofdm_init_mode(mode, &ofdm_config); + if (f->mode == FREEDV_MODE_DATA_CUSTOM) { + assert(adv != NULL); + assert(adv->config != NULL); + memcpy(&ofdm_config, (struct OFDM_CONFIG *)adv->config, + sizeof(struct OFDM_CONFIG)); + } else { + ofdm_init_mode(mode, &ofdm_config); + } f->ofdm = ofdm_create(&ofdm_config); assert(f->ofdm != NULL); @@ -219,8 +225,8 @@ void freedv_ofdm_data_open(struct freedv *f) { f->ofdm_ntxtbits = ofdm_config.txtbits; /* payload bits per FreeDV API "frame". In OFDM modem nomenclature this is - the number of payload data bits per packet, or the number of data bits in a - LDPC codeword */ + the number of payload data bits per packet, or the number of data bits in + a LDPC codeword */ f->bits_per_modem_frame = f->ldpc->data_bits_per_frame; // buffers for received symbols for one packet/LDPC codeword - may span many @@ -299,8 +305,8 @@ int freedv_comprx_700c(struct freedv *f, COMP demod_in_8kHz[]) { int rx_status = 0; - // quisk_cfInterpDecim() modifies input data so lets make a copy just in case - // there is no sync and we need to echo input to output + // quisk_cfInterpDecim() modifies input data so lets make a copy just in + // case there is no sync and we need to echo input to output // freedv_nin(f): input samples at Fs=8000 Hz // f->nin: input samples at Fs=7500 Hz @@ -332,7 +338,8 @@ int freedv_comprx_700c(struct freedv *f, COMP demod_in_8kHz[]) { rx_status |= FREEDV_RX_BITS; } else { if (f->test_frames_diversity) { - /* normal operation - error pattern on frame after diveristy combination + /* normal operation - error pattern on frame after diveristy + * combination */ short error_pattern[COHPSK_BITS_PER_FRAME]; int bit_errors; @@ -471,8 +478,8 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, memcpy(&rx_amps[Nsymsperpacket - Nsymsperframe], ofdm->rx_amp, sizeof(float) * Nsymsperframe); - /* look for UW as frames enter packet buffer, note UW may span several modem - * frames */ + /* look for UW as frames enter packet buffer, note UW may span several + * modem frames */ int st_uw = Nsymsperpacket - ofdm->nuwframes * Nsymsperframe; ofdm_extract_uw(ofdm, &rx_syms[st_uw], &rx_amps[st_uw], rx_uw); @@ -510,8 +517,8 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, else rx_status |= FREEDV_RX_BIT_ERRORS; } else { - // voice modes aren't as strict - pass everything through to the speech - // decoder, but flag frame with possible errors + // voice modes aren't as strict - pass everything through to the + // speech decoder, but flag frame with possible errors rx_status |= FREEDV_RX_BITS; if (parityCheckCount != ldpc->NumberParityBits) rx_status |= FREEDV_RX_BIT_ERRORS; diff --git a/src/freedv_api.c b/src/freedv_api.c index 3ccf63da..a158d2aa 100644 --- a/src/freedv_api.c +++ b/src/freedv_api.c @@ -60,7 +60,7 @@ /* The API version number. The first version is 10. Increment if the API changes in a way that would require changes by the API user. */ -#define VERSION 15 +#define VERSION 16 /* Version 10 Initial version August 2, 2015. @@ -79,6 +79,9 @@ Version 15 December 2022 Removing rarely used DPSK support which is not needed given fast fading modes + + Version 16 April 2024, added field to struct freedv_advanced to support + FREEDV_MODE_DATA_CUSTOM */ char *ofdm_statemode[] = {"search", "trial", "synced"}; @@ -101,7 +104,8 @@ char *rx_sync_flags_to_text[] = {"----", "---T", "--S-", "--ST", "-B--", "-B-T", struct freedv *freedv_open(int mode) { // defaults for those modes that support the use of adv - struct freedv_advanced adv = {0, 2, 100, 8000, 1000, 200, "H_256_512_4"}; + struct freedv_advanced adv = {0, 2, 100, 8000, + 1000, 200, "H_256_512_4", NULL}; return freedv_open_advanced(mode, &adv); } @@ -126,7 +130,8 @@ struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, mode)) == false) + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, mode)) == false) return NULL; /* set everything to zero just in case */ @@ -150,12 +155,16 @@ struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) { if (FDV_MODE_ACTIVE(FREEDV_MODE_2400B, mode)) freedv_2400b_open(f); if (FDV_MODE_ACTIVE(FREEDV_MODE_800XA, mode)) freedv_800xa_open(f); if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, mode)) freedv_fsk_ldpc_open(f, adv); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, mode)) freedv_ofdm_data_open(f); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, mode)) freedv_ofdm_data_open(f); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, mode)) freedv_ofdm_data_open(f); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode)) freedv_ofdm_data_open(f); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode)) freedv_ofdm_data_open(f); - if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, mode)) freedv_ofdm_data_open(f); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC0, mode)) freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC1, mode)) freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, mode)) freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode)) freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode)) + freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, mode)) + freedv_ofdm_data_open(f, NULL); + if (FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, mode)) + freedv_ofdm_data_open(f, adv); varicode_decode_init(&f->varicode_dec_states, 1); diff --git a/src/freedv_api.h b/src/freedv_api.h index e4ca468b..9f72c8f9 100644 --- a/src/freedv_api.h +++ b/src/freedv_api.h @@ -62,6 +62,7 @@ extern "C" { #define FREEDV_MODE_DATAC4 18 #define FREEDV_MODE_DATAC13 19 #define FREEDV_MODE_DATAC14 20 +#define FREEDV_MODE_DATA_CUSTOM 21 // Sample rates used #define FREEDV_FS_8000 8000 @@ -144,6 +145,9 @@ extern "C" { #if !defined(FREEDV_MODE_DATAC14_EN) #define FREEDV_MODE_DATAC14_EN FREEDV_MODE_EN_DEFAULT #endif +#if !defined(FREEDV_MODE_DATA_CUSTOM_EN) +#define FREEDV_MODE_DATA_CUSTOM_EN FREEDV_MODE_EN_DEFAULT +#endif #define FDV_MODE_ACTIVE(mode_name, var) \ ((mode_name##_EN) == 0 ? 0 : (var) == mode_name) @@ -163,6 +167,9 @@ struct freedv_advanced { int first_tone; // Freq of first tone Hz int tone_spacing; // Spacing between tones Hz char *codename; // LDPC codename, from codes listed in ldpc_codes.c + + // parameters for FREEDV_MODE_DATA_CUSTOM + void *config; // ptr to struct OFDM_CONFIG }; // Called when text message char is decoded diff --git a/src/freedv_api_internal.h b/src/freedv_api_internal.h index ef6426f6..9559a2d9 100644 --- a/src/freedv_api_internal.h +++ b/src/freedv_api_internal.h @@ -221,7 +221,7 @@ void freedv_2400a_open(struct freedv *f); void freedv_2400b_open(struct freedv *f); void freedv_800xa_open(struct freedv *f); void freedv_fsk_ldpc_open(struct freedv *f, struct freedv_advanced *adv); -void freedv_ofdm_data_open(struct freedv *f); +void freedv_ofdm_data_open(struct freedv *f, struct freedv_advanced *adv); // each mode has tx and rx functions in various flavours for real and complex // valued samples From cf069d2e62bd1e69efaddcfa60ca37374ed025b5 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 3 Apr 2024 18:00:35 +1030 Subject: [PATCH 08/26] demo custom mode --- src/freedv_api.c | 15 ++++++++++----- src/freedv_data_raw_rx.c | 24 ++++++++++++++++++++++-- src/freedv_data_raw_tx.c | 26 +++++++++++++++++++++++--- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/freedv_api.c b/src/freedv_api.c index a158d2aa..d04dd7c8 100644 --- a/src/freedv_api.c +++ b/src/freedv_api.c @@ -247,7 +247,8 @@ void freedv_close(struct freedv *freedv) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, freedv->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, freedv->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, freedv->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, freedv->mode)) { + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, freedv->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, freedv->mode)) { FREE(freedv->rx_syms); FREE(freedv->rx_amps); FREE(freedv->ldpc); @@ -279,7 +280,8 @@ static int is_ofdm_mode(struct freedv *f) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode); + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode); } static int is_ofdm_data_mode(struct freedv *f) { @@ -288,7 +290,8 @@ static int is_ofdm_data_mode(struct freedv *f) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode); + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode); } /*---------------------------------------------------------------------------*\ @@ -479,7 +482,8 @@ void freedv_rawdatacomptx(struct freedv *f, COMP mod_out[], FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode)) + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode)) freedv_comptx_ofdm(f, mod_out); if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, f->mode)) { @@ -1079,7 +1083,8 @@ int freedv_rawdatacomprx(struct freedv *f, unsigned char *packed_payload_bits, FDV_MODE_ACTIVE(FREEDV_MODE_DATAC3, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode)) + FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode)) rx_status = freedv_comp_short_rx_ofdm(f, (void *)demod_in, 0, 1.0f); if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, f->mode)) { rx_status = freedv_rx_fsk_ldpc_data(f, demod_in); diff --git a/src/freedv_data_raw_rx.c b/src/freedv_data_raw_rx.c index 0353ae54..435648d7 100644 --- a/src/freedv_data_raw_rx.c +++ b/src/freedv_data_raw_rx.c @@ -39,6 +39,7 @@ #include "ldpc_codes.h" #include "modem_stats.h" #include "octave.h" +#include "ofdm_internal.h" /* other processes can end this program using signals */ @@ -216,6 +217,8 @@ int main(int argc, char *argv[]) { mode = FREEDV_MODE_DATAC13; if (!strcmp(argv[dx], "DATAC14") || !strcmp(argv[dx], "datac14")) mode = FREEDV_MODE_DATAC14; + if (!strcmp(argv[dx], "CUSTOM") || !strcmp(argv[dx], "custom")) + mode = FREEDV_MODE_DATA_CUSTOM; if (mode == -1) { fprintf(stderr, "Error in mode: %s\n", argv[dx]); exit(1); @@ -248,6 +251,23 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Setting estimator limits to %d to %d Hz.\n", fsk_lower, fsk_upper); fsk_set_freq_est_limits(fsk, fsk_lower, fsk_upper); + } else if (mode == FREEDV_MODE_DATA_CUSTOM) { + // demonstrate custom OFDM raw data modes + struct OFDM_CONFIG ofdm_config; + ofdm_init_mode("datac14", &ofdm_config); + // modify datac14 to have 3 carriers instead of 4, which means + // we have to tweak Np, and the number of unique word bits + ofdm_config.nc = 3; + ofdm_config.np = 6; + ofdm_config.nuwbits = 48; + ofdm_config.bad_uw_errors = 18; + uint8_t uw[] = {1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0}; + memcpy(ofdm_config.tx_uw, uw, sizeof(uw)); + memcpy(&ofdm_config.tx_uw[ofdm_config.nuwbits - sizeof(uw)], uw, + sizeof(uw)); + adv.config = (void *)&ofdm_config; + freedv = freedv_open_advanced(mode, &adv); } else { freedv = freedv_open(mode); } @@ -264,8 +284,8 @@ int main(int argc, char *argv[]) { fsk->Ndft); } - /* for streaming bytes it's much easier use the modes that have a multiple of - * 8 payload bits/frame */ + /* for streaming bytes it's much easier use the modes that have a multiple + * of 8 payload bits/frame */ assert((freedv_get_bits_per_modem_frame(freedv) % 8) == 0); int bytes_per_modem_frame = freedv_get_bits_per_modem_frame(freedv) / 8; // last two bytes used for CRC diff --git a/src/freedv_data_raw_tx.c b/src/freedv_data_raw_tx.c index cb741077..ce5da562 100644 --- a/src/freedv_data_raw_tx.c +++ b/src/freedv_data_raw_tx.c @@ -238,6 +238,8 @@ int main(int argc, char *argv[]) { mode = FREEDV_MODE_DATAC13; if (!strcmp(argv[dx], "DATAC14") || !strcmp(argv[dx], "datac14")) mode = FREEDV_MODE_DATAC14; + if (!strcmp(argv[dx], "CUSTOM") || !strcmp(argv[dx], "custom")) + mode = FREEDV_MODE_DATA_CUSTOM; if (mode == -1) { fprintf(stderr, "Error: in mode: %s", argv[dx]); exit(1); @@ -259,10 +261,28 @@ int main(int argc, char *argv[]) { exit(1); } - if (mode != FREEDV_MODE_FSK_LDPC) - freedv = freedv_open(mode); - else + if (mode == FREEDV_MODE_FSK_LDPC) + freedv = freedv_open_advanced(mode, &adv); + else if (mode == FREEDV_MODE_DATA_CUSTOM) { + // demonstrate custom OFDM raw data modes + struct OFDM_CONFIG ofdm_config; + ofdm_init_mode("datac14", &ofdm_config); + // modify datac14 to have 3 carriers instead of 4, which means + // we have to tweak Np, and the number of unique word bits + ofdm_config.nc = 3; + ofdm_config.np = 6; + ofdm_config.nuwbits = 48; + ofdm_config.bad_uw_errors = 18; + uint8_t uw[] = {1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0}; + memcpy(ofdm_config.tx_uw, uw, sizeof(uw)); + memcpy(&ofdm_config.tx_uw[ofdm_config.nuwbits - sizeof(uw)], uw, + sizeof(uw)); + adv.config = (void *)&ofdm_config; freedv = freedv_open_advanced(mode, &adv); + } else { + freedv = freedv_open(mode); + } assert(freedv != NULL); From 1de1b46b1279a13517bcbc3da19d7e1effe6ecd5 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 3 Apr 2024 18:25:40 +1030 Subject: [PATCH 09/26] ctest for custom mode --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa518d87..e62c58a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1291,6 +1291,12 @@ endif(NOT APPLE) ./freedv_data_raw_rx --framesperburst 2 --testframes DATAC0 - /dev/null --vv") set_tests_properties(test_freedv_data_raw_ofdm_datac0_burst PROPERTIES PASS_REGULAR_EXPRESSION "Coded FER: 0.0000 Tfrms: 6 Tfers: 0") + add_test(NAME test_freedv_data_raw_ofdm_data_custom + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + ./freedv_data_raw_tx --bursts 3 --testframes 3 custom /dev/zero - | + ./freedv_data_raw_rx --testframes custom - /dev/null --vv") + set_tests_properties(test_freedv_data_raw_ofdm_data_custom PROPERTIES PASS_REGULAR_EXPRESSION "Coded FER: 0.0000 Tfrms: 3 Tfers: 0") + # Burst mode with data file I/O: add_test(NAME test_freedv_data_raw_ofdm_datac0_burst_file COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; From e02ef2c0a534bec50267e2dc6bec42b4de062c36 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 4 Apr 2024 06:36:56 +1030 Subject: [PATCH 10/26] EsNo supplied to LDPC dec now a config param --- src/freedv_2020.c | 3 +-- src/freedv_700.c | 2 +- src/ofdm.c | 3 +++ src/ofdm_internal.h | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/freedv_2020.c b/src/freedv_2020.c index f1dee284..4a54b5bd 100644 --- a/src/freedv_2020.c +++ b/src/freedv_2020.c @@ -229,8 +229,7 @@ int freedv_comprx_2020(struct freedv *f, COMP demod_in[]) { f->sync = 0; - // TODO: should be higher for 2020? - float EsNo = 3.0; + float EsNo = pow(10.0, ofdm->EsNodB / 10); /* looking for modem sync */ diff --git a/src/freedv_700.c b/src/freedv_700.c index a7d7aac1..8ab0cbb8 100644 --- a/src/freedv_700.c +++ b/src/freedv_700.c @@ -447,7 +447,7 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, assert((demod_in_is_short == 0) || (demod_in_is_short == 1)); int rx_status = 0; - float EsNo = 3.0; /* further work: estimate this properly from signal */ + float EsNo = pow(10.0, ofdm->EsNodB / 10); f->sync = 0; /* looking for OFDM modem sync */ diff --git a/src/ofdm.c b/src/ofdm.c index ba22f3f9..8f0953bc 100644 --- a/src/ofdm.c +++ b/src/ofdm.c @@ -190,6 +190,7 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->state_machine = "voice1"; ofdm->edge_pilots = 1; ofdm->codename = "HRA_112_112"; + ofdm->EsNodB = 3.0; ofdm->amp_est_mode = 0; ofdm->tx_bpf_en = true; ofdm->rx_bpf_en = false; @@ -224,6 +225,7 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->state_machine = config->state_machine; ofdm->edge_pilots = config->edge_pilots; ofdm->codename = config->codename; + ofdm->EsNodB = config->EsNodB; ofdm->amp_est_mode = config->amp_est_mode; ofdm->tx_bpf_en = config->tx_bpf_en; ofdm->rx_bpf_en = config->rx_bpf_en; @@ -273,6 +275,7 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->config.state_machine = ofdm->state_machine; ofdm->config.edge_pilots = ofdm->edge_pilots; ofdm->config.codename = ofdm->codename; + ofdm->config.EsNodB = ofdm->EsNodB; ofdm->config.amp_est_mode = ofdm->amp_est_mode; ofdm->config.tx_bpf_en = ofdm->tx_bpf_en; ofdm->config.rx_bpf_en = ofdm->rx_bpf_en; diff --git a/src/ofdm_internal.h b/src/ofdm_internal.h index 8007934f..1da990b1 100644 --- a/src/ofdm_internal.h +++ b/src/ofdm_internal.h @@ -108,6 +108,7 @@ struct OFDM_CONFIG { char *data_mode; float fmin; float fmax; + float EsNodB; /* EsNo est used for LDPC decoder */ }; struct OFDM { @@ -248,6 +249,7 @@ struct OFDM { detector */ char *codename; + float EsNodB; /* EsNo est used for LDPC decoder */ char *state_machine; }; From 6a5687c5f5ddf8c9d183ac41491bb179ffb8cbca Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 4 Apr 2024 06:37:41 +1030 Subject: [PATCH 11/26] removed unused EsNo_est_all_symbols --- octave/ofdm_lib.m | 2 -- octave/ofdm_mode.m | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/octave/ofdm_lib.m b/octave/ofdm_lib.m index e4ae35a2..b3c4dc1f 100644 --- a/octave/ofdm_lib.m +++ b/octave/ofdm_lib.m @@ -52,7 +52,6 @@ bad_uw_errors = config.bad_uw_errors; amp_scale = config.amp_scale; amp_est_mode = config.amp_est_mode; - EsNo_est_all_symbols = config.EsNo_est_all_symbols; EsNodB = config.EsNodB; state_machine = config.state_machine; edge_pilots = config.edge_pilots; @@ -275,7 +274,6 @@ % Es/No (SNR) est states - states.EsNo_est_all_symbols = EsNo_est_all_symbols; states.clock_offset_est = 0; % pre-amble for data modes diff --git a/octave/ofdm_mode.m b/octave/ofdm_mode.m index a56edace..a49c94aa 100644 --- a/octave/ofdm_mode.m +++ b/octave/ofdm_mode.m @@ -25,7 +25,6 @@ config.bad_uw_errors = 3; config.amp_scale = 245E3; config.amp_est_mode = 0; - config.EsNo_est_all_symbols = 1; config.EsNodB = 3; config.state_machine = "voice1"; config.edge_pilots = 1; @@ -59,12 +58,12 @@ config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 15*4; config.bad_uw_errors = 5; config.state_machine = "data"; config.ftwindow_width = 32; config.amp_scale = 132E3; - config.EsNo_est_all_symbols = 0; config.amp_est_mode = 1; config.EsNodB = 10; + config.amp_est_mode = 1; config.EsNodB = 10; elseif strcmp(mode,"qam16c2") Ns=5; config.Np=31; Tcp = 0.004; Ts = 0.016; Nc = 33; config.data_mode = "streaming"; config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 42*4; config.bad_uw_errors = 15; config.ftwindow_width = 80; config.amp_scale = 135E3; config.state_machine = "data"; - config.EsNo_est_all_symbols = 0; config.amp_est_mode = 1; config.EsNodB = 10; + config.amp_est_mode = 1; config.EsNodB = 10; config.tx_uw = zeros(1,config.Nuwbits = 42*4); config.tx_uw(1:24) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; config.tx_uw(end-24+1:end) = [1 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0]; From 4726a97450cca090a13fe6846009013c017754c6 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 4 Apr 2024 09:16:21 +1030 Subject: [PATCH 12/26] WIP qam16c2, ofdm_mod runs but doesn't decode in Octave --- CMakeLists.txt | 2 +- octave/ofdm_lib.m | 2 +- src/ofdm.c | 40 +++++++++++++++++++++++++++------------- src/ofdm_internal.h | 2 +- src/ofdm_mode.c | 26 ++++++++++++++++++++------ 5 files changed, 50 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e62c58a7..cd45f4f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -472,7 +472,7 @@ endif() add_test(NAME test_OFDM_modem_octave_qam16_uncoded COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; - echo \"ofdm_tx('test_qam16.raw','qam16c1',3,12,'awgn','bursts',3); ofdm_rx('test_qam16.raw','qam16c1', 'passber', 0.05, 'packetsperburst', 1); quit\" | + echo \"ofdm_tx('test_qam16.raw','qam16c2',1,12,'awgn','bursts',3); ofdm_rx('test_qam16.raw','qam16c2', 'passber', 0.05, 'packetsperburst', 1); quit\" | DISPLAY=\"\" octave-cli") set_tests_properties(test_OFDM_modem_octave_qam16_uncoded PROPERTIES PASS_REGULAR_EXPRESSION "Pass") diff --git a/octave/ofdm_lib.m b/octave/ofdm_lib.m index b3c4dc1f..18a1df90 100644 --- a/octave/ofdm_lib.m +++ b/octave/ofdm_lib.m @@ -76,7 +76,7 @@ 1 + j, 1 + j*3, 3 + j, 3 + j*3; 1 - j, 1 - j*3, 3 - j, 3 - j*3; -1 + j, -1 + j*3, -3 + j, -3 + j*3; - -1 - j, -1 - j*3, -3 - j, -3 - j*3]/3; + -1 - j, -1 - j*3, -3 - j, -3 - j*3]; rms = sqrt(states.qam16(:)'*states.qam16(:)/16);% set average Es to 1 states.qam16 /= rms; states.qam16 *= exp(-j*pi/4); % same rotation as QPSK constellation diff --git a/src/ofdm.c b/src/ofdm.c index 8f0953bc..4fee2ece 100644 --- a/src/ofdm.c +++ b/src/ofdm.c @@ -75,13 +75,22 @@ static void ofdm_demod_core(struct OFDM *, int *); */ static const complex float qpsk[] = {1.0f + 0.0f * I, 0.0f + 1.0f * I, 0.0f - 1.0f * I, -1.0f + 0.0f * I}; - +/* static const complex float qam16[] = { 1.0f + 1.0f * I, 1.0f + 3.0f * I, 3.0f + 1.0f * I, 3.0f + 3.0f * I, 1.0f - 1.0f * I, 1.0f - 3.0f * I, 3.0f - 1.0f * I, 3.0f - 3.0f * I, -1.0f + 1.0f * I, -1.0f + 3.0f * I, -3.0f + 1.0f * I, -3.0f + 3.0f * I, -1.0f - 1.0f * I, -1.0f - 3.0f * I, -3.0f - 1.0f * I, -3.0f - 3.0f * I}; - +*/ +static const complex float qam16[] = { + 4.4721e-01 + 2.7756e-17 * I, 8.9443e-01 + 4.4721e-01 * I, + 8.9443e-01 - 4.4721e-01 * I, 1.3416e+00 + 1.1102e-16 * I, + 2.7756e-17 - 4.4721e-01 * I, -4.4721e-01 - 8.9443e-01 * I, + 4.4721e-01 - 8.9443e-01 * I, 1.1102e-16 - 1.3416e+00 * I, + -2.7756e-17 + 4.4721e-01 * I, 4.4721e-01 + 8.9443e-01 * I, + -4.4721e-01 + 8.9443e-01 * I, -1.1102e-16 + 1.3416e+00 * I, + -4.4721e-01 - 2.7756e-17 * I, -8.9443e-01 - 4.4721e-01 * I, + -8.9443e-01 + 4.4721e-01 * I, -1.3416e+00 - 1.1102e-16 * I}; /* * These pilots are compatible with Octave version */ @@ -468,12 +477,12 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { MALLOC(sizeof(complex float) * (ofdm->nuwbits / ofdm->bps)); assert(ofdm->tx_uw_syms != NULL); - assert(ofdm->bps == 2); // TODO generalise - for (int s = 0; s < (ofdm->nuwbits / ofdm->bps); s++) { - int dibit[2]; - dibit[1] = ofdm->tx_uw[2 * s]; - dibit[0] = ofdm->tx_uw[2 * s + 1]; - ofdm->tx_uw_syms[s] = qpsk_mod(dibit); + for (int b = 0, s = 0; b < ofdm->nuwbits; b += ofdm->bps, s++) { + int bits[ofdm->bps]; + for (int i = 0; i < ofdm->bps; i++) + bits[ofdm->bps - 1 - i] = ofdm->tx_uw[b + i]; + if (ofdm->bps == 2) ofdm->tx_uw_syms[s] = qpsk_mod(bits); + if (ofdm->bps == 4) ofdm->tx_uw_syms[s] = qam16_mod(bits); } /* sync state machine */ @@ -1208,7 +1217,14 @@ void ofdm_mod(struct OFDM *ofdm, COMP *result, const int *tx_bits) { tx_sym_lin[i] = qpsk_mod(dibit); } - } /* else if (ofdm->bps == 3) { } TODO */ + } else if (ofdm->bps == 4) { + for (int b = 0, s = 0; b < ofdm->bitsperpacket; b += ofdm->bps, s++) { + int bits[ofdm->bps]; + for (int i = 0; i < ofdm->bps; i++) + bits[ofdm->bps - 1 - i] = tx_bits[b + i] & 0x1; + tx_sym_lin[s] = qam16_mod(bits); + } + } ofdm_txframe(ofdm, tx, tx_sym_lin); } @@ -2436,10 +2452,6 @@ void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *ofdm, int p = 0; int u = 0; - assert( - ofdm->bps == - 2); /* this only works for QPSK at this stage (e.g. modem packet mod) */ - for (s = 0; s < (Nsymsperpacket - Ntxtsyms); s++) { if ((u < Nuwsyms) && (s == ofdm->uw_ind_sym[u])) { modem_packet[s] = ofdm->tx_uw_syms[u++]; @@ -2451,6 +2463,8 @@ void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *ofdm, assert(u == Nuwsyms); assert(p == (Nsymsperpacket - Nuwsyms - Ntxtsyms)); + /* txt bit insertion only works for QPSK at this stage */ + assert((Ntxtsyms == 0) || (ofdm->bps == 2)); for (t = 0; s < Nsymsperpacket; s++, t += 2) { dibit[1] = txt_bits[t] & 0x1; dibit[0] = txt_bits[t + 1] & 0x1; diff --git a/src/ofdm_internal.h b/src/ofdm_internal.h index 1da990b1..bc8d525c 100644 --- a/src/ofdm_internal.h +++ b/src/ofdm_internal.h @@ -46,7 +46,7 @@ extern "C" { #define TAU (2.0f * M_PI) #define ROT45 (M_PI / 4.0f) -#define MAX_UW_BITS 64 +#define MAX_UW_BITS 192 #define cmplx(value) (cosf(value) + sinf(value) * I) #define cmplxconj(value) (cosf(value) + sinf(value) * -I) diff --git a/src/ofdm_mode.c b/src/ofdm_mode.c index f92d2884..0c616de5 100644 --- a/src/ofdm_mode.c +++ b/src/ofdm_mode.c @@ -42,6 +42,7 @@ void ofdm_init_mode(char mode[], struct OFDM_CONFIG *config) { config->state_machine = "voice1"; config->data_mode = ""; config->codename = "HRA_112_112"; + config->EsNodB = 3.0; config->clip_gain1 = 2.5; config->clip_gain2 = 0.8; config->clip_en = true; @@ -92,23 +93,36 @@ void ofdm_init_mode(char mode[], struct OFDM_CONFIG *config) { config->state_machine = "voice2"; config->ftwindowwidth = 64; config->foff_limiter = true; - } else if (strcmp(mode, "qam16") == 0) { - /* not in use yet */ + } else if (strcmp(mode, "qam16c2") == 0) { config->ns = 5; - config->np = 5; + config->np = 31; config->tcp = 0.004; config->ts = 0.016; config->nc = 33; config->bps = 4; config->txtbits = 0; - config->nuwbits = 15 * 4; - config->bad_uw_errors = 5; - config->ftwindowwidth = 32; + config->nuwbits = 42 * 4; + assert(config->nuwbits <= MAX_UW_BITS); + config->bad_uw_errors = 15; + config->ftwindowwidth = 80; config->state_machine = "data"; config->amp_est_mode = 1; config->tx_bpf_en = false; config->clip_en = false; config->data_mode = "streaming"; + config->amp_scale = 135E3; + config->clip_en = false; + config->tx_bpf_en = false; + config->rx_bpf_en = false; + + uint8_t uw[] = {1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0}; + memset(config->tx_uw, 0, config->nuwbits); + memcpy(config->tx_uw, uw, sizeof(uw)); + memcpy(&config->tx_uw[config->nuwbits - sizeof(uw)], uw, sizeof(uw)); + + config->EsNodB = 10; + config->codename = "H_16200_9720"; } else if (strcmp(mode, "datac0") == 0) { config->ns = 5; config->np = 4; From 10d344cb5968026efc7c08cbb1a26aff83c75637 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Thu, 4 Apr 2024 14:14:01 +1030 Subject: [PATCH 13/26] uncoded C tx works with Octave rx --- octave/ofdm_lib.m | 8 ++++---- octave/qam16.m | 2 +- src/ofdm.c | 10 ++-------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/octave/ofdm_lib.m b/octave/ofdm_lib.m index 18a1df90..93a2da7a 100644 --- a/octave/ofdm_lib.m +++ b/octave/ofdm_lib.m @@ -73,9 +73,9 @@ states.Nsampersymbol = states.M+states.Ncp; % number of samples in a single symbol states.Nsamperframe = Ns*states.Nsampersymbol; % number of samples in a modem frame states.qam16 = [ - 1 + j, 1 + j*3, 3 + j, 3 + j*3; - 1 - j, 1 - j*3, 3 - j, 3 - j*3; - -1 + j, -1 + j*3, -3 + j, -3 + j*3; + 1 + j, 1 + j*3, 3 + j, 3 + j*3, ... + 1 - j, 1 - j*3, 3 - j, 3 - j*3, ... + -1 + j, -1 + j*3, -3 + j, -3 + j*3, ... -1 - j, -1 - j*3, -3 - j, -3 - j*3]; rms = sqrt(states.qam16(:)'*states.qam16(:)/16);% set average Es to 1 states.qam16 /= rms; @@ -127,7 +127,7 @@ states.uw_ind = [states.uw_ind bps*ind_sym-b]; % bit index end end - + % how many of the first few frames have UW symbols in them Nsymsperframe = states.Nbitsperframe/states.bps; states.Nuwframes = ceil(states.uw_ind_sym(end)/Nsymsperframe); diff --git a/octave/qam16.m b/octave/qam16.m index 5b0f047c..7d2dff4d 100644 --- a/octave/qam16.m +++ b/octave/qam16.m @@ -25,7 +25,7 @@ function test_qam16_mod_demod(constellation) for decimal=0:15 tx_bits = zeros(1,4); for i=1:4 - tx_bits(1,5-i) = bitand(bitshift(decimal-1,1-i),1); + tx_bits(1,5-i) = bitand(bitshift(decimal,1-i),1); end symbol = qam16_mod(constellation, tx_bits); rx_bits = qam16_demod(constellation,symbol); diff --git a/src/ofdm.c b/src/ofdm.c index 4fee2ece..26fb2016 100644 --- a/src/ofdm.c +++ b/src/ofdm.c @@ -75,13 +75,7 @@ static void ofdm_demod_core(struct OFDM *, int *); */ static const complex float qpsk[] = {1.0f + 0.0f * I, 0.0f + 1.0f * I, 0.0f - 1.0f * I, -1.0f + 0.0f * I}; -/* -static const complex float qam16[] = { - 1.0f + 1.0f * I, 1.0f + 3.0f * I, 3.0f + 1.0f * I, 3.0f + 3.0f * I, - 1.0f - 1.0f * I, 1.0f - 3.0f * I, 3.0f - 1.0f * I, 3.0f - 3.0f * I, - -1.0f + 1.0f * I, -1.0f + 3.0f * I, -3.0f + 1.0f * I, -3.0f + 3.0f * I, - -1.0f - 1.0f * I, -1.0f - 3.0f * I, -3.0f - 1.0f * I, -3.0f - 3.0f * I}; -*/ + static const complex float qam16[] = { 4.4721e-01 + 2.7756e-17 * I, 8.9443e-01 + 4.4721e-01 * I, 8.9443e-01 - 4.4721e-01 * I, 1.3416e+00 + 1.1102e-16 * I, @@ -91,6 +85,7 @@ static const complex float qam16[] = { -4.4721e-01 + 8.9443e-01 * I, -1.1102e-16 + 1.3416e+00 * I, -4.4721e-01 - 2.7756e-17 * I, -8.9443e-01 - 4.4721e-01 * I, -8.9443e-01 + 4.4721e-01 * I, -1.3416e+00 - 1.1102e-16 * I}; + /* * These pilots are compatible with Octave version */ @@ -476,7 +471,6 @@ struct OFDM *ofdm_create(const struct OFDM_CONFIG *config) { ofdm->tx_uw_syms = MALLOC(sizeof(complex float) * (ofdm->nuwbits / ofdm->bps)); assert(ofdm->tx_uw_syms != NULL); - for (int b = 0, s = 0; b < ofdm->nuwbits; b += ofdm->bps, s++) { int bits[ofdm->bps]; for (int i = 0; i < ofdm->bps; i++) From 31d8044c89bcdea03dc711c8293b16e8abeca5fa Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 5 Apr 2024 04:57:13 +1030 Subject: [PATCH 14/26] WIP qam demod --- src/ofdm.c | 48 ++++++++++++------------------------------------ 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/src/ofdm.c b/src/ofdm.c index 26fb2016..f1773595 100644 --- a/src/ofdm.c +++ b/src/ofdm.c @@ -1193,31 +1193,14 @@ void ofdm_mod(struct OFDM *ofdm, COMP *result, const int *tx_bits) { complex float *tx = (complex float *)result; // complex has same memory layout complex float tx_sym_lin[length]; - int dibit[2]; - int s, i; - - if (ofdm->bps == 1) { - /* Here we will have Nbitsperpacket / 1 */ - - for (s = 0; s < length; s++) { - tx_sym_lin[s] = (float)(2 * tx_bits[s] - 1); - } - } else if (ofdm->bps == 2) { - /* Here we will have Nbitsperpacket / 2 */ - - for (s = 0, i = 0; i < length; s += 2, i++) { - dibit[0] = tx_bits[s + 1] & 0x1; - dibit[1] = tx_bits[s] & 0x1; - tx_sym_lin[i] = qpsk_mod(dibit); - } - } else if (ofdm->bps == 4) { - for (int b = 0, s = 0; b < ofdm->bitsperpacket; b += ofdm->bps, s++) { - int bits[ofdm->bps]; - for (int i = 0; i < ofdm->bps; i++) - bits[ofdm->bps - 1 - i] = tx_bits[b + i] & 0x1; - tx_sym_lin[s] = qam16_mod(bits); - } + assert((ofdm->bps == 2) || (ofdm->bps == 4)); + for (int b = 0, s = 0; b < ofdm->bitsperpacket; b += ofdm->bps, s++) { + int bits[ofdm->bps]; + for (int i = 0; i < ofdm->bps; i++) + bits[ofdm->bps - 1 - i] = tx_bits[b + i] & 0x1; + if (ofdm->bps == 2) tx_sym_lin[s] = qpsk_mod(bits); + if (ofdm->bps == 4) tx_sym_lin[s] = qam16_mod(bits); } ofdm_txframe(ofdm, tx, tx_sym_lin); @@ -1888,7 +1871,7 @@ static void ofdm_demod_core(struct OFDM *ofdm, int *rx_bits) { * frame bit ordering correct */ complex float rx_corr; - int abit[2]; + int abit[ofdm->bps]; int bit_index = 0; float sum_amp = 0.0f; @@ -1935,17 +1918,10 @@ static void ofdm_demod_core(struct OFDM *ofdm, int *rx_bits) { ofdm->aphase_est_pilot_log[(rr * ofdm->nc) + (i - 1)] = aphase_est_pilot[i]; - if (ofdm->bps == 1) { - rx_bits[bit_index++] = crealf(rx_corr) > 0.0f; - } else if (ofdm->bps == 2) { - /* - * Only one final task, decode what quadrant the phase - * is in, and return the dibits - */ - qpsk_demod(rx_corr, abit); - rx_bits[bit_index++] = abit[1]; - rx_bits[bit_index++] = abit[0]; - } + if (ofdm->bps == 2) qpsk_demod(rx_corr, abit); + if (ofdm->bps == 4) qam16_demod(rx_corr, abit); + for (int i = 0; i < ofdm->bps; i++) + rx_bits[bit_index++] = abit[ofdm->bps - 1 - i]; } } From 2841e6c50982d559831a21cccddd74a69e2798f0 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 5 Apr 2024 05:06:39 +1030 Subject: [PATCH 15/26] non-table driven interleaver b --- octave/gp_interleaver.m | 29 +++++++++++++++++++-------- src/gp_interleaver.c | 43 ++++++++++++++--------------------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/octave/gp_interleaver.m b/octave/gp_interleaver.m index 1ee0ee38..c97af0f1 100644 --- a/octave/gp_interleaver.m +++ b/octave/gp_interleaver.m @@ -7,18 +7,31 @@ 1; +% return 1 if prime +function ret = is_prime(x) + for i=2:x-1 + if mod(x,i) == 0 + ret = 0; + return; + end + end + ret = 1; +end + +function x = next_prime(x) + x++; + while is_prime(x) == 0 + x++; + end +end + % Choose b for Golden Prime Interleaver. b is chosen to be the % closest integer, which is relatively prime to N, to the Golden % section of N. function b = choose_interleaver_b(Nbits) - - p = primes(Nbits); - i = 1; - while(p(i) < Nbits/1.62) - i++; - end - b = p(i); + b = floor(Nbits/1.62); + b = next_prime(b); assert(gcd(b,Nbits) == 1, "b and Nbits must be co-prime"); end @@ -36,7 +49,7 @@ function frame = gp_deinterleave(interleaved_frame) Nbits = length(interleaved_frame); - b = choose_interleaver_b(Nbits); + b = choose_interleaver_b(Nbits); frame = zeros(1,Nbits); for i=1:Nbits j = mod((b*(i-1)), Nbits); diff --git a/src/gp_interleaver.c b/src/gp_interleaver.c index 6710b0c3..70b8ad07 100644 --- a/src/gp_interleaver.c +++ b/src/gp_interleaver.c @@ -31,45 +31,32 @@ #include "gp_interleaver.h" #include +#include #include /* Choose b for Golden Prime Interleaver. b is chosen to be the closest integer, which is relatively prime to N, to the Golden section of N. - - Implemented with a LUT in C for convenience, Octave version - has a more complete implementation. If you find you need some more - numbers head back to the Octave choose_interleaver_b() function. */ -static const int b_table[] = { - 48, 31, /* datac14: HRA_56_56, 40 data bits used */ - 56, 37, /* 700E: HRA_56_56 */ - 106, 67, /* 2020B: (112,56) partial protection */ - 112, 71, /* 700D: HRA_112_112 */ - 128, 83, /* datac0: H_128_256_5 */ - 192, 127, /* datac13: H_256_512_4, 128 data bits used */ - 210, 131, /* 2020: HRAb_396_504 with 312 data bits used */ - 736, 457, /* datac4: H_1024_2048_4f, 448 data bits used */ - 1024, 641, /* datac3: H_1024_2048_4f */ - 1290, 797, /* datac2: H2064_516_sparse */ - 4096, 2531 /* datac1: H_4096_8192_3d */ -}; - -int choose_interleaver_b(int Nbits) { - int i; - for (i = 0; i < sizeof(b_table) / sizeof(int); i += 2) { - if (b_table[i] == Nbits) { - return b_table[i + 1]; - } +int is_prime(int x) { + for (int i = 2; i < x; i++) { + if ((x % i) == 0) return 0; } + return 1; +} - /* if we get to here it means a Nbits we don't have in our table so choke */ +int next_prime(int x) { + x++; + while (is_prime(x) == 0) x++; + return x; +} - fprintf(stderr, "gp_interleaver: Nbits: %d, b not found!\n", Nbits); - assert(0); - return -1; +int choose_interleaver_b(int Nbits) { + int b = floor(Nbits / 1.62); + b = next_prime(b); + return b; } void gp_interleave_comp(COMP interleaved_frame[], COMP frame[], int Nbits) { From 1079f804b240df714cb5492d3cd1456502323653 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 5 Apr 2024 06:34:55 +1030 Subject: [PATCH 16/26] ofdm_demod uncoded demods qam16c2 --- src/ofdm.c | 44 ++++++++++++++++++++++---------------------- src/ofdm_demod.c | 10 +++++----- src/ofdm_internal.h | 2 +- unittest/tqam16.c | 2 +- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/ofdm.c b/src/ofdm.c index f1773595..c5debf5e 100644 --- a/src/ofdm.c +++ b/src/ofdm.c @@ -127,10 +127,13 @@ complex float qam16_mod(int *bits) { return qam16[(bits[3] << 3) | (bits[2] << 2) | (bits[1] << 1) | bits[0]]; } -void qam16_demod(complex float symbol, int *bits) { +void qam16_demod(complex float symbol, int *bits, float amp_est) { float dist[16]; int i; + amp_est += 1E-12; // prevent /0 errors + symbol /= amp_est; + for (i = 0; i < 16; i++) { dist[i] = cnormf(symbol - qam16[i]); } @@ -1919,7 +1922,7 @@ static void ofdm_demod_core(struct OFDM *ofdm, int *rx_bits) { aphase_est_pilot[i]; if (ofdm->bps == 2) qpsk_demod(rx_corr, abit); - if (ofdm->bps == 4) qam16_demod(rx_corr, abit); + if (ofdm->bps == 4) qam16_demod(rx_corr, abit, aamp_est_pilot[i]); for (int i = 0; i < ofdm->bps; i++) rx_bits[bit_index++] = abit[ofdm->bps - 1 - i]; } @@ -2458,14 +2461,12 @@ void ofdm_disassemble_qpsk_modem_packet(struct OFDM *ofdm, int Nsymsperpacket = ofdm->bitsperpacket / ofdm->bps; int Nuwsyms = ofdm->nuwbits / ofdm->bps; int Ntxtsyms = ofdm->ntxtbits / ofdm->bps; - int dibit[2]; + int bits[ofdm->bps]; int s, t; int p = 0; int u = 0; - assert(ofdm->bps == 2); /* this only works for QPSK at this stage */ - for (s = 0; s < (Nsymsperpacket - Ntxtsyms); s++) { if ((u < Nuwsyms) && (s == ofdm->uw_ind_sym[u])) { u++; @@ -2479,11 +2480,12 @@ void ofdm_disassemble_qpsk_modem_packet(struct OFDM *ofdm, assert(u == Nuwsyms); assert(p == (Nsymsperpacket - Nuwsyms - Ntxtsyms)); - for (t = 0; s < Nsymsperpacket; s++, t += 2) { - qpsk_demod(rx_syms[s], dibit); + for (t = 0; s < Nsymsperpacket; s++, t += ofdm->bps) { + if (ofdm->bps == 2) qpsk_demod(rx_syms[s], bits); + if (ofdm->bps == 4) qam16_demod(rx_syms[s], bits, rx_amps[s]); - txt_bits[t] = dibit[1]; - txt_bits[t + 1] = dibit[0]; + for (int i = 0; i < ofdm->bps; i++) + txt_bits[t + i] = bits[ofdm->bps - 1 - i]; } assert(t == ofdm->ntxtbits); @@ -2502,13 +2504,12 @@ void ofdm_disassemble_qpsk_modem_packet_with_text_amps( int Nsymsperpacket = ofdm->bitsperpacket / ofdm->bps; int Nuwsyms = ofdm->nuwbits / ofdm->bps; int Ntxtsyms = ofdm->ntxtbits / ofdm->bps; - int dibit[2]; + int bits[ofdm->bps]; int s, t; int p = 0; int u = 0; - assert(ofdm->bps == 2); /* this only works for QPSK at this stage */ assert(textIndex != NULL); for (s = 0; s < (Nsymsperpacket - Ntxtsyms); s++) { @@ -2525,11 +2526,12 @@ void ofdm_disassemble_qpsk_modem_packet_with_text_amps( assert(p == (Nsymsperpacket - Nuwsyms - Ntxtsyms)); *textIndex = s; - for (t = 0; s < Nsymsperpacket; s++, t += 2) { - qpsk_demod(rx_syms[s], dibit); + for (t = 0; s < Nsymsperpacket; s++, t += ofdm->bps) { + if (ofdm->bps == 2) qpsk_demod(rx_syms[s], bits); + if (ofdm->bps == 4) qam16_demod(rx_syms[s], bits, rx_amps[s]); - txt_bits[t] = dibit[1]; - txt_bits[t + 1] = dibit[0]; + for (int i = 0; i < ofdm->bps; i++) + txt_bits[t + i] = bits[ofdm->bps - 1 - i]; } assert(t == ofdm->ntxtbits); @@ -2542,17 +2544,15 @@ void ofdm_extract_uw(struct OFDM *ofdm, complex float rx_syms[], float rx_amps[], uint8_t rx_uw[]) { int Nsymsperframe = ofdm->bitsperframe / ofdm->bps; int Nuwsyms = ofdm->nuwbits / ofdm->bps; - int dibit[2]; int s, u; - assert(ofdm->bps == - 2); /* this only works for QPSK at this stage (e.g. UW demod) */ - for (s = 0, u = 0; s < Nsymsperframe * ofdm->nuwframes; s++) { if ((u < Nuwsyms) && (s == ofdm->uw_ind_sym[u])) { - qpsk_demod(rx_syms[s], dibit); - rx_uw[2 * u] = dibit[1]; - rx_uw[2 * u + 1] = dibit[0]; + int bits[ofdm->bps]; + if (ofdm->bps == 2) qpsk_demod(rx_syms[s], bits); + if (ofdm->bps == 4) qam16_demod(rx_syms[s], bits, rx_amps[s]); + for (int i = 0; i < ofdm->bps; i++) + rx_uw[ofdm->bps * u + i] = bits[ofdm->bps - 1 - i]; u++; } } diff --git a/src/ofdm_demod.c b/src/ofdm_demod.c index 80f01bd6..0f3d8025 100644 --- a/src/ofdm_demod.c +++ b/src/ofdm_demod.c @@ -552,12 +552,12 @@ int main(int argc, char *argv[]) { /* count errors across UW, payload, txt bits */ int rx_bits[Nbitsperpacket]; - int dibit[2]; - assert(ofdm->bps == 2); /* this only works for QPSK at this stage */ + int bits[ofdm->bps]; for (int s = 0; s < Nsymsperpacket; s++) { - qpsk_demod(rx_syms[s], dibit); - rx_bits[2 * s] = dibit[1]; - rx_bits[2 * s + 1] = dibit[0]; + if (ofdm->bps == 2) qpsk_demod(rx_syms[s], bits); + if (ofdm->bps == 4) qam16_demod(rx_syms[s], bits, rx_amps[s]); + for (int i = 0; i < ofdm->bps; i++) + rx_bits[ofdm->bps * s + i] = bits[ofdm->bps - 1 - i]; } for (Nerrs_raw = 0, i = 0; i < Nbitsperpacket; i++) if (tx_bits[i] != rx_bits[i]) Nerrs_raw++; diff --git a/src/ofdm_internal.h b/src/ofdm_internal.h index bc8d525c..b57451ad 100644 --- a/src/ofdm_internal.h +++ b/src/ofdm_internal.h @@ -258,7 +258,7 @@ struct OFDM { complex float qpsk_mod(int *); complex float qam16_mod(int *); void qpsk_demod(complex float, int *); -void qam16_demod(complex float, int *); +void qam16_demod(complex float, int *, float); void ofdm_txframe(struct OFDM *, complex float *, complex float[]); void ofdm_assemble_qpsk_modem_packet(struct OFDM *, uint8_t[], uint8_t[], uint8_t[]); diff --git a/unittest/tqam16.c b/unittest/tqam16.c index 9e4c8d02..04971d3d 100644 --- a/unittest/tqam16.c +++ b/unittest/tqam16.c @@ -19,7 +19,7 @@ int main(void) { int tx_bits[4], rx_bits[4]; for (int i = 0; i < 4; i++) tx_bits[i] = (c >> (3 - i)) & 0x1; complex float symbol = qam16_mod(tx_bits); - qam16_demod(symbol, rx_bits); + qam16_demod(symbol, rx_bits, 1.0); if (memcmp(tx_bits, rx_bits, 4)) { fprintf(stderr, "FAIL on %d!\ntx_bits: ", c); for (int i = 0; i < 4; i++) fprintf(stderr, "%d ", tx_bits[i]); From 73482ea69de469986a23172509624a2e450bc273 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 5 Apr 2024 15:40:35 +1030 Subject: [PATCH 17/26] uncoded BER 0 with ofdm_mod/ofdm_demod, LDPC decode not working yet --- src/freedv_2020.c | 6 +++--- src/freedv_700.c | 7 ++++--- src/interldpc.c | 40 ++++++++++++++++++++++------------------ src/interldpc.h | 3 ++- src/ofdm.c | 29 +++++++++++++++-------------- src/ofdm_demod.c | 13 ++++++------- src/ofdm_internal.h | 22 +++++++++++----------- src/ofdm_mod.c | 3 +-- unittest/tofdm.c | 4 ++-- 9 files changed, 66 insertions(+), 61 deletions(-) diff --git a/src/freedv_2020.c b/src/freedv_2020.c index 4a54b5bd..99638190 100644 --- a/src/freedv_2020.c +++ b/src/freedv_2020.c @@ -248,7 +248,7 @@ int freedv_comprx_2020(struct freedv *f, COMP demod_in[]) { ofdm_demod(ofdm, rx_bits, demod_in); ofdm_extract_uw(ofdm, ofdm->rx_np, ofdm->rx_amp, rx_uw); - ofdm_disassemble_qpsk_modem_packet_with_text_amps( + ofdm_disassemble_psk_modem_packet_with_text_amps( ofdm, ofdm->rx_np, ofdm->rx_amp, payload_syms, payload_amps, txt_bits, &txt_sym_index); @@ -279,8 +279,8 @@ int freedv_comprx_2020(struct freedv *f, COMP demod_in[]) { uint8_t out_char[coded_bits_per_frame]; if (f->test_frames) { - Nerrs_raw = - count_uncoded_errors(ldpc, &f->ofdm->config, codeword_symbols_de, 0); + Nerrs_raw = count_uncoded_errors(ldpc, &f->ofdm->config, + codeword_symbols_de, payload_amps_de, 0); f->total_bit_errors += Nerrs_raw; f->total_bits += f->ofdm_bitsperframe; } diff --git a/src/freedv_700.c b/src/freedv_700.c index 8ab0cbb8..257b80fb 100644 --- a/src/freedv_700.c +++ b/src/freedv_700.c @@ -490,7 +490,7 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, /* we have received enough modem frames to complete packet and run LDPC * decoder */ int txt_sym_index = 0; - ofdm_disassemble_qpsk_modem_packet_with_text_amps( + ofdm_disassemble_psk_modem_packet_with_text_amps( ofdm, rx_syms, rx_amps, payload_syms, payload_amps, txt_bits, &txt_sym_index); @@ -526,8 +526,9 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, if (f->test_frames) { /* est uncoded BER from payload bits */ - Nerrs_raw = count_uncoded_errors( - ldpc, &f->ofdm->config, payload_syms_de, strlen(ofdm->data_mode)); + Nerrs_raw = + count_uncoded_errors(ldpc, &f->ofdm->config, payload_syms_de, + payload_amps_de, strlen(ofdm->data_mode)); f->total_bit_errors += Nerrs_raw; f->total_bits += Npayloadbitsperpacket; diff --git a/src/interldpc.c b/src/interldpc.c index 8cec6477..cb6f964a 100644 --- a/src/interldpc.c +++ b/src/interldpc.c @@ -134,17 +134,18 @@ void ldpc_encode_frame(struct LDPC *ldpc, int codeword[], for (j = 0; j < ldpc->NumberParityBits; i++, j++) codeword[i] = pbits[j]; } -void qpsk_modulate_frame(COMP tx_symbols[], int codeword[], int n) { +void psk_modulate_frame(int bps, COMP tx_symbols[], int codeword[], int n) { int s, i; - int dibit[2]; - complex float qpsk_symb; - - for (s = 0, i = 0; i < n; s += 2, i++) { - dibit[0] = codeword[s + 1] & 0x1; - dibit[1] = codeword[s] & 0x1; - qpsk_symb = qpsk_mod(dibit); - tx_symbols[i].real = crealf(qpsk_symb); - tx_symbols[i].imag = cimagf(qpsk_symb); + int bits[bps]; + complex float symb; + + assert((bps == 2) || (bps == 4)); + for (s = 0, i = 0; i < n; s += bps, i++) { + for (int b = 0; b < bps; b++) bits[b] = codeword[s + bps - 1 - b] & 0x1; + if (bps == 2) symb = qpsk_mod(bits); + if (bps == 4) symb = qam16_mod(bits); + tx_symbols[i].real = crealf(symb); + tx_symbols[i].imag = cimagf(symb); } } @@ -220,7 +221,8 @@ void ldpc_decode_frame(struct LDPC *ldpc, int *parityCheckCount, int *iter, of txt bits as this is done after we dissassemmble the frame */ int count_uncoded_errors(struct LDPC *ldpc, struct OFDM_CONFIG *config, - COMP codeword_symbols_de[], int crc16) { + COMP codeword_symbols_de[], float codeword_amps_de[], + int crc16) { int i, Nerrs; int coded_syms_per_frame = ldpc->coded_bits_per_frame / config->bps; @@ -248,12 +250,13 @@ int count_uncoded_errors(struct LDPC *ldpc, struct OFDM_CONFIG *config, ldpc_encode_frame(ldpc, test_codeword, tx_bits); for (i = 0; i < coded_syms_per_frame; i++) { - int bits[2]; + int bits[config->bps]; complex float s = codeword_symbols_de[i].real + I * codeword_symbols_de[i].imag; - qpsk_demod(s, bits); - rx_bits_raw[config->bps * i] = bits[1]; - rx_bits_raw[config->bps * i + 1] = bits[0]; + if (config->bps == 2) qpsk_demod(s, bits); + if (config->bps == 4) qam16_demod(s, bits, codeword_amps_de[i]); + for (int b = 0; b < config->bps; b++) + rx_bits_raw[config->bps * i + b] = bits[config->bps - 1 - b]; } Nerrs = 0; @@ -329,10 +332,11 @@ void ofdm_ldpc_interleave_tx(struct OFDM *ofdm, struct LDPC *ldpc, complex float tx_symbols[Nbitsperpacket / ofdm->bps]; ldpc_encode_frame(ldpc, codeword, tx_bits); - qpsk_modulate_frame(payload_symbols, codeword, Npayloadsymsperpacket); + psk_modulate_frame(ofdm->bps, payload_symbols, codeword, + Npayloadsymsperpacket); gp_interleave_comp(payload_symbols_inter, payload_symbols, Npayloadsymsperpacket); - ofdm_assemble_qpsk_modem_packet_symbols(ofdm, tx_symbols, - payload_symbols_inter, txt_bits); + ofdm_assemble_psk_modem_packet_symbols(ofdm, tx_symbols, + payload_symbols_inter, txt_bits); ofdm_txframe(ofdm, tx_sams, tx_symbols); } diff --git a/src/interldpc.h b/src/interldpc.h index 3b1d1caf..1c72558f 100644 --- a/src/interldpc.h +++ b/src/interldpc.h @@ -49,7 +49,8 @@ void qpsk_modulate_frame(COMP tx_symbols[], int codeword[], int n); void ldpc_decode_frame(struct LDPC *ldpc, int *parityCheckCount, int *iter, uint8_t out_char[], float llr[]); int count_uncoded_errors(struct LDPC *ldpc, struct OFDM_CONFIG *config, - COMP codeword_symbols_de[], int crc16); + COMP codeword_symbols_de[], float codeword_amps_de[], + int crc16); int count_errors(uint8_t tx_bits[], uint8_t rx_bits[], int n); void count_errors_protection_mode(int protection_mode, int *pNerrs, int *pNcoded, uint8_t tx_bits[], diff --git a/src/ofdm.c b/src/ofdm.c index c5debf5e..bb9a10f2 100644 --- a/src/ofdm.c +++ b/src/ofdm.c @@ -2381,9 +2381,9 @@ void ofdm_get_demod_stats(struct OFDM *ofdm, struct MODEM_STATS *stats, /* * Assemble packet of bits from UW, payload bits, and txt bits */ -void ofdm_assemble_qpsk_modem_packet(struct OFDM *ofdm, uint8_t modem_frame[], - uint8_t payload_bits[], - uint8_t txt_bits[]) { +void ofdm_assemble_psk_modem_packet(struct OFDM *ofdm, uint8_t modem_frame[], + uint8_t payload_bits[], + uint8_t txt_bits[]) { int s, t; int p = 0; @@ -2410,10 +2410,10 @@ void ofdm_assemble_qpsk_modem_packet(struct OFDM *ofdm, uint8_t modem_frame[], /* * Assemble packet of symbols from UW, payload symbols, and txt bits */ -void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *ofdm, - complex float modem_packet[], - COMP payload_syms[], - uint8_t txt_bits[]) { +void ofdm_assemble_psk_modem_packet_symbols(struct OFDM *ofdm, + complex float modem_packet[], + COMP payload_syms[], + uint8_t txt_bits[]) { complex float *payload = (complex float *)&payload_syms[0]; // complex has same memory layout int Nsymsperpacket = ofdm->bitsperpacket / ofdm->bps; @@ -2436,7 +2436,8 @@ void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *ofdm, assert(u == Nuwsyms); assert(p == (Nsymsperpacket - Nuwsyms - Ntxtsyms)); - /* txt bit insertion only works for QPSK at this stage */ + /* txt bit insertion only works for QPSK at this stage, however QAM modes + * generally has no txt bits */ assert((Ntxtsyms == 0) || (ofdm->bps == 2)); for (t = 0; s < Nsymsperpacket; s++, t += 2) { dibit[1] = txt_bits[t] & 0x1; @@ -2451,11 +2452,11 @@ void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *ofdm, * Disassemble a received packet of symbols into UW bits and payload data * symbols */ -void ofdm_disassemble_qpsk_modem_packet(struct OFDM *ofdm, - complex float rx_syms[], - float rx_amps[], COMP codeword_syms[], - float codeword_amps[], - short txt_bits[]) { +void ofdm_disassemble_psk_modem_packet(struct OFDM *ofdm, + complex float rx_syms[], float rx_amps[], + COMP codeword_syms[], + float codeword_amps[], + short txt_bits[]) { complex float *codeword = (complex float *)&codeword_syms[0]; // complex has same memory layout int Nsymsperpacket = ofdm->bitsperpacket / ofdm->bps; @@ -2495,7 +2496,7 @@ void ofdm_disassemble_qpsk_modem_packet(struct OFDM *ofdm, * Disassemble a received packet of symbols into UW bits and payload data * symbols */ -void ofdm_disassemble_qpsk_modem_packet_with_text_amps( +void ofdm_disassemble_psk_modem_packet_with_text_amps( struct OFDM *ofdm, complex float rx_syms[], float rx_amps[], COMP codeword_syms[], float codeword_amps[], short txt_bits[], int *textIndex) { diff --git a/src/ofdm_demod.c b/src/ofdm_demod.c index 0f3d8025..f8dc7a5f 100644 --- a/src/ofdm_demod.c +++ b/src/ofdm_demod.c @@ -473,8 +473,8 @@ int main(int argc, char *argv[]) { /* we have received enough frames to make a complete packet .... */ /* extract payload symbols from packet */ - ofdm_disassemble_qpsk_modem_packet(ofdm, rx_syms, rx_amps, payload_syms, - payload_amps, txt_bits); + ofdm_disassemble_psk_modem_packet(ofdm, rx_syms, rx_amps, payload_syms, + payload_amps, txt_bits); if (ldpc_en) { assert((ofdm_nuwbits + ofdm_ntxtbits + Npayloadbitsperpacket) <= @@ -492,8 +492,8 @@ int main(int argc, char *argv[]) { uint8_t out_char[Npayloadbitsperpacket]; if (testframes == true) { - Nerrs_raw = - count_uncoded_errors(&ldpc, ofdm_config, payload_syms_de, 0); + Nerrs_raw = count_uncoded_errors( + &ldpc, ofdm_config, payload_syms_de, payload_amps_de, 0); Terrs += Nerrs_raw; Tbits += Npayloadbitsperpacket; /* not counting errors in txt bits */ @@ -524,7 +524,7 @@ int main(int argc, char *argv[]) { assert(Npayloadsymsperpacket * ofdm_config->bps == Npayloadbitsperpacket); for (i = 0; i < Npayloadsymsperpacket; i++) { - int bits[2]; + int bits[ofdm->bps]; complex float s = payload_syms[i].real + I * payload_syms[i].imag; qpsk_demod(s, bits); rx_bits_char[ofdm_config->bps * i] = bits[1]; @@ -547,8 +547,7 @@ int main(int argc, char *argv[]) { memset(txt_bits, 0, ofdm_ntxtbits); uint8_t tx_bits[Nbitsperpacket]; ofdm_generate_payload_data_bits(payload_bits, Npayloadbitsperpacket); - ofdm_assemble_qpsk_modem_packet(ofdm, tx_bits, payload_bits, - txt_bits); + ofdm_assemble_psk_modem_packet(ofdm, tx_bits, payload_bits, txt_bits); /* count errors across UW, payload, txt bits */ int rx_bits[Nbitsperpacket]; diff --git a/src/ofdm_internal.h b/src/ofdm_internal.h index b57451ad..af449d33 100644 --- a/src/ofdm_internal.h +++ b/src/ofdm_internal.h @@ -260,17 +260,17 @@ complex float qam16_mod(int *); void qpsk_demod(complex float, int *); void qam16_demod(complex float, int *, float); void ofdm_txframe(struct OFDM *, complex float *, complex float[]); -void ofdm_assemble_qpsk_modem_packet(struct OFDM *, uint8_t[], uint8_t[], - uint8_t[]); -void ofdm_assemble_qpsk_modem_packet_symbols(struct OFDM *, complex float[], - COMP[], uint8_t[]); -void ofdm_disassemble_qpsk_modem_packet(struct OFDM *, complex float rx_syms[], - float rx_amps[], COMP[], float[], - short[]); -void ofdm_disassemble_qpsk_modem_packet_with_text_amps(struct OFDM *, - complex float rx_syms[], - float rx_amps[], COMP[], - float[], short[], int *); +void ofdm_assemble_psk_modem_packet(struct OFDM *, uint8_t[], uint8_t[], + uint8_t[]); +void ofdm_assemble_psk_modem_packet_symbols(struct OFDM *, complex float[], + COMP[], uint8_t[]); +void ofdm_disassemble_psk_modem_packet(struct OFDM *, complex float rx_syms[], + float rx_amps[], COMP[], float[], + short[]); +void ofdm_disassemble_psk_modem_packet_with_text_amps(struct OFDM *, + complex float rx_syms[], + float rx_amps[], COMP[], + float[], short[], int *); void ofdm_extract_uw(struct OFDM *ofdm, complex float rx_syms[], float rx_amps[], uint8_t rx_uw[]); void ofdm_rand(uint16_t[], int); diff --git a/src/ofdm_mod.c b/src/ofdm_mod.c index d4c495e8..23444f57 100644 --- a/src/ofdm_mod.c +++ b/src/ofdm_mod.c @@ -398,8 +398,7 @@ int main(int argc, char *argv[]) { /* assemble packet of bits then modulate */ uint8_t tx_bits_char[Nbitsperpacket]; - ofdm_assemble_qpsk_modem_packet(ofdm, tx_bits_char, data_bits, - txt_bits); + ofdm_assemble_psk_modem_packet(ofdm, tx_bits_char, data_bits, txt_bits); int tx_bits[Nbitsperpacket]; for (i = 0; i < Nbitsperpacket; i++) tx_bits[i] = tx_bits_char[i]; COMP tx_sams[Nsamperpacket]; diff --git a/unittest/tofdm.c b/unittest/tofdm.c index b4d60b75..0efcc60c 100644 --- a/unittest/tofdm.c +++ b/unittest/tofdm.c @@ -324,8 +324,8 @@ int main(int argc, char *argv[]) { for (i = 0; i < ofdm_ntxtbits; i++) txt_bits[i] = 0; uint8_t tx_bits_char[ofdm_bitsperframe]; - ofdm_assemble_qpsk_modem_packet(ofdm, tx_bits_char, payload_bits, - txt_bits); + ofdm_assemble_psk_modem_packet(ofdm, tx_bits_char, payload_bits, + txt_bits); for (i = 0; i < ofdm_bitsperframe; i++) tx_bits[i] = tx_bits_char[i]; } From c0821c86e7a0f6923e1f3c72631371c3296c94bc Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 5 Apr 2024 19:33:46 +1030 Subject: [PATCH 18/26] refactoring to support QAM16 LDPC decode --- src/freedv_2020.c | 2 +- src/freedv_700.c | 2 +- src/mpdecode_core.c | 35 ++++++++++++++--------------------- src/mpdecode_core.h | 6 +++--- src/ofdm_demod.c | 2 +- src/reliable_text.c | 3 ++- unittest/tofdm.c | 4 ++-- 7 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/freedv_2020.c b/src/freedv_2020.c index 99638190..83e1f54a 100644 --- a/src/freedv_2020.c +++ b/src/freedv_2020.c @@ -286,7 +286,7 @@ int freedv_comprx_2020(struct freedv *f, COMP demod_in[]) { } symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, EsNo, - ofdm->mean_amp, coded_syms_per_frame); + ofdm->mean_amp, ofdm->bps, coded_syms_per_frame); ldpc_decode_frame(ldpc, &parityCheckCount, &iter, out_char, llr); if (parityCheckCount != ldpc->NumberParityBits) rx_status |= FREEDV_RX_BIT_ERRORS; diff --git a/src/freedv_700.c b/src/freedv_700.c index 257b80fb..40aa2c08 100644 --- a/src/freedv_700.c +++ b/src/freedv_700.c @@ -504,7 +504,7 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, float llr[Npayloadbitsperpacket]; uint8_t decoded_codeword[Npayloadbitsperpacket]; symbols_to_llrs(llr, payload_syms_de, payload_amps_de, EsNo, - ofdm->mean_amp, Npayloadsymsperpacket); + ofdm->mean_amp, ofdm->bps, Npayloadsymsperpacket); ldpc_decode_frame(ldpc, &parityCheckCount, &iter, decoded_codeword, llr); // iter = run_ldpc_decoder(ldpc, decoded_codeword, llr, // &parityCheckCount); diff --git a/src/mpdecode_core.c b/src/mpdecode_core.c index 53742551..65b2be67 100644 --- a/src/mpdecode_core.c +++ b/src/mpdecode_core.c @@ -565,17 +565,14 @@ void sd_to_llr(float llr[], float sd[], int n) { */ void Demod2D(float symbol_likelihood[], /* output, M*number_symbols */ - COMP r[], /* received QPSK symbols, number_symbols */ - COMP S_matrix[], /* constellation of size M */ - float EsNo, - float fading[], /* real fading values, number_symbols */ + COMP r[], /* received PSK symbols, number_symbols */ + COMP S_matrix[], /* constellation of size M */ + int M, float EsNo, + float fading[], /* real fading values, number_symbols */ float mean_amp, int number_symbols) { - int M = QPSK_CONSTELLATION_SIZE; int i, j; float tempsr, tempsi, Er, Ei; - /* determine output */ - for (i = 0; i < number_symbols; i++) { /* go through each received symbol */ for (j = 0; j < M; j++) { /* each postulated symbol */ tempsr = fading[i] * S_matrix[j].real / mean_amp; @@ -583,10 +580,7 @@ void Demod2D(float symbol_likelihood[], /* output, M*number_symbols */ Er = r[i].real / mean_amp - tempsr; Ei = r[i].imag / mean_amp - tempsi; symbol_likelihood[i * M + j] = -EsNo * (Er * Er + Ei * Ei); - // printf("symbol_likelihood[%d][%d] = %f\n", - // i,j,symbol_likelihood[i*M+j]); } - // exit(0); } } @@ -633,18 +627,17 @@ void Somap(float bit_likelihood[], /* number_bits, bps*number_symbols */ } } -void symbols_to_llrs(float llr[], COMP rx_qpsk_symbols[], float rx_amps[], - float EsNo, float mean_amp, int nsyms) { +void symbols_to_llrs(float llr[], COMP rx_psk_symbols[], float rx_amps[], + float EsNo, float mean_amp, int bps, int nsyms) { int i; - - float symbol_likelihood[nsyms * QPSK_CONSTELLATION_SIZE]; - float bit_likelihood[nsyms * QPSK_BITS_PER_SYMBOL]; - - Demod2D(symbol_likelihood, rx_qpsk_symbols, S_matrix, EsNo, rx_amps, mean_amp, - nsyms); - Somap(bit_likelihood, symbol_likelihood, QPSK_CONSTELLATION_SIZE, - QPSK_BITS_PER_SYMBOL, nsyms); - for (i = 0; i < nsyms * QPSK_BITS_PER_SYMBOL; i++) { + int constellation_points = 1 << bps; + float symbol_likelihood[nsyms * constellation_points]; + float bit_likelihood[nsyms * bps]; + + Demod2D(symbol_likelihood, rx_psk_symbols, S_matrix, constellation_points, + EsNo, rx_amps, mean_amp, nsyms); + Somap(bit_likelihood, symbol_likelihood, constellation_points, bps, nsyms); + for (i = 0; i < nsyms * bps; i++) { llr[i] = -bit_likelihood[i]; } } diff --git a/src/mpdecode_core.h b/src/mpdecode_core.h index 95ead460..d36a1f00 100644 --- a/src/mpdecode_core.h +++ b/src/mpdecode_core.h @@ -47,12 +47,12 @@ int run_ldpc_decoder(struct LDPC *ldpc, uint8_t out_char[], float input[], int *parityCheckCount); void sd_to_llr(float llr[], float sd[], int n); -void Demod2D(float symbol_likelihood[], COMP r[], COMP S_matrix[], float EsNo, - float fading[], float mean_amp, int number_symbols); +void Demod2D(float symbol_likelihood[], COMP r[], COMP S_matrix[], int M, + float EsNo, float fading[], float mean_amp, int number_symbols); void Somap(float bit_likelihood[], float symbol_likelihood[], int M, int bps, int number_symbols); void symbols_to_llrs(float llr[], COMP rx_qpsk_symbols[], float rx_amps[], - float EsNo, float mean_amp, int nsyms); + float EsNo, float mean_amp, int bps, int nsyms); void fsk_rx_filt_to_llrs(float llr[], float rx_filt[], float v_est, float SNRest, int M, int nsyms); diff --git a/src/ofdm_demod.c b/src/ofdm_demod.c index f8dc7a5f..e855e5ec 100644 --- a/src/ofdm_demod.c +++ b/src/ofdm_demod.c @@ -500,7 +500,7 @@ int main(int argc, char *argv[]) { } symbols_to_llrs(llr, payload_syms_de, payload_amps_de, EsNo, - ofdm->mean_amp, Npayloadsymsperpacket); + ofdm->mean_amp, ofdm->bps, Npayloadsymsperpacket); assert(Ndatabitsperpacket == ldpc.data_bits_per_frame); ldpc_decode_frame(&ldpc, &parityCheckCount, &iter, out_char, llr); diff --git a/src/reliable_text.c b/src/reliable_text.c index 8a5ba219..ff7c5c0e 100644 --- a/src/reliable_text.c +++ b/src/reliable_text.c @@ -182,7 +182,8 @@ static int reliable_text_ldpc_decode(reliable_text_impl_t* obj, char* dest) { float EsNo = 3.0; // note: constant from freedv_700.c symbols_to_llrs(llr, (COMP*)deinterleavedSyms, deinterleavedAmps, EsNo, - obj->fdv->ofdm->mean_amp, Npayloadsymsperpacket); + obj->fdv->ofdm->mean_amp, obj->fdv->ofdm->bps, + Npayloadsymsperpacket); } else { // Deinterlace the received bits. gp_deinterleave_bits(deinterleavedBits, src, LDPC_TOTAL_SIZE_BITS / 2); diff --git a/unittest/tofdm.c b/unittest/tofdm.c index 0efcc60c..a6b139f8 100644 --- a/unittest/tofdm.c +++ b/unittest/tofdm.c @@ -475,8 +475,8 @@ int main(int argc, char *argv[]) { float *ldpc_codeword_symbol_amps = &ofdm->rx_amp[(ofdm_nuwbits + ofdm_ntxtbits) / ofdm_bps]; - Demod2D(symbol_likelihood, ldpc_codeword_symbols, S_matrix, EsNo, - ldpc_codeword_symbol_amps, ofdm->mean_amp, + Demod2D(symbol_likelihood, ldpc_codeword_symbols, S_matrix, 1 << ofdm_bps, + EsNo, ldpc_codeword_symbol_amps, ofdm->mean_amp, CODED_BITSPERFRAME / ofdm_bps); Somap(bit_likelihood, symbol_likelihood, 1 << ofdm_bps, ofdm_bps, CODED_BITSPERFRAME / ofdm_bps); From 1b2a4a797b007af00bd501a0999bce85bc7624e1 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 5 Apr 2024 19:55:30 +1030 Subject: [PATCH 19/26] first pass LDPC decoder working with QAM16 --- src/mpdecode_core.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/mpdecode_core.c b/src/mpdecode_core.c index 65b2be67..9cf44f72 100644 --- a/src/mpdecode_core.c +++ b/src/mpdecode_core.c @@ -27,11 +27,21 @@ #define QPSK_CONSTELLATION_SIZE 4 #define QPSK_BITS_PER_SYMBOL 2 -/* QPSK constellation for symbol likelihood calculations */ +/* Constellations for symbol likelihood calculations */ -static COMP S_matrix[] = { +static COMP S_matrix_qpsk[] = { {1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, -1.0f}, {-1.0f, 0.0f}}; +static COMP S_matrix_qam16[] = { + {4.4721e-01, 2.7756e-17}, {8.9443e-01, 4.4721e-01}, + {8.9443e-01, -4.4721e-01}, {1.3416e+00, 1.1102e-16}, + {2.7756e-17, -4.4721e-01}, {-4.4721e-01, -8.9443e-01}, + {4.4721e-01, -8.9443e-01}, {1.1102e-16, -1.3416e+00}, + {-2.7756e-17, 4.4721e-01}, {4.4721e-01, 8.9443e-01}, + {-4.4721e-01, 8.9443e-01}, {-1.1102e-16, 1.3416e+00}, + {-4.4721e-01, -2.7756e-17}, {-8.9443e-01, -4.4721e-01}, + {-8.9443e-01, 4.4721e-01}, {-1.3416e+00, -1.1102e-16}}; + // c_nodes will be an array of NumberParityBits of struct c_node // Each c_node contains an array of c_sub_node elements // This structure reduces the indexing calclations in SumProduct() @@ -634,6 +644,11 @@ void symbols_to_llrs(float llr[], COMP rx_psk_symbols[], float rx_amps[], float symbol_likelihood[nsyms * constellation_points]; float bit_likelihood[nsyms * bps]; + COMP *S_matrix; + assert((bps == 2) || (bps == 4)); + if (bps == 2) S_matrix = S_matrix_qpsk; + if (bps == 4) S_matrix = S_matrix_qam16; + Demod2D(symbol_likelihood, rx_psk_symbols, S_matrix, constellation_points, EsNo, rx_amps, mean_amp, nsyms); Somap(bit_likelihood, symbol_likelihood, constellation_points, bps, nsyms); From 2e5f845b1108b9469f4aed3e8b1b46e2a509f196 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 5 Apr 2024 20:16:24 +1030 Subject: [PATCH 20/26] adjusted UW thresh and EsNo for LDPC decoder - PER < 0.1 with 100 packets mpp at 15dB-ish SNR --- octave/ofdm_mode.m | 2 +- src/ofdm_demod.c | 4 +--- src/ofdm_mode.c | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/octave/ofdm_mode.m b/octave/ofdm_mode.m index a49c94aa..12470e83 100644 --- a/octave/ofdm_mode.m +++ b/octave/ofdm_mode.m @@ -61,7 +61,7 @@ config.amp_est_mode = 1; config.EsNodB = 10; elseif strcmp(mode,"qam16c2") Ns=5; config.Np=31; Tcp = 0.004; Ts = 0.016; Nc = 33; config.data_mode = "streaming"; - config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 42*4; config.bad_uw_errors = 15; + config.bps=4; config.Ntxtbits = 0; config.Nuwbits = 42*4; config.bad_uw_errors = 50; config.ftwindow_width = 80; config.amp_scale = 135E3; config.state_machine = "data"; config.amp_est_mode = 1; config.EsNodB = 10; config.tx_uw = zeros(1,config.Nuwbits = 42*4); diff --git a/src/ofdm_demod.c b/src/ofdm_demod.c index e855e5ec..84439d99 100644 --- a/src/ofdm_demod.c +++ b/src/ofdm_demod.c @@ -408,9 +408,7 @@ int main(int argc, char *argv[]) { else Ndiscard = 1; /* much longer packets, so discard thresh smaller */ - float EsNo = 3.0f; - - if (verbose == 2) fprintf(stderr, "Warning EsNo: %f hard coded\n", EsNo); + float EsNo = pow(10.0, ofdm->EsNodB / 10.0); /* More logging */ COMP payload_syms_log[NFRAMES][Npayloadsymsperpacket]; diff --git a/src/ofdm_mode.c b/src/ofdm_mode.c index 0c616de5..8034380e 100644 --- a/src/ofdm_mode.c +++ b/src/ofdm_mode.c @@ -103,7 +103,7 @@ void ofdm_init_mode(char mode[], struct OFDM_CONFIG *config) { config->txtbits = 0; config->nuwbits = 42 * 4; assert(config->nuwbits <= MAX_UW_BITS); - config->bad_uw_errors = 15; + config->bad_uw_errors = 50; config->ftwindowwidth = 80; config->state_machine = "data"; config->amp_est_mode = 1; From 82546c4401ecd049dbda08aabe879bd2db12d190 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 5 Apr 2024 20:26:40 +1030 Subject: [PATCH 21/26] qam16c2 FreeDV API support --- src/freedv_700.c | 3 +-- src/freedv_api.c | 10 +++++++++- src/freedv_api.h | 4 ++++ src/freedv_data_raw_rx.c | 2 ++ src/freedv_data_raw_tx.c | 2 ++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/freedv_700.c b/src/freedv_700.c index 40aa2c08..72e05632 100644 --- a/src/freedv_700.c +++ b/src/freedv_700.c @@ -198,6 +198,7 @@ void freedv_ofdm_data_open(struct freedv *f, struct freedv_advanced *adv) { if (f->mode == FREEDV_MODE_DATAC4) strcpy(mode, "datac4"); if (f->mode == FREEDV_MODE_DATAC13) strcpy(mode, "datac13"); if (f->mode == FREEDV_MODE_DATAC14) strcpy(mode, "datac14"); + if (f->mode == FREEDV_MODE_QAM16C2) strcpy(mode, "qam16c2"); if (f->mode == FREEDV_MODE_DATA_CUSTOM) { assert(adv != NULL); assert(adv->config != NULL); @@ -506,8 +507,6 @@ int freedv_comp_short_rx_ofdm(struct freedv *f, void *demod_in_8kHz, symbols_to_llrs(llr, payload_syms_de, payload_amps_de, EsNo, ofdm->mean_amp, ofdm->bps, Npayloadsymsperpacket); ldpc_decode_frame(ldpc, &parityCheckCount, &iter, decoded_codeword, llr); - // iter = run_ldpc_decoder(ldpc, decoded_codeword, llr, - // &parityCheckCount); memcpy(f->rx_payload_bits, decoded_codeword, Ndatabitsperpacket); if (strlen(ofdm->data_mode)) { diff --git a/src/freedv_api.c b/src/freedv_api.c index d04dd7c8..cb9e7064 100644 --- a/src/freedv_api.c +++ b/src/freedv_api.c @@ -131,7 +131,8 @@ struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, mode) || - FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, mode)) == false) + FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, mode)) == false) return NULL; /* set everything to zero just in case */ @@ -165,6 +166,8 @@ struct freedv *freedv_open_advanced(int mode, struct freedv_advanced *adv) { freedv_ofdm_data_open(f, NULL); if (FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, mode)) freedv_ofdm_data_open(f, adv); + if (FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, mode)) + freedv_ofdm_data_open(f, NULL); varicode_decode_init(&f->varicode_dec_states, 1); @@ -248,6 +251,7 @@ void freedv_close(struct freedv *freedv) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, freedv->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, freedv->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, freedv->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, freedv->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, freedv->mode)) { FREE(freedv->rx_syms); FREE(freedv->rx_amps); @@ -281,6 +285,7 @@ static int is_ofdm_mode(struct freedv *f) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode); } @@ -291,6 +296,7 @@ static int is_ofdm_data_mode(struct freedv *f) { FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode); } @@ -483,6 +489,7 @@ void freedv_rawdatacomptx(struct freedv *f, COMP mod_out[], FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode)) freedv_comptx_ofdm(f, mod_out); @@ -1084,6 +1091,7 @@ int freedv_rawdatacomprx(struct freedv *f, unsigned char *packed_payload_bits, FDV_MODE_ACTIVE(FREEDV_MODE_DATAC4, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC13, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATAC14, f->mode) || + FDV_MODE_ACTIVE(FREEDV_MODE_QAM16C2, f->mode) || FDV_MODE_ACTIVE(FREEDV_MODE_DATA_CUSTOM, f->mode)) rx_status = freedv_comp_short_rx_ofdm(f, (void *)demod_in, 0, 1.0f); if (FDV_MODE_ACTIVE(FREEDV_MODE_FSK_LDPC, f->mode)) { diff --git a/src/freedv_api.h b/src/freedv_api.h index 9f72c8f9..4acf40ca 100644 --- a/src/freedv_api.h +++ b/src/freedv_api.h @@ -63,6 +63,7 @@ extern "C" { #define FREEDV_MODE_DATAC13 19 #define FREEDV_MODE_DATAC14 20 #define FREEDV_MODE_DATA_CUSTOM 21 +#define FREEDV_MODE_QAM16C2 22 // Sample rates used #define FREEDV_FS_8000 8000 @@ -148,6 +149,9 @@ extern "C" { #if !defined(FREEDV_MODE_DATA_CUSTOM_EN) #define FREEDV_MODE_DATA_CUSTOM_EN FREEDV_MODE_EN_DEFAULT #endif +#if !defined(FREEDV_MODE_QAM16C2_EN) +#define FREEDV_MODE_QAM16C2_EN FREEDV_MODE_EN_DEFAULT +#endif #define FDV_MODE_ACTIVE(mode_name, var) \ ((mode_name##_EN) == 0 ? 0 : (var) == mode_name) diff --git a/src/freedv_data_raw_rx.c b/src/freedv_data_raw_rx.c index 435648d7..50fcb32f 100644 --- a/src/freedv_data_raw_rx.c +++ b/src/freedv_data_raw_rx.c @@ -217,6 +217,8 @@ int main(int argc, char *argv[]) { mode = FREEDV_MODE_DATAC13; if (!strcmp(argv[dx], "DATAC14") || !strcmp(argv[dx], "datac14")) mode = FREEDV_MODE_DATAC14; + if (!strcmp(argv[dx], "QAM16C2") || !strcmp(argv[dx], "qam16c2")) + mode = FREEDV_MODE_QAM16C2; if (!strcmp(argv[dx], "CUSTOM") || !strcmp(argv[dx], "custom")) mode = FREEDV_MODE_DATA_CUSTOM; if (mode == -1) { diff --git a/src/freedv_data_raw_tx.c b/src/freedv_data_raw_tx.c index ce5da562..70fefe74 100644 --- a/src/freedv_data_raw_tx.c +++ b/src/freedv_data_raw_tx.c @@ -238,6 +238,8 @@ int main(int argc, char *argv[]) { mode = FREEDV_MODE_DATAC13; if (!strcmp(argv[dx], "DATAC14") || !strcmp(argv[dx], "datac14")) mode = FREEDV_MODE_DATAC14; + if (!strcmp(argv[dx], "QAM16C2") || !strcmp(argv[dx], "qam16c2")) + mode = FREEDV_MODE_QAM16C2; if (!strcmp(argv[dx], "CUSTOM") || !strcmp(argv[dx], "custom")) mode = FREEDV_MODE_DATA_CUSTOM; if (mode == -1) { From 43388ddf162465e3713d32e6829211b869084813 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Fri, 5 Apr 2024 20:52:32 +1030 Subject: [PATCH 22/26] qam16 ctests and README_data.md --- CMakeLists.txt | 22 ++++++++++++++++++++++ README_data.md | 1 + 2 files changed, 23 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd45f4f7..dbd2eca7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -669,6 +669,13 @@ endif() cd ${CMAKE_CURRENT_BINARY_DIR}/src; cat test.raw | ./ofdm_demod --mode datac14 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + # QAM16C2 Octave Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_qam16c2_octave + COMMAND sh -c "cd ${CMAKE_CURRENT_SOURCE_DIR}/octave; + DISPLAY=\"\" octave-cli -qf --eval 'ofdm_ldpc_tx(\"${CMAKE_CURRENT_BINARY_DIR}/src/test.raw\",\"qam16c2\",1,10,\"awgn\",\"bursts\",2)'; + cd ${CMAKE_CURRENT_BINARY_DIR}/src; + cat test.raw | ./ofdm_demod --mode qam16c2 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + # DATAC4 C Tx, C Rx, burst mode add_test(NAME test_OFDM_modem_datac4_ldpc_burst COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; @@ -690,6 +697,13 @@ endif() ./ch - - --No -17 | ./ofdm_demod --mode datac14 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + # QAM16C2 C Tx, C Rx, burst mode + add_test(NAME test_OFDM_modem_qam16c2_ldpc_burst + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + ./ofdm_mod --mode qam16c2 --in /dev/zero --testframes 1 --verbose 1 --ldpc --bursts 10 | + ./ch - - --No -30 | + ./ofdm_demod --mode qam16c2 --out /dev/null --testframes --ldpc --verbose 2 --packetsperburst 1") + # ------------------------------------------------------------------------- # LDPC # ------------------------------------------------------------------------- @@ -1340,6 +1354,13 @@ endif(NOT APPLE) ./freedv_data_raw_rx DATAC14 - binaryOut.bin -v; diff binaryIn.bin binaryOut.bin") + add_test(NAME test_freedv_data_raw_ofdm_qam16c2_burst_file + COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src; + head -c $((1213*10)) binaryIn.bin; + ./freedv_data_raw_tx qam16c2 binaryIn.bin - --bursts 10 | + ./freedv_data_raw_rx qam16c2 - binaryOut.bin -v; + diff binaryIn.bin binaryOut.bin") + # FSK LDPC default 100 bit/s 2FSK, enough noise for several % raw BER to give # FEC/acquisition a work out, bursts of 1 frame as that stresses acquisition add_test(NAME test_freedv_data_raw_fsk_ldpc_100 @@ -1426,6 +1447,7 @@ endif(NOT APPLE) test_OFDM_modem_datac4_octave test_OFDM_modem_datac13_octave test_OFDM_modem_datac14_octave + test_OFDM_modem_qam16c2_octave test_fsk_lib_4fsk_ldpc test_OFDM_modem_datac0_compression PROPERTIES diff --git a/README_data.md b/README_data.md index 3ceccb46..1656469d 100644 --- a/README_data.md +++ b/README_data.md @@ -147,6 +147,7 @@ These modes use an OFDM modem with powerful LDPC codes and are designed for send | DATAC4 | 250 | 87 | 56 | (1472,448) | 5.17 | 90/100 at -4dB | Forward link data (low SNR) | | DATAC13 | 200 | 64 | 14 | (384,128) | 2.0 | 90/100 at -4dB | Reverse link ACK packets (low SNR) | | DATAC14 | 250 | 58 | 3 | (112,56) | 0.69 | 90/100 at -4dB | Reverse link ACK packets (low SNR) | +| QAM16C2 | 2100 | 3100 | 1213 | (16200,9720) | 3.2 | 90/100 at 15dB | Forward link data (high SNR) | Notes: 1. 16 bits (2 bytes) per frame are reserved for a 16 bit CRC, e.g. for `datac3` we have 128 byte frames, and 128-2=126 bytes/frame of payload data. From 1fd68ac6f2868d79fcdb816e57b9f0f926a49588 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Sat, 6 Apr 2024 05:31:52 +1030 Subject: [PATCH 23/26] attempt to fix stm32 build issues --- stm32/unittest/src/tst_ofdm_demod.c | 680 ++++++++++++++-------------- stm32/unittest/src/tst_ofdm_mod.c | 316 ++++++------- 2 files changed, 506 insertions(+), 490 deletions(-) diff --git a/stm32/unittest/src/tst_ofdm_demod.c b/stm32/unittest/src/tst_ofdm_demod.c index 54cc027b..3be96c4f 100644 --- a/stm32/unittest/src/tst_ofdm_demod.c +++ b/stm32/unittest/src/tst_ofdm_demod.c @@ -25,7 +25,6 @@ along with this program; if not, see . */ - /* This is a unit test implementation of the OFDM Demod function. * It is used for several tests: * @@ -45,40 +44,39 @@ * ofdm_get_test_bits - 10 | * ofdm_mod - - | \ * cohpsk_ch - stm_in.raw -20 -Fs 8000 -f -5 * - * Reference data can be created by running the same input through the x86 + * Reference data can be created by running the same input through the x86 * ofdm_demod tool. * - * ofdm_demod stm_in.raw ref_demod_out.raw -o ofdm_demod_ref_log.txt --testframes + * ofdm_demod stm_in.raw ref_demod_out.raw -o ofdm_demod_ref_log.txt + * --testframes * - * Comparison of the results to the reference will depend on the test conditions. - * Some small differences are expected due to differences in implementation. + * Comparison of the results to the reference will depend on the test + * conditions. Some small differences are expected due to differences in + * implementation. * */ - #include +#include +#include +#include #include #include #include -#include -#include -#include #include -#include "semihosting.h" #include "codec2_ofdm.h" -#include "ofdm_internal.h" -#include "mpdecode_core.h" -#include "ldpc_codes.h" -#include "interldpc.h" -#include "gp_interleaver.h" -#include "test_bits_ofdm.h" - #include "debug_alloc.h" - -#include "stm32f4xx_conf.h" -#include "stm32f4xx.h" +#include "gp_interleaver.h" +#include "interldpc.h" +#include "ldpc_codes.h" #include "machdep.h" +#include "mpdecode_core.h" +#include "ofdm_internal.h" +#include "semihosting.h" +#include "stm32f4xx.h" +#include "stm32f4xx_conf.h" +#include "test_bits_ofdm.h" #define NDISCARD 20 @@ -92,347 +90,361 @@ static int ofdm_rowsperframe; static int ofdm_nuwbits; static int ofdm_ntxtbits; static int ofdm_nin; -static char fout_buffer[4*4096]; -static __attribute__ ((section (".ccm"))) char fdiag_buffer[4*8192]; -static __attribute__ ((section (".ccm"))) char fin_buffer[4096*8]; +static char fout_buffer[4 * 4096]; +static __attribute__((section(".ccm"))) char fdiag_buffer[4 * 8192]; +static __attribute__((section(".ccm"))) char fin_buffer[4096 * 8]; -static char *statemode[] = { - "search", - "trial", - "synced" -}; +static char *statemode[] = {"search", "trial", "synced"}; static FILE *fout, *fdiag; void flush_all(void) { - fflush(fout); - fflush(fdiag); - fflush(stdout); - fflush(stderr); - } + fflush(fout); + fflush(fdiag); + fflush(stdout); + fflush(stderr); +} int main(int argc, char *argv[]) { - struct OFDM *ofdm; - FILE *fcfg; - int nin_frame; - struct LDPC ldpc; - - // Test configuration, read from stm_cfg.txt - int config_verbose; - int config_testframes; - int config_ldpc_en; - int config_log_payload_syms; - int config_profile; - - int i; - int Nerrs, Terrs, Tbits, Terrs2, Tbits2, frame_count; - int Tbits_coded, Terrs_coded; - - semihosting_init(); - - fprintf(stdout, "OFDM Demod test\n"); - - // Read configuration - a file of '0' or '1' characters - char config[8]; - fcfg = fopen("stm_cfg.txt", "r"); - if (fcfg == NULL) { - fprintf(stderr, "Error opening config file\n"); - exit(1); - } - if (fread(&config[0], 1, 8, fcfg) != 8) { - fprintf(stderr, "Error reading config file\n"); - exit(1); + struct OFDM *ofdm; + FILE *fcfg; + int nin_frame; + struct LDPC ldpc; + + // Test configuration, read from stm_cfg.txt + int config_verbose; + int config_testframes; + int config_ldpc_en; + int config_log_payload_syms; + int config_profile; + + int i; + int Nerrs, Terrs, Tbits, Terrs2, Tbits2, frame_count; + int Tbits_coded, Terrs_coded; + + semihosting_init(); + + fprintf(stdout, "OFDM Demod test\n"); + + // Read configuration - a file of '0' or '1' characters + char config[8]; + fcfg = fopen("stm_cfg.txt", "r"); + if (fcfg == NULL) { + fprintf(stderr, "Error opening config file\n"); + exit(1); + } + if (fread(&config[0], 1, 8, fcfg) != 8) { + fprintf(stderr, "Error reading config file\n"); + exit(1); + } + config_verbose = config[0] - '0'; + config_testframes = config[1] - '0'; + config_ldpc_en = config[2] - '0'; + config_log_payload_syms = config[3] - '0'; + config_profile = config[4] - '0'; + fclose(fcfg); + + int Nerrs_raw = 0; + int Nerrs_coded = 0; + int iter = 0; + int parityCheckCount = 0; + + PROFILE_VAR(ofdm_demod_start, ofdm_demod_sync_search, ofdm_demod_demod, + ofdm_demod_diss, ofdm_demod_snr); + ofdm_demod_start = 0; + ofdm_demod_sync_search = 0; + ofdm_demod_demod = 0; + ofdm_demod_diss = 0; + ofdm_demod_snr = 0; + if (config_profile) machdep_profile_init(); + + ofdm = ofdm_create(NULL); + assert(ofdm != NULL); + + /* Get a copy of the actual modem config */ + ofdm_config = ofdm_get_config_param(ofdm); + + ldpc_codes_setup(&ldpc, "HRA_112_112"); + + ofdm_bitsperframe = ofdm_get_bits_per_frame(ofdm); + ofdm_rowsperframe = ofdm_bitsperframe / (ofdm_config->nc * ofdm_config->bps); + ofdm_nuwbits = + (ofdm_config->ns - 1) * ofdm_config->bps - ofdm_config->txtbits; + ofdm_ntxtbits = ofdm_config->txtbits; + ofdm_nin = ofdm_get_nin(ofdm); + + ofdm_set_verbose(ofdm, config_verbose); + + int Nmaxsamperframe = ofdm_get_max_samples_per_frame(ofdm); + + int data_bits_per_frame = ldpc.data_bits_per_frame; + int coded_bits_per_frame = ldpc.coded_bits_per_frame; + int coded_syms_per_frame = ldpc.coded_bits_per_frame / ofdm->bps; + + short rx_scaled[Nmaxsamperframe]; + int rx_bits[ofdm_bitsperframe]; + char rx_bits_char[ofdm_bitsperframe]; + uint8_t rx_uw[ofdm_nuwbits]; + short txt_bits[ofdm_ntxtbits]; + int f = 0; + Nerrs = Terrs = Tbits = Terrs2 = Tbits2 = Terrs_coded = Tbits_coded = + frame_count = 0; + + float snr_est_smoothed_dB = 0.0; + + float EsNo = 3.0f; // Constant from ofdm_demod.c + + COMP payload_syms[coded_syms_per_frame]; + float payload_amps[coded_syms_per_frame]; + COMP codeword_symbols[coded_syms_per_frame]; + float codeword_amps[coded_syms_per_frame]; + + FILE *fin = fopen("stm_in.raw", "rb"); + if (fin == NULL) { + fprintf(stderr, "Error opening input file\n"); + exit(1); + } + setvbuf(fin, fin_buffer, _IOFBF, sizeof(fin_buffer)); + + fout = fopen("stm_out.raw", "wb"); + if (fout == NULL) { + fprintf(stderr, "Error opening output file\n"); + exit(1); + } + setvbuf(fout, fout_buffer, _IOFBF, sizeof(fout_buffer)); + + fdiag = fopen("stm_diag.raw", "wb"); + if (fdiag == NULL) { + fprintf(stderr, "Error opening diag file\n"); + exit(1); + } + setvbuf(fdiag, fdiag_buffer, _IOFBF, sizeof(fdiag_buffer)); + + nin_frame = ofdm_get_nin(ofdm); + int num_read; + + while ((num_read = fread(rx_scaled, sizeof(short), nin_frame, fin)) == + nin_frame) { + int log_payload_syms_flag = 0; + + if (config_profile) PROFILE_SAMPLE(ofdm_demod_start); + + /* demod */ + + if (config_profile) + PROFILE_SAMPLE_AND_LOG2(ofdm_demod_start, " ofdm_demod_start"); + + if (ofdm->sync_state == search) { + if (config_profile) PROFILE_SAMPLE(ofdm_demod_sync_search); + ofdm_sync_search_shorts(ofdm, rx_scaled, (OFDM_PEAK / 2)); + if (config_profile) + PROFILE_SAMPLE_AND_LOG2(ofdm_demod_sync_search, + " ofdm_demod_sync_search"); } - config_verbose = config[0] - '0'; - config_testframes = config[1] - '0'; - config_ldpc_en = config[2] - '0'; - config_log_payload_syms = config[3] - '0'; - config_profile = config[4] - '0'; - fclose(fcfg); - - int Nerrs_raw = 0; - int Nerrs_coded = 0; - int iter = 0; - int parityCheckCount = 0; - - PROFILE_VAR(ofdm_demod_start, ofdm_demod_sync_search, - ofdm_demod_demod, ofdm_demod_diss, ofdm_demod_snr); - ofdm_demod_start = 0; - ofdm_demod_sync_search = 0; - ofdm_demod_demod = 0; - ofdm_demod_diss = 0; - ofdm_demod_snr = 0; - if (config_profile) machdep_profile_init(); - - ofdm = ofdm_create(NULL); - assert(ofdm != NULL); - - /* Get a copy of the actual modem config */ - ofdm_config = ofdm_get_config_param(ofdm); - - ldpc_codes_setup(&ldpc, "HRA_112_112"); - - ofdm_bitsperframe = ofdm_get_bits_per_frame(ofdm); - ofdm_rowsperframe = ofdm_bitsperframe / (ofdm_config->nc * ofdm_config->bps); - ofdm_nuwbits = (ofdm_config->ns - 1) * ofdm_config->bps - ofdm_config->txtbits; - ofdm_ntxtbits = ofdm_config->txtbits; - ofdm_nin = ofdm_get_nin(ofdm); - - ofdm_set_verbose(ofdm, config_verbose); - - int Nmaxsamperframe = ofdm_get_max_samples_per_frame(ofdm); - - int data_bits_per_frame = ldpc.data_bits_per_frame; - int coded_bits_per_frame = ldpc.coded_bits_per_frame; - int coded_syms_per_frame = ldpc.coded_bits_per_frame/ofdm->bps; - - short rx_scaled[Nmaxsamperframe]; - int rx_bits[ofdm_bitsperframe]; - char rx_bits_char[ofdm_bitsperframe]; - uint8_t rx_uw[ofdm_nuwbits]; - short txt_bits[ofdm_ntxtbits]; - int f = 0; - Nerrs = Terrs = Tbits = Terrs2 = Tbits2 = Terrs_coded = Tbits_coded = frame_count = 0; - - float snr_est_smoothed_dB = 0.0; - - float EsNo = 3.0f; // Constant from ofdm_demod.c - - COMP payload_syms[coded_syms_per_frame]; - float payload_amps[coded_syms_per_frame]; - COMP codeword_symbols[coded_syms_per_frame]; - float codeword_amps[coded_syms_per_frame]; - - FILE* fin = fopen("stm_in.raw", "rb"); - if (fin == NULL) { - fprintf(stderr, "Error opening input file\n"); - exit(1); - } - setvbuf(fin, fin_buffer,_IOFBF,sizeof(fin_buffer)); + if ((ofdm->sync_state == synced) || (ofdm->sync_state == trial)) { + if (config_profile) PROFILE_SAMPLE(ofdm_demod_demod); + ofdm_demod_shorts(ofdm, rx_bits, rx_scaled, (OFDM_PEAK / 2)); + if (config_profile) + PROFILE_SAMPLE_AND_LOG2(ofdm_demod_demod, " ofdm_demod_demod"); + if (config_profile) PROFILE_SAMPLE(ofdm_demod_diss); + ofdm_extract_uw(ofdm, ofdm->rx_np, ofdm->rx_amp, rx_uw); + ofdm_disassemble_psk_modem_packet(ofdm, ofdm->rx_np, ofdm->rx_amp, + payload_syms, payload_amps, txt_bits); + if (config_profile) + PROFILE_SAMPLE_AND_LOG2(ofdm_demod_diss, " ofdm_demod_diss"); + log_payload_syms_flag = 1; + + /* SNR estimation and smoothing */ + if (config_profile) PROFILE_SAMPLE(ofdm_demod_snr); + float EsNodB = ofdm_esno_est_calc((complex float *)payload_syms, + coded_syms_per_frame); + float snr_est_dB = ofdm_snr_from_esno(ofdm, EsNodB); + snr_est_smoothed_dB = 0.9f * snr_est_smoothed_dB + 0.1f * snr_est_dB; + if (config_profile) { + PROFILE_SAMPLE_AND_LOG2(ofdm_demod_snr, " ofdm_demod_snr"); + } + + // LDPC + if (config_ldpc_en) { // was llr_en in orig + + /* first few symbols are used for UW and txt bits, find + start of (224,112) LDPC codeword and extract QPSK + symbols and amplitude estimates */ + assert((ofdm_nuwbits + ofdm_ntxtbits + coded_bits_per_frame) == + ofdm_bitsperframe); + + /* newest symbols at end of buffer (uses final i from last loop) */ + for (i = 0; i < coded_syms_per_frame; i++) { + codeword_symbols[i] = payload_syms[i]; + codeword_amps[i] = payload_amps[i]; + } - fout = fopen("stm_out.raw", "wb"); - if (fout == NULL) { - fprintf(stderr, "Error opening output file\n"); - exit(1); - } - setvbuf(fout, fout_buffer,_IOFBF,sizeof(fout_buffer)); + /* run de-interleaver */ + COMP codeword_symbols_de[coded_syms_per_frame]; + float codeword_amps_de[coded_syms_per_frame]; - fdiag = fopen("stm_diag.raw", "wb"); - if (fdiag == NULL) { - fprintf(stderr, "Error opening diag file\n"); - exit(1); - } - setvbuf(fdiag, fdiag_buffer,_IOFBF,sizeof(fdiag_buffer)); + gp_deinterleave_comp(codeword_symbols_de, codeword_symbols, + coded_syms_per_frame); + gp_deinterleave_float(codeword_amps_de, codeword_amps, + coded_syms_per_frame); - nin_frame = ofdm_get_nin(ofdm); - int num_read; - - while((num_read = fread(rx_scaled, sizeof(short) , nin_frame, fin)) == nin_frame) { + float llr[coded_bits_per_frame]; - int log_payload_syms_flag = 0; + if (config_ldpc_en) { + uint8_t out_char[coded_bits_per_frame]; + + if (config_testframes) { + Terrs += count_uncoded_errors( + &ldpc, ofdm_config, codeword_symbols_de, codeword_amps_de, 0); + Tbits += coded_bits_per_frame; + } + + symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, EsNo, + ofdm->mean_amp, coded_syms_per_frame); + iter = run_ldpc_decoder(&ldpc, out_char, llr, &parityCheckCount); + + // fprintf(stderr,"iter: %d pcc: %d\n", iter, parityCheckCount); + + if (config_testframes) { + /* construct payload data bits */ + uint8_t payload_data_bits[data_bits_per_frame]; + ofdm_generate_payload_data_bits(payload_data_bits, + data_bits_per_frame); + + Nerrs_coded = + count_errors(payload_data_bits, out_char, data_bits_per_frame); + Terrs_coded += Nerrs_coded; + Tbits_coded += data_bits_per_frame; + } + + fwrite(out_char, sizeof(char), data_bits_per_frame, fout); + } else { + /* lpdc_en == 0, external LDPC decoder, so output LLRs */ + symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, EsNo, + ofdm->mean_amp, coded_syms_per_frame); + fwrite(llr, sizeof(double), coded_bits_per_frame, fout); + } + } else { // !llrs_en (or ldpc_en) + + /* simple hard decision output for uncoded testing, excluding UW and txt + */ + assert(coded_syms_per_frame * ofdm_config->bps == coded_bits_per_frame); + for (i = 0; i < coded_syms_per_frame; i++) { + int bits[2]; + complex float s = payload_syms[i].real + I * payload_syms[i].imag; + qpsk_demod(s, bits); + rx_bits_char[ofdm_config->bps * i] = bits[1]; + rx_bits_char[ofdm_config->bps * i + 1] = bits[0]; + } - if (config_profile) PROFILE_SAMPLE(ofdm_demod_start); + fwrite(rx_bits_char, sizeof(uint8_t), coded_bits_per_frame, fout); + } - /* demod */ + /* optional error counting on uncoded data in non-LDPC testframe mode */ - if (config_profile) PROFILE_SAMPLE_AND_LOG2(ofdm_demod_start, " ofdm_demod_start"); + if (config_testframes && (config_ldpc_en == 0)) { + /* build up a test frame consisting of unique word, txt bits, and + psuedo-random uncoded payload bits. The psuedo-random generator is + the same as Octave so it can interoperate with ofdm_tx.m/ofdm_rx.m */ - if (ofdm->sync_state == search) { - if (config_profile) PROFILE_SAMPLE(ofdm_demod_sync_search); - ofdm_sync_search_shorts(ofdm, rx_scaled, (OFDM_PEAK/2)); - if (config_profile) PROFILE_SAMPLE_AND_LOG2(ofdm_demod_sync_search, " ofdm_demod_sync_search"); - } + int Npayloadbits = ofdm_bitsperframe - (ofdm_nuwbits + ofdm_ntxtbits); + uint16_t r[Npayloadbits]; + uint8_t payload_bits[Npayloadbits]; + uint8_t tx_bits[Npayloadbits]; + + ofdm_rand(r, Npayloadbits); - if ((ofdm->sync_state == synced) || (ofdm->sync_state == trial) ) { - if (config_profile) PROFILE_SAMPLE(ofdm_demod_demod); - ofdm_demod_shorts(ofdm, rx_bits, rx_scaled, (OFDM_PEAK/2)); - if (config_profile) PROFILE_SAMPLE_AND_LOG2(ofdm_demod_demod, " ofdm_demod_demod"); - if (config_profile) PROFILE_SAMPLE(ofdm_demod_diss); - ofdm_extract_uw(ofdm, ofdm->rx_np, ofdm->rx_amp, rx_uw); - ofdm_disassemble_qpsk_modem_packet(ofdm, ofdm->rx_np, ofdm->rx_amp, payload_syms, payload_amps, txt_bits); - if (config_profile) PROFILE_SAMPLE_AND_LOG2(ofdm_demod_diss, " ofdm_demod_diss"); - log_payload_syms_flag = 1; - - /* SNR estimation and smoothing */ - if (config_profile) PROFILE_SAMPLE(ofdm_demod_snr); - float EsNodB = ofdm_esno_est_calc((complex float*)payload_syms, coded_syms_per_frame); - float snr_est_dB = ofdm_snr_from_esno(ofdm, EsNodB); - snr_est_smoothed_dB = 0.9f * snr_est_smoothed_dB + 0.1f *snr_est_dB; - if (config_profile) { - PROFILE_SAMPLE_AND_LOG2(ofdm_demod_snr, " ofdm_demod_snr"); - } - - // LDPC - if (config_ldpc_en) { // was llr_en in orig - - /* first few symbols are used for UW and txt bits, find - start of (224,112) LDPC codeword and extract QPSK - symbols and amplitude estimates */ - assert((ofdm_nuwbits + ofdm_ntxtbits + coded_bits_per_frame) - == ofdm_bitsperframe); - - /* newest symbols at end of buffer (uses final i from last loop) */ - for(i=0; i < coded_syms_per_frame; i++) { - codeword_symbols[i] = payload_syms[i]; - codeword_amps[i] = payload_amps[i]; - } - - /* run de-interleaver */ - COMP codeword_symbols_de[coded_syms_per_frame]; - float codeword_amps_de[coded_syms_per_frame]; - - gp_deinterleave_comp (codeword_symbols_de, codeword_symbols, coded_syms_per_frame); - gp_deinterleave_float(codeword_amps_de, codeword_amps, coded_syms_per_frame); - - float llr[coded_bits_per_frame]; - - if (config_ldpc_en) { - uint8_t out_char[coded_bits_per_frame]; - - if (config_testframes) { - Terrs += count_uncoded_errors(&ldpc, ofdm_config, codeword_symbols_de, 0); - Tbits += coded_bits_per_frame; - } - - symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, - EsNo, ofdm->mean_amp, coded_syms_per_frame); - iter = run_ldpc_decoder(&ldpc, out_char, llr, &parityCheckCount); - - //fprintf(stderr,"iter: %d pcc: %d\n", iter, parityCheckCount); - - if (config_testframes) { - /* construct payload data bits */ - uint8_t payload_data_bits[data_bits_per_frame]; - ofdm_generate_payload_data_bits(payload_data_bits, data_bits_per_frame); - - Nerrs_coded = count_errors(payload_data_bits, out_char, data_bits_per_frame); - Terrs_coded += Nerrs_coded; - Tbits_coded += data_bits_per_frame; - } - - fwrite(out_char, sizeof(char), data_bits_per_frame, fout); - } else { - /* lpdc_en == 0, external LDPC decoder, so output LLRs */ - symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, EsNo, ofdm->mean_amp, coded_syms_per_frame); - fwrite(llr, sizeof(double), coded_bits_per_frame, fout); - } - } else { // !llrs_en (or ldpc_en) - - /* simple hard decision output for uncoded testing, excluding UW and txt */ - assert(coded_syms_per_frame*ofdm_config->bps == coded_bits_per_frame); - for (i = 0; i < coded_syms_per_frame; i++) { - int bits[2]; - complex float s = payload_syms[i].real + I * payload_syms[i].imag; - qpsk_demod(s, bits); - rx_bits_char[ofdm_config->bps * i] = bits[1]; - rx_bits_char[ofdm_config->bps * i + 1] = bits[0]; - } - - fwrite(rx_bits_char, sizeof (uint8_t), coded_bits_per_frame, fout); - } - - /* optional error counting on uncoded data in non-LDPC testframe mode */ - - if (config_testframes && (config_ldpc_en == 0)) { - /* build up a test frame consisting of unique word, txt bits, and psuedo-random - uncoded payload bits. The psuedo-random generator is the same as Octave so - it can interoperate with ofdm_tx.m/ofdm_rx.m */ - - int Npayloadbits = ofdm_bitsperframe-(ofdm_nuwbits+ofdm_ntxtbits); - uint16_t r[Npayloadbits]; - uint8_t payload_bits[Npayloadbits]; - uint8_t tx_bits[Npayloadbits]; - - ofdm_rand(r, Npayloadbits); - - for(i=0; i 16384; - //fprintf(stderr,"%d %d ", r[i], tx_bits_char[i]); - } - - uint8_t txt_bits[ofdm_ntxtbits]; - - for(i=0; i= NDISCARD) { - Terrs2 += Nerrs; - Tbits2 += ofdm_bitsperframe; - } - } // config_testframes ... - - frame_count++; - } // state "synced" or "trial" - - nin_frame = ofdm_get_nin(ofdm); - ofdm_sync_state_machine(ofdm, rx_uw); - - /* act on any events returned by state machine */ - - if (ofdm->sync_start) { - Terrs = Tbits = Terrs2 = Tbits2 = Terrs_coded = Tbits_coded = frame_count = Nerrs_raw = Nerrs_coded = 0; + for (i = 0; i < Npayloadbits; i++) { + payload_bits[i] = r[i] > 16384; + // fprintf(stderr,"%d %d ", r[i], tx_bits_char[i]); } - if (config_testframes && config_verbose) { - fprintf(stderr, "%3d st: %-6s", f, statemode[ofdm->last_sync_state]); - fprintf(stderr, " euw: %2d %1d f: %5.1f eraw: %3d ecdd: %3d iter: %3d pcc: %3d\n", - ofdm->uw_errors, ofdm->sync_counter, - (double)ofdm->foff_est_hz, - Nerrs, Nerrs_coded, iter, parityCheckCount); + uint8_t txt_bits[ofdm_ntxtbits]; + + for (i = 0; i < ofdm_ntxtbits; i++) { + txt_bits[i] = 0; } - if (config_log_payload_syms) { - if (! log_payload_syms_flag) { - memset(payload_syms, 0, (sizeof(COMP)*coded_syms_per_frame)); - memset(payload_amps, 0, (sizeof(float)*coded_syms_per_frame)); - } - fwrite(payload_syms, sizeof(COMP), coded_syms_per_frame, fdiag); - fwrite(payload_amps, sizeof(float), coded_syms_per_frame, fdiag); - } - - f++; - } // while(fread(.., fin)) - - flush_all(); // To make sure this function is included in binary. - fclose(fin); - fclose(fout); - fclose(fdiag); - - if (config_testframes) { - printf("BER......: %5.4f Tbits: %5d Terrs: %5d\n", (double)Terrs/Tbits, Tbits, Terrs); - if (!config_ldpc_en) { - printf("BER2.....: %5.4f Tbits: %5d Terrs: %5d\n", (double)Terrs2/Tbits2, Tbits2, Terrs2); + ofdm_assemble_psk_modem_packet(ofdm, tx_bits, payload_bits, txt_bits); + + Nerrs = 0; + for (i = 0; i < ofdm_bitsperframe; i++) { + if (tx_bits[i] != rx_bits[i]) { + Nerrs++; + } } - if (config_ldpc_en) { - printf("Coded BER: %5.4f Tbits: %5d Terrs: %5d\n", - (double)Terrs_coded/Tbits_coded, Tbits_coded, Terrs_coded); + + Terrs += Nerrs; + Tbits += ofdm_bitsperframe; + + if (frame_count >= NDISCARD) { + Terrs2 += Nerrs; + Tbits2 += ofdm_bitsperframe; } + } // config_testframes ... + + frame_count++; + } // state "synced" or "trial" + + nin_frame = ofdm_get_nin(ofdm); + ofdm_sync_state_machine(ofdm, rx_uw); + + /* act on any events returned by state machine */ + + if (ofdm->sync_start) { + Terrs = Tbits = Terrs2 = Tbits2 = Terrs_coded = Tbits_coded = + frame_count = Nerrs_raw = Nerrs_coded = 0; } - if (config_profile) { - printf("\nStart Profile Data\n"); - machdep_profile_print_logged_samples(); - printf("End Profile Data\n"); - } + if (config_testframes && config_verbose) { + fprintf(stderr, "%3d st: %-6s", f, statemode[ofdm->last_sync_state]); + fprintf(stderr, + " euw: %2d %1d f: %5.1f eraw: %3d ecdd: %3d iter: %3d pcc: %3d\n", + ofdm->uw_errors, ofdm->sync_counter, (double)ofdm->foff_est_hz, + Nerrs, Nerrs_coded, iter, parityCheckCount); + } + + if (config_log_payload_syms) { + if (!log_payload_syms_flag) { + memset(payload_syms, 0, (sizeof(COMP) * coded_syms_per_frame)); + memset(payload_amps, 0, (sizeof(float) * coded_syms_per_frame)); + } + fwrite(payload_syms, sizeof(COMP), coded_syms_per_frame, fdiag); + fwrite(payload_amps, sizeof(float), coded_syms_per_frame, fdiag); + } + + f++; + } // while(fread(.., fin)) + + flush_all(); // To make sure this function is included in binary. + fclose(fin); + fclose(fout); + fclose(fdiag); + + if (config_testframes) { + printf("BER......: %5.4f Tbits: %5d Terrs: %5d\n", (double)Terrs / Tbits, + Tbits, Terrs); + if (!config_ldpc_en) { + printf("BER2.....: %5.4f Tbits: %5d Terrs: %5d\n", + (double)Terrs2 / Tbits2, Tbits2, Terrs2); + } + if (config_ldpc_en) { + printf("Coded BER: %5.4f Tbits: %5d Terrs: %5d\n", + (double)Terrs_coded / Tbits_coded, Tbits_coded, Terrs_coded); + } + } + + if (config_profile) { + printf("\nStart Profile Data\n"); + machdep_profile_print_logged_samples(); + printf("End Profile Data\n"); + } - printf("\nEnd of Test\n"); - fclose(stdout); - fclose(stderr); + printf("\nEnd of Test\n"); + fclose(stdout); + fclose(stderr); - return 0; + return 0; } /* vi:set ts=4 et sts=4: */ diff --git a/stm32/unittest/src/tst_ofdm_mod.c b/stm32/unittest/src/tst_ofdm_mod.c index 77b2e370..33a35a0d 100644 --- a/stm32/unittest/src/tst_ofdm_mod.c +++ b/stm32/unittest/src/tst_ofdm_mod.c @@ -29,7 +29,7 @@ * * Typical run: - ofdm_gen_test_bits stm_in.raw 6 --rand + ofdm_gen_test_bits stm_in.raw 6 --rand ofdm_mod stm_in.raw ref_mod_out.raw @@ -58,191 +58,195 @@ */ #include +#include +#include +#include #include #include #include -#include -#include -#include #include -#include "semihosting.h" #include "codec2_ofdm.h" -#include "ofdm_internal.h" -#include "ldpc_codes.h" -#include "interldpc.h" +#include "debug_alloc.h" #include "gp_interleaver.h" - -#include "stm32f4xx_conf.h" -#include "stm32f4xx.h" +#include "interldpc.h" +#include "ldpc_codes.h" #include "machdep.h" - -#include "debug_alloc.h" +#include "ofdm_internal.h" +#include "semihosting.h" +#include "stm32f4xx.h" +#include "stm32f4xx_conf.h" int main(int argc, char *argv[]) { - struct OFDM *ofdm; - FILE *fcfg; - struct LDPC ldpc; - - // Test configuration, read from stm_cfg.txt - int config_verbose; -// int config_testframes; - int config_ldpc_en; -// int config_log_payload_syms; - int config_profile; - - int Nbitsperframe, Nsamperframe; - int frame = 0; - int i; - - semihosting_init(); - - printf("OFDM_mod test and profile\n"); - - // Read configuration - a file of '0' or '1' characters - char config[8]; - fcfg = fopen("stm_cfg.txt", "r"); - if (fcfg == NULL) { - fprintf(stderr, "Error opening config file\n"); - exit(1); - } - if (fread(&config[0], 1, 8, fcfg) != 8) { - fprintf(stderr, "Error reading config file\n"); - exit(1); - } - config_verbose = config[0] - '0'; -// config_testframes = config[1] - '0'; - config_ldpc_en = config[2] - '0'; -// config_log_payload_syms = config[3] - '0'; - config_profile = config[4] - '0'; - fclose(fcfg); - - PROFILE_VAR(ofdm_mod_start); - if (config_profile) machdep_profile_init(); - - struct OFDM_CONFIG *ofdm_config; + struct OFDM *ofdm; + FILE *fcfg; + struct LDPC ldpc; + + // Test configuration, read from stm_cfg.txt + int config_verbose; + // int config_testframes; + int config_ldpc_en; + // int config_log_payload_syms; + int config_profile; + + int Nbitsperframe, Nsamperframe; + int frame = 0; + int i; + + semihosting_init(); + + printf("OFDM_mod test and profile\n"); + + // Read configuration - a file of '0' or '1' characters + char config[8]; + fcfg = fopen("stm_cfg.txt", "r"); + if (fcfg == NULL) { + fprintf(stderr, "Error opening config file\n"); + exit(1); + } + if (fread(&config[0], 1, 8, fcfg) != 8) { + fprintf(stderr, "Error reading config file\n"); + exit(1); + } + config_verbose = config[0] - '0'; + // config_testframes = config[1] - '0'; + config_ldpc_en = config[2] - '0'; + // config_log_payload_syms = config[3] - '0'; + config_profile = config[4] - '0'; + fclose(fcfg); + + PROFILE_VAR(ofdm_mod_start); + if (config_profile) machdep_profile_init(); + + struct OFDM_CONFIG *ofdm_config; + + ofdm = ofdm_create(NULL); + assert(ofdm != NULL); + + /* Get a copy of the actual modem config */ + ofdm_config = ofdm_get_config_param(ofdm); + + ldpc_codes_setup(&ldpc, "HRA_112_112"); + + Nbitsperframe = ofdm_get_bits_per_frame(ofdm); + int Ndatabitsperframe; + if (config_ldpc_en) { + Ndatabitsperframe = ldpc.data_bits_per_frame; + } else { + Ndatabitsperframe = + ofdm_get_bits_per_frame(ofdm) - ofdm->nuwbits - ofdm->ntxtbits; + } + + Nsamperframe = ofdm_get_samples_per_frame(ofdm); + // int ofdm_nuwbits = (ofdm_config->ns - 1) * ofdm_config->bps - + // ofdm_config->txtbits; + + if (config_verbose) { + ofdm_set_verbose(ofdm, config_verbose); + fprintf(stderr, "Nsamperframe: %d, Nbitsperframe: %d \n", Nsamperframe, + Nbitsperframe); + } + + int ofdm_ntxtbits = ofdm_config->txtbits; + + uint8_t tx_bits_char[Ndatabitsperframe]; + int16_t tx_scaled[Nsamperframe]; + uint8_t txt_bits_char[ofdm_ntxtbits]; + + for (i = 0; i < ofdm_ntxtbits; i++) { + txt_bits_char[i] = 0; + } + + if (config_verbose) { + ofdm_print_info(ofdm); + } + + int sin = open("stm_in.raw", O_RDONLY); + if (sin < 0) { + printf("Error opening input file\n"); + exit(1); + } + + int sout = open("mod.raw", O_WRONLY | O_TRUNC | O_CREAT, 0666); + if (sout < 0) { + printf("Error opening output file\n"); + exit(1); + } + + while (read(sin, tx_bits_char, sizeof(char) * Ndatabitsperframe) == + Ndatabitsperframe) { + fprintf(stderr, "Frame %d\n", frame); - ofdm = ofdm_create(NULL); - assert(ofdm != NULL); - - /* Get a copy of the actual modem config */ - ofdm_config = ofdm_get_config_param(ofdm); - - ldpc_codes_setup(&ldpc, "HRA_112_112"); - - Nbitsperframe = ofdm_get_bits_per_frame(ofdm); - int Ndatabitsperframe; - if (config_ldpc_en) { - Ndatabitsperframe = ldpc.data_bits_per_frame; - } else { - Ndatabitsperframe = ofdm_get_bits_per_frame(ofdm) - ofdm->nuwbits - ofdm->ntxtbits; - } - - Nsamperframe = ofdm_get_samples_per_frame(ofdm); -// int ofdm_nuwbits = (ofdm_config->ns - 1) * ofdm_config->bps - ofdm_config->txtbits; - - if (config_verbose) { - ofdm_set_verbose(ofdm, config_verbose); - fprintf(stderr, "Nsamperframe: %d, Nbitsperframe: %d \n", Nsamperframe, Nbitsperframe); - } - - int ofdm_ntxtbits = ofdm_config->txtbits; - - uint8_t tx_bits_char[Ndatabitsperframe]; - int16_t tx_scaled[Nsamperframe]; - uint8_t txt_bits_char[ofdm_ntxtbits]; - - for(i=0; i< ofdm_ntxtbits; i++) { - txt_bits_char[i] = 0; - } - - if (config_verbose) { - ofdm_print_info(ofdm); - } - - int sin = open("stm_in.raw", O_RDONLY); - if (sin < 0) { - printf("Error opening input file\n"); - exit(1); - } - - int sout = open("mod.raw", O_WRONLY|O_TRUNC|O_CREAT, 0666); - if (sout < 0) { - printf("Error opening output file\n"); - exit(1); + if (config_profile) { + PROFILE_SAMPLE(ofdm_mod_start); } - while (read(sin, tx_bits_char, sizeof(char) * Ndatabitsperframe) == Ndatabitsperframe) { - fprintf(stderr, "Frame %d\n", frame); - - if (config_profile) { PROFILE_SAMPLE(ofdm_mod_start); } - - if (config_ldpc_en) { - - complex float tx_sams[Nsamperframe]; - ofdm_ldpc_interleave_tx(ofdm, &ldpc, tx_sams, tx_bits_char, txt_bits_char); + if (config_ldpc_en) { + complex float tx_sams[Nsamperframe]; + ofdm_ldpc_interleave_tx(ofdm, &ldpc, tx_sams, tx_bits_char, + txt_bits_char); - for(i=0; i=3) { - fprintf(stderr, "\ntx_bits:\n"); - for (i = 0; i < Nbitsperframe; i++) { - fprintf(stderr, " %3d %8d\n", i, tx_bits[i]); - } - } + if (config_verbose >= 3) { + fprintf(stderr, "\ntx_bits:\n"); + for (i = 0; i < Nbitsperframe; i++) { + fprintf(stderr, " %3d %8d\n", i, tx_bits[i]); + } + } - COMP tx_sams[Nsamperframe]; - ofdm_mod(ofdm, tx_sams, tx_bits); + COMP tx_sams[Nsamperframe]; + ofdm_mod(ofdm, tx_sams, tx_bits); - if (config_verbose >=3) { - fprintf(stderr, "\ntx_sams:\n"); - for (i = 0; i < Nsamperframe; i++) { - fprintf(stderr, " %3d % f\n", i, (double)tx_sams[i].real); - } - } + if (config_verbose >= 3) { + fprintf(stderr, "\ntx_sams:\n"); + for (i = 0; i < Nsamperframe; i++) { + fprintf(stderr, " %3d % f\n", i, (double)tx_sams[i].real); + } + } - for(i=0; i Date: Sat, 6 Apr 2024 05:32:33 +1030 Subject: [PATCH 24/26] freedv 2020 build issue --- src/freedv_2020.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/freedv_2020.c b/src/freedv_2020.c index 83e1f54a..a1a3d318 100644 --- a/src/freedv_2020.c +++ b/src/freedv_2020.c @@ -279,8 +279,8 @@ int freedv_comprx_2020(struct freedv *f, COMP demod_in[]) { uint8_t out_char[coded_bits_per_frame]; if (f->test_frames) { - Nerrs_raw = count_uncoded_errors(ldpc, &f->ofdm->config, - codeword_symbols_de, payload_amps_de, 0); + Nerrs_raw = count_uncoded_errors( + ldpc, &f->ofdm->config, codeword_symbols_de, codeword_amps_de, 0); f->total_bit_errors += Nerrs_raw; f->total_bits += f->ofdm_bitsperframe; } From 15f565ea0090831fc5ce40e4d40e31fc6184d167 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Sat, 6 Apr 2024 06:28:13 +1030 Subject: [PATCH 25/26] fix stm32 build ... --- stm32/unittest/src/tst_ofdm_demod.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stm32/unittest/src/tst_ofdm_demod.c b/stm32/unittest/src/tst_ofdm_demod.c index 3be96c4f..980c2368 100644 --- a/stm32/unittest/src/tst_ofdm_demod.c +++ b/stm32/unittest/src/tst_ofdm_demod.c @@ -300,7 +300,7 @@ int main(int argc, char *argv[]) { } symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, EsNo, - ofdm->mean_amp, coded_syms_per_frame); + ofdm->mean_amp, ofdm->bps, coded_syms_per_frame); iter = run_ldpc_decoder(&ldpc, out_char, llr, &parityCheckCount); // fprintf(stderr,"iter: %d pcc: %d\n", iter, parityCheckCount); @@ -321,7 +321,7 @@ int main(int argc, char *argv[]) { } else { /* lpdc_en == 0, external LDPC decoder, so output LLRs */ symbols_to_llrs(llr, codeword_symbols_de, codeword_amps_de, EsNo, - ofdm->mean_amp, coded_syms_per_frame); + ofdm->mean_amp, ofdm->bps, coded_syms_per_frame); fwrite(llr, sizeof(double), coded_bits_per_frame, fout); } } else { // !llrs_en (or ldpc_en) From 7f6188ccce691d8d93a1dd98ab86a9f0b23ffe36 Mon Sep 17 00:00:00 2001 From: drowe67 Date: Wed, 1 May 2024 15:56:01 +0930 Subject: [PATCH 26/26] adjusted clipper in C & octave, regerenerated curves --- README_data.md | 4 ++-- doc/c_tx_comp.png | Bin 36292 -> 39874 bytes doc/c_tx_comp_thruput.png | Bin 32796 -> 34357 bytes octave/ofdm_mode.m | 2 +- src/ofdm_mode.c | 4 ++-- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README_data.md b/README_data.md index 3ceccb46..49fb9e3e 100644 --- a/README_data.md +++ b/README_data.md @@ -146,7 +146,7 @@ These modes use an OFDM modem with powerful LDPC codes and are designed for send | DATAC3 | 500 | 321 | 126 | (2048,1024) | 3.19 | 74/100 at 0dB | Forward link data (low SNR) | | DATAC4 | 250 | 87 | 56 | (1472,448) | 5.17 | 90/100 at -4dB | Forward link data (low SNR) | | DATAC13 | 200 | 64 | 14 | (384,128) | 2.0 | 90/100 at -4dB | Reverse link ACK packets (low SNR) | -| DATAC14 | 250 | 58 | 3 | (112,56) | 0.69 | 90/100 at -4dB | Reverse link ACK packets (low SNR) | +| DATAC14 | 250 | 58 | 3 | (112,56) | 0.69 | 90/100 at -2dB | Reverse link ACK packets (low SNR) | Notes: 1. 16 bits (2 bytes) per frame are reserved for a 16 bit CRC, e.g. for `datac3` we have 128 byte frames, and 128-2=126 bytes/frame of payload data. @@ -246,7 +246,7 @@ This command line demonstrates the effect: ``` Try adjusting `--clip` and `No` argument of `ch` (noise level) for different modes. Note the SNR estimates returned from `freedv_data_raw_rx` compared to the SNR from the channel simulator `ch`. You will notice clipping also increases the RMS power and reduces the PER for a given channel noise power. CPAPR will also reduce with clipping enabled. -The following plots illustrate the SNR estimates versus actual channel SNR with and without compression (clipping). Not that even with the uncompressed waveform there is a small offset of around 1dB, possibly due to modem implementation loss or noise in the frequency, phase, or timing estimators. +The following plots illustrate the SNR estimates versus actual channel SNR with and without compression (clipping). Note that even with the uncompressed waveform there is a small offset of around 1dB, possibly due to modem implementation loss or noise in the frequency, phase, or timing estimators. ![](doc/snrest_snr_ctx.png) ![](doc/snrest_snr_ctxc.png) diff --git a/doc/c_tx_comp.png b/doc/c_tx_comp.png index 5d781c059cc0ec2f2c398ff19870ba13c161b1f1..a95047961d140ee2c7bba3776562badb02aa930d 100644 GIT binary patch literal 39874 zcmdqJhdZ3#w>Ca{?;=DAi5@*#^b*l~36ddt2|+NU6Fqvb(M3xTZFHiG5@n)}E;^$d z-TU}_&pE$yzUO`afuHMg&5Y-n{p`K=-fP|KUibD{OGA+e{}Db21R_#adZ_~fVQd1w zjJW8)U!=Vj?LioGbxJq{i&y-%4aQv$PZ(!W+<^MT83~#3`UXe4G5_y#ggcWFR_~TM!reK$FV@ zZCNAdMVc%Wy>!c6y7!3Ft?T-N>&S&O8e@2nRmY&WxlYqh#IG0{YRVzZMfRwil+MEw zH;l!JW-?HDw|CRx$VCM%S-jo#T0=w2#|w@?H&E`b)#Q+hrqOc2pc+Z+U8l`!!yj*# z1QrdKH~l?#Re!g0T_&vV41@h1;DhKmE@_GHLhoGnBlJWAPo_)y9f~wR>UkNYF(#zL z#_Gi7ZPvDydtHo4y9uh>8HgHl*_Ya z#~TRvn3ttRriZUD>#jv{Lf}T+ggzgPofkGLDu{7I zItzZm-}rfPU;;cYOtX{z;K8}D1R=q4>H!@(>61>GBr0L!<1fQ^N1yn#tuQYiUGTDG#8$wV zp*#pCb3FQK)7Fiu2?UP$@Up(3GB0c_aT7{S82ZV~Y>4l78lxJW_46WXS88}@V-!ud z+Pn21hTjPdaNdWU{tPvPS7A-7>?-fF?2UkhT!ENl9a0`*yL>tS^peFcv7wMu2iuN!OLR+Mi)o7}U2T#Pkr4E2>u>8{)+7BR z-6L=QjI?ZOO)fcyOv5_Eu<5wzXZYFq8p~S3S|8chlAb4tCmFf4G=z%2mQeHg=23V4 z;!ykW1ztvQUU1mU{FhZPYr54x+i|9p$-3vh(mF2fD|uZ?AsAewvO0cVn^lWFD>TbY zFK;vEc_QjXc4Ko10y8To3#Oc;4yAlgX<_8BWF#cQj*b7xZuZ4Gicd;~L&>G^$=5Tb zIu#=2Vbwupgo>idLzQ}E5>@{svjNfm?!n#Ett8JUkqHwirwPLel25$VAqFu#HXMDa z&yvKsQA!LcQz@vFMJ|7}Hd8+hS?vyGCWSnm<+9H2Pvpl8_%*834)a%{$kl%ta~PDW zZ6%Bo6fq}usl7Ac^G<(afz^No;7om!oueqmAJ zdPT~&$^0`V`pVx1vD(sw3&m2E9pkv8)8%8O{KE;>hlVe{Q%$C^8o^&Z*RL@-Zlih? zS^l8X-EgNu_`731eF3=KR}p8Df8z7Ri|@}H@LLCb^vBCC^$VJqf%R1MArjfZAU~qc zt68bLpWK$=_v6;@M(Sn+n-v=m8?Y5oX(&&qXXt#WKdl|@VRTtEvXZqD^0w8{eQJBE zSFAU_S25~w+Nj!`IwbQX%|}4!`GYiA#_zP!^pK2+wCL(B2Tch7Rp^V^x!*pS7oNu? zR7F%K$NTOuCFI8jH+^gOell)AX~FM>Fi`?S20k+ZH%bSnE0+4;f4d zcf!UX5d}S;SHJviseb;0V!947?8@EnrXh8Y%$w~>?g|a3EGS1lxl32^L7#0Om12D2 zXSj9o;BQ--*Bdv#3nZ`@a;CZ|8q*pbT(;a<8ZR3R8l_y}5Q+x(<}M@;5@)V&vaC|w z7_o&W;lY3-k?m0}p)2Ws+UeoxrSC0?5}S`*IORoR!b z@_1$Ouk_=0kF_2#68<5XBli$j5@r`Jc}7jU`*@C8qW-tpe204?6Dt!I)6*TJotmk| zImTutv#yZnq-ZnRk>GrJ47p7Chg}k#+gbR7n%=8nqUG9NFj$?|h?x zt+1vL*>k<}M*7jM;o<7|LP;wIOA(9Wnb{f39_@@%&E3n=mv$Ok-DoHbdaI%8&gyy` z5r!6{4{uGp!=2ez<9>9O=YP{F|7f~quCF%XTnBc46YVpcopI4c78VvW&0trqU&38> z;~{vq86;NR6yp_rd1R4hk=~araxQXm(|uc7stv0uIfNe;u69x^uz6(;Wn^TmWvUu4 zr9`EqCo-|^-#Xm6zW(;%J(#wje3Mc%*=4(jH?KYF3q*3>HPpB0^!w#Z2E^Fg4ChZ( zsr(-rIL9vj_9Kanj}^s;>gJo*t){U#ucb_VWsl^3DRlN+N2;cNCNtYjZwL)yBX`byL{^@HKOs6TvVz46WNo2+_uC+JD$N&TJm(VMG) zEtx+08gVdmcppMiLDM2?`P^+S`=)gV<%x14sibG1JEZmGdnCZ=Lwfk`(yX&)&epXKp8W@hR2Ys9H=eo6n<%GFP3V7te+~ ze@ptNo_{cFJ7^ucqIf+0_)-en=ZkT_aW{{F&-mq^o0-$&X`PkvacMcOxVEHWLIMCp z;W;ZAxq?6h6!*Vqpo}lnz#r+|6b#*TovhuUW*@CUidt%nindO!Zmt#|?VQ~hU+@U= z-ByDYKp;kt@=IAgk1xCP(oP7&HtE~0$1n}GDQE;=0GK&>%i5ZjCO;qx;#AVOM*Jf$ z`=>*pwUf)UUyF*2P178_>=v2a)*yIXW;*C${Hk8;O3?1%eI zi@cc6VK3<*Z9rLB*Rq!W1bvfW<}eQr548o%Q8A~^(OW8~xnjpNJDhwFp&YkB^jydd;8bR3OL(hD`nG)y=di@$rR(?t@U2k36z%GSq*#H0yC$dkdL0lj zuE^@|Wo3!KUZz;}rrBkjkdGYquDWd2phZ7HUETDU%f{Mfta~oZ%owTsR@zV7@C1zF zKln|Bu*(^o>aWTj_39~jN^%75p2d>Nf;pv>%mfTEvL{dq%v?Sus6NXSvzk_9NBwF% zp(h0|f+(v_v$Wy`d;%vf)3-a>tNQ@*}c?v!{bS!+SF`-_~MS>kP z?Wa);4+{$$v#p&#ehre!9H#5T+k3nqP^pzqWsV}zH}uSYIG@OI6)Yy4u4ap7l){#X z=SZ*@_RCW4iW8#TM(ar*X=N0%I=a==b*ttXvCnR7mlEC7(2#pWHOEviwz<3OAeG~E zdVvq()Ir5bgZ@O7i3j@A)pJGqNoz?eyM2o0K5x@}I$WjjXl(3*Ni#X85MEnEY)vSq zG7^cj{q9m9GgdEqLOJnQi;UP*NK&DS!!R|W?Uj9mbXsli5)9s{tQtD1TWQj29@Xl1 zlL1X*z-!~Q8d1!dM<56wnYRsONjho|5F<7t+5El0YBFd}%?A1xj(mJ6R-4K-7*QeC zv|L*tj7(grU>wPoB=F6wWhS7kIQRg6D84_ER2=wzs9M`iH2*ybJqO5^5)&FwQNT|6 zoh^^Kn?hM!}yLSA_pIfxCbqTHOSaDBGdqW)&y!%D%3t zxw)X>mnL(JlV~g7d86CvjFM`b*XcGA8MB`_TK`An@^Yre=HbJKE%bh@qcy-}ZIrg?iIq<2p|M!R+TujiZoHNd5Qx>lV5f&;Ac=EJ}Uv|4^5y@I? zbcf`X%u;Vm`P5EiBte8M(B}2PYiUx;eI(K&b(7=r$r<^0($eotb>o6N(X=97nwPQd zp2wk~(^ES_?bu1ubuJ^iUc|RFpNtC}^RR<*kmoOc!v6v;WBu53d@L{ZKr+!6g{=vj zUizJ?Cgz%-!%_s_$`oaXC$I-8@uCB_;jv`6r0#nr} z>%2uY-YR*DoO8tR)yL_X)zkiM-F)5|_bP;ARZF@rlZc)>W1F%ca&dxl%+A8ni6tjQ-Wc?PESvFrYbpZ3{y@I4xHWGl6>>uM;O%lTs{h_=B3#^*xK6a>FHTp zvpT6H65sdirB?X)y3#~DRn#lbE+g@mWtE{}io5Uqk0c#2FvI!7*i$Bdki+a}C;c^= z0u?+Sd%X3>w?oz61`+I_68cwjXt^F$W+7UkZSCzFITRj^$Gz5e4e=uqUDU@CvGx)P z>*UJ&)Q>a23|nUu9X<+r_gYg^lg6U2ukVEW>p|3NNA?Cys0-lIsA-AAXs6OzovH%q zHJZTM>jF`C;|-55=WNfK|U*!DC-gk_Z}Qr*^>!P~+j{Wl_eL z+*B+wUR2Vb@N4(?^eo*G(_htc8%$`i`+m5*ynKG1_9}_K&(p(W8t^mGZ>?XB{RLtP zt9{{*2fLiZRsB*MOF*RB*?AAMI}mTG1cITu3#V`NNn_xE!g#8h+*OJ%@k-vX^2~8l z1-#cjqiCne5mCF`%@?)h=H=__NZtF6?+4e;_4PHdSp`*})=^}bND4NnWR?c-E70Y4!`vbc%bs}ki+h@SL=Er-gwmG z6CtO#-t!BV%#J~|=g4osxRxfrR>qsYoZd%?oJA8L<=YGoGL#lRok+GDm!CU?0zRc9 zQdfSYdc-v$7((#SdyPkkde=W?UNv?!ISFu7HVyp(4R#%e>q{>Z+cf3ADP5$r5iiW7 z5}WT5Ko@d2G_hV+O}q*@qXmo5iTjg*{%9Q4Kjy;#^^dsFaemRFv_g!(xivOQab4gX zKbCl8s;4jXwX;}e>wTem!5(L+6Nh(pCI4COflWd4ygS(BNBo~R2pCRK2e1L2-<~pl zJ#MClnuw9ptNwNsCcmT|?CA;N8a%=I6fHUD=x;qU;VC>II6P*10-%%8y%A6@-aP`% zeUec$Mx^!aB1(^iVNRivPg_4(iney28m$(@qk#nqh#0c22@AvV+j(O92m!0CtOTN^ zj=0S8dp`s!2`?+V=8@x;PhQC%saJAEMBXZ51W?JB{)2l1Kw6x-70~?!-}8e!q{%&Y z4LBg38$4R>3X{IO7`eO1xhu7sUA((RHT?@7o69@%4&HyRY`fgPyWPKYK-`{Ancgkl zUHQACaPM2d3|%sbAbBj+;(K)l?0wA~shjO@mA5uj-{uRx-_ct-}VWM=jz*mE* zyjzHeye8LIi+2@uivL^^1}=4*#9bns+VvMH*wdJOFWAhlI`MZ?byu zq7Q}7LH(j%4cE@{vYC^UlPH~(?`0DI*-B63=3x8f_BZ=Wyst|%86SLQ7FEcft!m)5 z#rFR`jM{mgWk~A?UXRn!F_xWEdI^Kk$W9f=>JT-%Fpjn~2Iu+I(prsfef_|4L-LyWWt_0lJ8sZ{BaeTM!&>zu9v#^*x=KGWEY{zq@Mp zzd|fhTiib|=k}yHNuQ2^p;zj*(*Ne$Pohr}#MBm1^9u_{m8$OE-n4Hmis|`YUF8Md zm~hj*1qB%5;FMoQo04LphNXPY4+eSL`FgV@JjuNVs=v42o>uBH93QO>O5bd0IxV(+ zE%rNK4g)%IyN9T6zd1v|i#3V5luQSQht=_X!vg8)7z7sm1?jTO_14eMAjg$?KS{4T zHby?4uPG3d5X*n%!XkdHoB0Bv`J%fj2qkrlKxr4A3fwRT^*e{>EtAfPcwZc?#Y*4y zQ(q05-je%V&o@J(ssJ;_UdOX(X1Dm?Ww#H&9503Ss7T~i%p?svbY6WDm*|bO3_Prl zdP1R>g8`E1GBZe@SFrhvMUs1x$I+p9O}zG!qZ|SXe9xc2t#;8t+#1pQZYq ziYK#=$`JQU1Q*$}8ib@#j+)QcctuHBG#L2@cLxvssx|3GSN-aGN5B9`1Q%A50>frfnz9(Z2uP)lOym zLSAW5SJ&v`Z?Z+SAUN_`bFei#z^Pa%J6;q5CN1y z*NG4a^n+FnCq2@PdVjHfN^q|y@*yxE^u%F{zNq;R>R>RKzgI&;gF$i|-aVefRapT+ za{fWC_QlRTMDq7a`T#Z_0b&woyo~7OZA?8^adV( zKefOTPUSU4PIdypW_UQ7^35Z%7cwWUlqg2Q$w`sZ+(b0#?3I3x@E{Ry^yMX_M0uDz zAyF6^3Vrv>RFH>#ajcGM+Zw^kHjk5!mQXSW&u0C5d+=(lE^xK&t0pij^ZVm{Hu!nm zmv=!-LK5h&)!|+Y-0_1O6J@=Ov3F?o+=ZVWPmAlCf~NXD3wOZkXyvFjby!kt3kW4J z=vNvSb{9}W0ap%a0=)~q7JFKX$y%wbmfRaYpwHRGJ_ysoOQ&?Gn1%mal$LGoeS*$N z64fALtm1=8?i% z@Wpx`hm=in)Hy-RIe}K(r~|!S$G#w=s;2Gc+qpu&>GgCIEXL2He~xns7`%7QwR0-Wi{XBCerT~7zm|~o<%HTIp*v;p+h~pCWezi#JJkwBgij{f zedranjALME6#FreQCjvCu*~sw%Fd}*i2GOYc<9GG0pJ13?S`Jw-UIO~Pm!W2(ZFu@ zc6KJc(sNEhoo^h-a|R~K)5*Oq%AW?moQeJDTG-MKO2T0?Q3fnRyL4k}+mL!c2Fp)N z3vO@Jd4rbN*e&nLjA`5s`dqTO`DSe0MhwtT80j2VoYg4(gaCKI?gClK2f!Hfj17@Y zS3=$4K63Jqdn+^JA_F^ey?1&np@kkLnqJBz_sriTPn?#1Xue^T zPdN0D$Q)L&pF5U7z-TNoihfnU9XDoF&Ng*vHL3jJR~s3u%8kPHLu0)7*LgxSg7bE7enGJ9%P$ zbzN;rbvVp%#)VEDPN{Gx5lh}uR`$XRA7OX?nT;oDARH&Bu4(OX`PHzRcz7~2(d*fy zK|n5$`ZqL8FNdri8o~wpT;5Ji2s(;oQ7woBY%?_waJ7!pqg9pzAud-X;bhI!FMC6~ zP?)gGeSUK_qbR!1V&s_GqR6hPVn;N_E<<^6bhTzi4K4_tDi!W^pC23?1hQ!Y!muuY zo=Ex~Y@iZ7I<Emrr>=DTov^}^Ad*afFc5c2|1C45(d`k9Hc#MmQNnva0!DD z+`xX_4JyI~?*YyyuECDk2Lp3m%g%h%TvP zibWcX%h!)zaPwtp0I0jPuE)H}kjv6Ym@vqMWw5%)uDH0k7Dx$7D|Vu)#%y1PhFG<-gtMfkdgG z;3)z|)MZ$1&{QEF&G0hemiHtNhJ>nvuFN#HoY5jPAra_GC&=g$3EaDwiYy_ZItr4=i73PAp77)AoFzet}`wz zQ!&fJUUdhXR!((L!M3qw{MeJp()O=t_q&7b)6a0PeIE%u*J^!df=Yj^zRjLVrf{}M zt-R0Y>9>LTT=4N<6D}$n8=ICnD+vZ&En*d8Ykz;~KdkCcyQp31!lY;qX69-H`>AOO zvA<13@I>hApWfasRC9S<+}`>fl)-JK(NsdnP{KcRy$5?*mCfhq2dGrGIYvHGL!Hv6 z1G3rbNW!{+Tt!Mj%{ijCwN>&ma8ZbZ6G8)?^305?+Hz|$E~ix3LS41^4psxHOjea_ zFF-p1$tVhia@!~Z<_j)ZO89EZedPF@^ByKSDKu`_G zURf_1+=D8Y6tby82i@;+ zFj24iJ^gecv64wAF%cGTn~ws;pA{27fKALSX;cqM!_je(OHIh*jghS;{gLC%b@j1j zpP!#!lP3+-_Vp6z=arL==0Va}$;ARWT?N$>neF`jP4)Ht{0#!|-x;I}PXqpXJ!Okk zO%y{vLPIFX(>gbquxm*q7`+4tgZJTW&FN)A;o3(UAIo3U%9XG6uHyRoVjn~O9Aj_U zlwZfjI=RJ@(xEsVeE}z2w?oZ6b5sZ=_jzePe#E&|_&_K7v1Ne9uj4_0#Cb3o3z3cX zeoCo>pQ5)FBYhu9&=kR zU*+6>GMs&61B7l6e%R6ir_U%jyp%~dbec>mkHA>>A`5c{_>v(mGHW!>jdbsogSNN4 z7Xj#k2}pq`aXVTa$8qN0``6D=55&)k^k3IglHG2iXUPb?&BM8G6B4kr@L(9Pn?ur0 zgDsQY^;B$NF7MNQDsm+6?3B0ux~UIawNO0(avx6n@j6;vGY+KErRbSKwG%Mi77KI_ zPyiz)3KBu@=F}etxg<)IVpU%no|ay2HJP`y(MT>CJ{sRBAPr zsER!bPyiR{+?!`#$6G5#u|;RU{aVmKxU$as!PpwSt86|*J@yOnIOk7PpD5bnfR=!!dBip~ z>_5Bu{f*qzr9-#Tpw7Z!D%|*32muA;{%3qH`*X&kY|N7yRmEb#)@p+FKB~?#il3_4 ze1X3rg&}1Fh}0LfTl~GS>F2GERG}>piEhc>Cf~FahhNm5GIReL+AyYruo;D%f2<}@j8#z{H!H%tI( z+i*h5r#}POFjU@oAM?{~MW8uKtvbZTg;?Bes@FDVml*VusR62L>y>WZ_V`J2u`IPy z>4#o;)SLO?EDR872r%t=vZ&DF_E0DZFlEnHE7W#<_Oj+_K_DZ>2B-$chFk0dFBDY) zT%Dz#KkNgZmD?>h0CQ!?F;T=90^g#Tl@ovpY3@rlt=E|t$wH%+G+ko(K9SLVtJSah zGlvIEuw<-A=fs(0I*@i5#^hMCb-R zzm%Dt)8Loc1Vh@{s1qkyOKLYK+%0%cZQJ$WOzwtfQZW%wEfBeED6(5UoIaMgFEmu_ zh{afBSdR;woI41^1=XkAr6e??i*Zne$w#l$;D380PLzKhw=A)W0&XtX#-g4$mmL=g z?g*P!pm|Ty8B!MO;E9PPu~e!PI*ovpf`lTYw_emV7umraNw6`DQuOuLVs+PnpbEs` zD#Px{Ha6~pohBZN^p(8w#r@7UF;^f-r4{;`HbSn8k&;5$x-3!kq57QqH zg_9>bC)+a}#b($zF}d~sgENd$w&LGKH``@Uu1)RKt%^qRckyuz>gZ4KMHO1n^)km$4yp1aQ&l_S>n}_I7D8v1&&IU(|&C*PW(SQ6QKBq^gWYoqg89+c8gFphcwUj(?_V0t>@ zddD}~ln9t^#j9v=Z}oQ|Kp_BnQ*b3)!67K4NK!&VG{yn}X%8YW8NWh$ zrw; zff);DJ~M~jC-{^na&qX0a3LgM{n^1&iuU{@{$89$V0LC^Xn3JOJ&-a9fQ~bZizIbh zO+38i%JJ3X9q)b^mv819>g(_C?{g34XPn;L+$`xAkNKe|;Xg&vvux&ycZHOR+bK7w z=Ma+{;|C#yBIpH1j&bsrojrVghl|63-d>m91B}k&mO`EMX)8DIhND1l5u8IGW*J|r zqYnQ)G6J+Nq@HMXvANkVsLmM#;MZe`HoR*Ag8+MA_ksy3^dI3C_GSk8!IL3~+RME-#o#XZ8;*4Kv%0;od2wWX!-g^8t6(S=DuiZ&(7G^;vM z`q$?i&j7>$5S{=O!X$RDzP-X;bhpG%yQ0Opmy0eh|<9v-f)x;I`Nbysa8 zL9y&M@`9y8EgS!GbP6yfJE7x|{gg-%wG>+u6l_hRTQDvvID=Vm)MP4#^A@Mk$Gx?^ zaEW3N^Zne+#sE!|*96Sb_~)@ipUZh?)!>2fOwngVLBb<7xf^ZD9(QolkBRE<5-5n{ zH|%VWJs^nTo?=6(`jO~aMCMkgP)wAg$ryE&``&OS8F@#mgL*&m-!TSF-?=yzkaTKs zV&u#HoQd%8lvEx_$+gA?sk};lkx`_aP}|;KS+;E{P};0J%9N&G#8FXEQD(aJu~Fj1 zi;LQ`fATr+O7YbDYWuww0QS9rt=UKbGQM-@M7wYi`&gQ4+BlXUc4`Ml+_IeRV70Xw z^UO@>Z+2|fxc_noum+xoTUo8;VB!MaodEzg?2UXyB5q`{$!J33ttKD!9}|>6^l%Mh zOd1fBi}u$hZ!!-3tjnkCT{@&uX&W_N5!O4kv+zSs#frMjN@v1^J4lBnAj)|q8S|?E znC+6SzG5-i?rMdUX;9f#IMP7gWCUGT9$lu7siBL_XA|52?ToQT?FBg?@|}Rh2n2eE zfeAeq*yQ;@Jg{)AVR`w1{74h;hY(>ChD<8(orf#LO*IZsz2t?o-7e(m5tIa*4~rjU$7vXLLcs zfsg+}I`sfd5l7rX--bIMr95YwO6gA;Y4}W4RoF3s`Kzy%vOiS@VGt5DDujz9E$|&9 z7W?Z$xUtw%^iP5hdW6+MZys*Ko%qMQsvcj5nXoBL<1Y{~UQ)Z^1Zc=QhbiVy2vpTn z3mSPj^~|Q|)V+ZDEx>HE`$z$bC!C;zo;-M)XnVUirTRoS$&o{oU~n$6#~Nj8zyC+d zj^^H0P{sy%L)%esu0*15+Zn92%HkwVAs_lK$ANH7X%8?3CMHygRaxzCX6)ssI~dgy z8)1$syyhQR_vWMjv_$OEJtW}QIklwzbqb)jSG?l z4$pr5J^fSNJG^7L4?aI$HQ>O1-?sn;6d?RY~ z0ojTG-TqtOMBF5~ztPN4G|8|)m|6mZ0F-3z1nHdF^HT1D_`KT5gz`W=DTKKk)E4dD z8j(y57&1HEEGMHDRZu;wfCvB}96f^GS7dt3mpx59-=md~2Z|Tk&y%uX&lea!HuPn+ z`qJHc+MNHCZ<8nP|HBAjDK<4TK$5^cn4wV|kb-!8WA*t_^htC3~keXsh2=Cnhd@sB~;tSanJij@$nft!k}3#02n; zY2Afu`fvPJ?8m%Mz@Kb9Jf-`GA6&nDb731}WmHyXp8=cr_Vym30QK7wIq+059LW0D zPRLf)E*f_v{fe`_?Z`JxgMEsc$(%@6sdZeS_QlNImv8kNHl z+Zs6ReX6v)uCusAwfis;e83aRMH&5a#V4wLSS>wxBg><>fJ%*`cy0xV9^Ay-=@hk- zepwz}U$}a?F2_Fa1=2z_)@rWIBIVV!e<@b1HtUM)Oy0ijuarcP+vY; zKL$`GW$gGYesDpc7zt#57DMjy7d}$yYSb1!6t4>6t&D1adProev38W}QnHM+ksDhC zMQ`%05F06zORWoKGS>+PJT%M93Zln5P#z}=bG;7MVgwM)&Z)7!{%u3WPWDFhtEVH! z083(ZIQl*@WjS_nex3*$W3bu{OwwjHym9Q;{&-K#AqdF1NhUOl;Q(_1P$fOW0K^f_ z+-gEL@sw-8Yk`UoJ`%t_F{>qIVvnMW;H5=&6+6h50+2c2c&yv@>#facR-_r+QeWn* z&7)imZ0etvSub#pVIdmj#d>Z0oJYatg%vyFN-wcc&gAO;YPvTp zj}eSS`QrZ@`&52!>9X3@|PVKxS09TlY8RzO@ z3n$JRa${u0RfgkYJ(r7&UTt~D`e;R4i6ZC@^MOc}nzM>~cHw`+Kw|?bg;r-Ml)Ph8 zg*O!12N$-8itV$pi{lrjCN%h#yeg`v?`^uQqdFOI~L;b3LAOsz2FM z@Q6Oa(?r0n_6T9_CC_Yt_FSH}^2pphc1!GdaJXh6p7`e-`F3nRJFxIW%2TZNAWl<| z_e-Crh;fn8qkoUZsR3-YyHEr#V(G|Q7FPT95eLP^%Xvn+%Akg&GJ<@wXNd`Q2BR*H zH{9mSHas(m>@r^I%E*ywv?Ksaqlq{f`S$%zWBuJrAlnpH{=9x}i;2h|0C-|%<=Mr> z;;Zt>d5%reAx@0upJ2T=tSI^~!3mDlX&-=OzaJns{p8sLBFwt!Veo+#VA{_RTP;09 z$h|ln&Y&$TAh_Woe-sE06Z}$n<`@<_d%Ej5WweM#Tkrx%J8=OP0X8`xi;DZ*miRO+ zG& zubXk^^Bj+oTb63nua@M{L8Q2W;=2{_l;%0Tg- z#c#9Ie#3JdlLDnw@T$t2pIu_u3_^jf3Ig!SG9g>78@CQGFl)o~>Qk6`s>3&`8#0!f zJ(6b;^KY;x1VD`Vn9ylP$^N=h<4_5ejc|*&Tz59Vja*}F(97T@;vQ0{pxEWiDjfC_ zR^pqZ)ATJ|?o^8waK6f_>le#E-#H8Y$N@;I`_2=rIG98HTeK8f?Xb;Ex$}U5IJ#RE% z5Z>U9`x(J$7Q4(Cjs^5`)CW+Yz9C(T26xBOxO3Q;vP|?Y^#c_~4P2xabKW;AfV1Dv z{p|=POD$oo3;wg9R+54Sssd0I|I5mhnS=PJqM6=XzNI-!I5qaP(1&Nm>{{FyAR++U zU{Sb?O3rSijWD5qc<5lDRrE^m;X?pr$}j>=Q_JGaC+c{sKooaq-wq@y1M5I~7#;K~Op^`n=8jRhJxIB| zYpQ6KZb=&s|Wrj-`Fn&z1_eAB=_L zrVN@akvi4=VqzL&%m1qwt5Xw8EbfHMasmK51244JNCfzL@96-2 zc-C3jW1*#&B4E4%%8ad|v(1SL|?B*kn;8IrhW?FMsu4%NPV~hoh&Q>Pbm_6(G zbXK^MUgjSc8FzQX;*qi@-6JuQsR1AW_6UJ=wQOmBjdqob)hK>_eYQkETvlYKQLL+= z9om&=MqM>H`d%I0($En1k61Hl-;<%-N+7+vje@0IPn*)dTX49!xp~H+m)aE!mCDg% zrRb$z3?8$!G@|0>V~Ax-RCeB>RQT1}^>X6i-BX~VCDUP?K5Ha5ti~Ro9M2m(nIfMm zo1&6L=ptU*m(X^4qjn_>3uZmX<+L0KvZvwMs-OG)A26#Qkgjt2znyYNz{0wg-|-o8 zQB~=0DGzQoeK4AI7S*E+$zF-24JCW4nu7iM+k}r)F;>Y?uRFMR`o5M#Rn0{kDq9K2 zuHb@F`TkyBWjkUP4uX$f#MB&{;(>GkpUe1sG1i)8>zI^sVMF0TrvC-0H#>(Sxrj6e zuzYC?tGhzVU2M!#rEF#O&yA?Ax!KmJp(5&VK>6fuSNKJ;!bL6t!M}Wab6B6Jq#>V0u5us+^4W5Cg==)ws zOSc2s$uYN)V*oLEye}&o-)#P;$~1L@8Hf86*ky|-k&%IM0d#c%4DtSyQn=!%NK7Rh z{mspH#=6uzao0hm^nZ{F)<>XVRmQ0~-L)JiZH0j4v%?&V-$pW&0Ij?O%; z=0KQgaVrd}3M6Pu4ZshH@v_@V@x$!X@i8L}3A+H9lvvhL$8HJNAn9PW#K?0(dAJ~F zz?k5$;4l}_K=s@{$`N5wZctXHV_4kb0iK%LasLs*^SVl&8{iExFQ~O|hiud$QaB_k za&2FRN0fgJU^arh4%zbf#`&laGHr0C%L>!8{zjEAF|ulUEQ{sxA8Rf5 zzmO9x-Nl*2{^g~y{)AS)hS3(VlFAKYR<)RhZ*E$xjPz;%eV``GKJ%<*fDRBqRhYYp z(qPGg$lJ9Usx2+{Im6#NVVW2Regyt88KvSA0yKy7Ob?P~fi-!0DirMlkK*~iQ%YO* zb>o7LXEYD~0=lftiG1b%8-^mm0Q7gv!Y7=5G|yy&0GSsoC29UJn-^B-wh$c4`)j5( zIwvL*eL{eX9FP<80TBM4Vj_$7KW1{iCz$>lgi?9?-{~cAT*m*Vml{pAlYqVZx_C!S z^A7ajrvw09uD3LvYpmUFvg8!I^Ag;2lKKe3Ra9#a%U zw2d_8Z|v#5c(BNp68?k;U+=Zpx!*K4!;?kR))>^>`x!=QA?tORWmb&A|5eb+LDm~X zi*x1qz7Zy=CK#W-&Pq#v>?{3cg0^}%&5*U3OPmvlRGlDL@Ouze&n$HuKgAScMtvQs z1$xjGE}rxiZ>G*r`M9pn!4BR#&!n)M2HBl?)2B@YVg6J4_g8eT67C^rln#iz;`l{H zJeDY!qh`WV8bp3HNx+3-kP)aBx#0(j%NPK}Lk@$T6U0d)kXvCO4f6b4uQ~q2fufm9 zLJeRSh5;P$^}VW(RTTf-ar?OqB6I#tjJ-vEm}Z<7-8Sp*#?CwdZ9D=nnaZCw0aUcP z2+jTlI>mEJf^l^~!9ff>;urI0eu&}f+(=J|Xt!+dqo#*iyC$M~gCH3UfGeQng1rl! z5I~yDnMyRF>Jve-!VEL5dH@6nU#Jsv_!nN{VrWx z`4=)=$8q$}%P64yF=`GB)=GH*$(1nAqD;OPOCIP}gSp~^`JlKsP_~W`^atROU;)r_ zN2^~*iE0sv=BLDyCVVxn&JJhk6Snscd<8HfYDeu*A&KNkxrkjVrn3ZQCR{&*iI{0& zsr+|E%wah9S{jfT5is1T83Aa5z$OEcuGQYRCpvFRqGPaST{Nr#>B>E|g2N8>fmE{A zJ^W+su)bqU`n~xbtGFdf?jD?F05BmJUqnP?ivaf!inpLhCiW%|yU)xL)wxWr4=N!G z6xUM>M@JqND!4B0pHz^=Xgz)#yv+py6#_lv#O43;_S5;z!9~Zq4eIa8HIl6OT`1n< z467V4N!cn(sy=a^QsD6QPIu%f9Y-@ZZ+x<+0hJH{HzehLbAYNdPbZ7pReDa8?n< zrUrS1rnl=1Hw`OSezVue6L4+TADg^+e40ZA2rqt_6)2Yqn2)sx^}RgaKvn5Wp5^zp zq+>kSFCmLG_g5kJSMdQAjuTDf2y)Ca1apjW#RfW!cHavx0KU}`r!*E5RXM#t-fppA zxf2Gs?T3+rWR!qQSv;wYi*s{@4!6C#x32%>tKP{;qQO3B4G zRJ_kvH?IQl*=`*MCBWz9k_lL&-j>pjeOId&iG2!QML~LZd6aW*Whu+7j>IJ;)lGj2 zsv}^~4~y>ezbwgpEQK>A{cq>0zi+vfu@9J6O*gvSYg{k{dkj{QfTUbae3XZFq+zMP zAE`w!SPdLCf_MOjh3CEx6r@h3Zxge65 zSAeK`CrJYk{?h_s|KY_b8|KJyVCFjD^8&J?=j{U=f$91ROjouxuqKx#%e2CeYP_q~ zXq2VnmG)@zjIG~f6wSaSsR=EO4D^$wHPXFB>4>p|dd$h*TxHirRqUi#5rxSsXm4^D zk2wef((}AJ9RI%G_iRs&ivnU_bKZu&HCv-F(mWg?h#Ds1IHLs8NongQw5+# zR{GI9FQ^`Iv~xP^Lf4ySMxA0+T4oO%^%00!eQRP;G4*}{IIKmPq)QzNgx#~Q&krtr^iYVrTzeiVS6 z?v*>_TcbWhpYaVmLtiRMUuFfN;!5Txl-Ew$avQ)YFR129^qpv`;H8QL7Qp64@brUU z{|^ZBWgbH*P=}@@WB*EN&*&V`3Hq`qH=8~qEvX0ov%!Yy_k@8I&V-vZR!8%gnkS;} zVwyvdJuARTvDD_MzExW+)o}rU%F7c5Ppy3wVgj^S#g4tykBdjDpY%@u176&!fJ0q? zfm5u;(r*4Gu_?r-3-|(%i=&KwCz1KR^YQJRth2e^Iykh8F6_FymCZ_?$=>KeSqw9WzxdWsURY`^GnjW4w4{x4aiV+Fe8AwNd%(9m00@{Yk|4 z=YatHNtLZpEXbthoSyW%?zwxb z7ygW-@^fA#tN<8rhKqEjXqVv@c0w#02vn{+N+t2Cy@4nI%GCp0^m9TCr)OXG6KcHO z79&!L5SHJ}lh!%58QQoLv;VNLG7VtJd?KAwMFHyR$@!2_xpZ&seD5P&?W>}mTzeYc zI|oh|FXQ;=QprJc5NL*}A$6xKjAYUO&Ud~w0EXAY#v#w{UkeIV-boPrd_~MXk%Xna z+eSv8F^VP!!3mIi$AGE^bnnZ6&>Zkg?$E;M@%<&^r5kVv5OH@tzqTlZ^b;XC@FW`` zkuEQ-)A7GK&NrVSNnp=yJRp{}KT_tiDzB7!M@bO5NRZCe4ENKB7H0GgqNM@LYqISR zEbki@i49J$685#{Xa+~iZdok zzB!tp3UR>bgn*^IQJE0O{%rT#(4&~Q*ZpH-naZi7;zXt zOmhMvhPy7Cb2<67uBRu*k3g*u>UA``)RzfLhD*33PDQgjk<7z2ThAho=oP-zM)}O5 zbOV9{?UU#(ldkw7`!A|1(-&GP~*A7A7vEATx zyrQ?S#WvJ{VYK@Dcj+(xvZq`Apj7a^Qc+>j?37DkvdYsULPd%KhHrT)z%fJ0fjAi* zW3@&miCC$lCP`ysSA|4GV=vzsa&Snms4xf!^`gH1dVGv0E8xMIwcm{|_mbG<4SPG* zjGm`Kkg&@p!*-;J2Kr*Mg?`Sr$9Ns|C4tIA>#NMO zr}qy!bXy+v|FCX3X9rit^W1p_uJnVRxQLNb^7&bgO-4Ks6!oFn+1H$2nIY3<7GF^T zdmGL>4buiK4aL!^;c@G!Svd^|=>uSulc;l|BXp-%xRyFbq#k)ZQA*!esJ|EFT($9OhM zy0Z4|-7CFqpFR&r7%kQ?Yw}?c09-Emf`e+#k+9ME^rR-koL#rt`SACe4gV3UYJC}X z@&5fM!mBM;!|Cd!8Qe(tsWZ2KPbk%JTBZtc-61D+OF`H>G>+Dp-?KuCF9+Q}cD?MXTWySLXn4 zLbV`HH`I%nvouIhsrdfty$^|Ac6M+3s~J2F2%Ge%waY&@GG?R_*|KlfFz7tkwFu>G zVYqQCAA4xJ{kovb(H4`mmBvcfZ0GH(^Aj=be*3w)Ie+Q%I?WKShPL*!3ZubBll+?Y z%_ID6%ykoz;*ML-l6gS8kUEz68n;tM!2C_!1qHLc^4f@+YQani96o_;*TjNzk169> zjQ4~SzXUq`R+DK6;4d~ZI_+*)^e_Li`fKsd^|7|X4JN#2_5ESueZO{Ng3Bhj|1#x0>qkWzU({1iYy3>*F``#$5sCLB|19I(>Fx&pe49;oq95N2J;TbY=g zq@ki3E(Pf+0A~*4wh16e0QUR?MGB+$3yFz|8$Y0`nxk)`K~0D|>_{z27*sCKXKyto zTwLL2W%;dJP)X1Vrx}&#@DJ>>VcO=Og!}OSAl#cZ2}h%`Pa%^Rk(bQle*gM4#UEll zdhDs!S3F?xfPjXErtf9s$R8>2AM6uKPTiDL?I;te%chNwKVG+(ef$w*usRmY`4o4H zx*u*9liXDQWJuwk$^=m~v8JSn#*IWk7N_K!SAsa@`m($|XWtlW0Jd6cov#X%FXm5u z`qgF|jh(D?rV__;b8s{b6*XOdK?1DMi;zRkP_X_v^_h(kR zU1{~i3rAp_K|%BV-wFA&|0Lu?S-!gX1_S@*n#;t_PRYlQDPf7dv43fEh8q8aHg|$` z5tAO(%}L=jDcw?LO<1G$Xz|^UpRH3$v**RGt$la?tGar6Tg-+&Ta~N-Tah^1=Q^2A zflQV{Kt@_y;Wbw%;_m~dnqgkZ3PTy2Ax(p8ud^!@`s{LD&?PjF`kXi4M{gE?E5V5I zz>oHiUd>ALIaw@Cd{WbHkQGO*M_ed$YqZunek=cvl))?^p4h?izb6dnn!fB-CBv;W zCYjH2mA`l%F)9nBeOxYj(sghwqPICc{lc#9KT-r=!OS0PTUz~5{7$DLOFI-s?Y*d< z9(HzLD6}NjH*~WmX0LoMI!lb9=dgOYFKPTo+iaCYpW;el0!K?8*Sdkgwb1>bg85Ar zRCd=2%RauN9D89-el(g}W@P1Z{U}f5^^p);^L`YC7@i-`ba#+MTE=+&TgW8h!opih zmzE!omYQR9ZC!1hCTL}A95p_3p=_Pj{DE{<(wo%syocnan$b}rwsKX#YW}%jB)+~8 zPYz-!hgqYCGgtAJ?OcxM#Y@wZ5_(?L^s4U7%P*xwWrV&V?@7{+NE%5KPH@@v$L;Z$ zB)%QS)ijZ<$F4_Y>mpV)a;hRf9+I-V=+a-`$Jnk&M8?iL-96R0HSlkUs{K|*g3+m1 z;~ye)f9}cTGSv^?I7;T}mCeCu9kbi7BcC+pi1&yWXbLnE-s>R54`y-vFPXqVo3Rq$ zs(yg0^2lqH8Cbmf5NjU7;ljdq2+S??3+5)k^SE77OJ^wJb8bek67SnNbA05CywC|(>lP&g^DlCUpm zRC8EbRsTrjTR~_UTVk?RhVxfo;_1Mtv@WdCx5%FzmO5>(8wJE|<)d+#Ki+tb`ob(x zvK6i)`jYBx7W#jdS5__;P#XnFuUfb~e)i<{3QTh~HML8hKE{9la)Yebg*Dsrau(;EZ)vf`FfuZx;Qe8GRq+myBoWz`$RD*T zS3K-}Y|QstUY^3Jr?nYP_iDZS7DS9ur=?@=k#2gmk?)GD&%2djt+IA$sjqKnU9vg< z^uYu+?D_z{26<3pS|4#{XT-Z-EG;eKX%Eva%>z9$_0_AsS`+FK%55FWPU>f&Z-uYhQXT&AF?87S%ZAN%nja_d!=2OBwU5Vl?JmW6$xr?mCb*s^I@+nRYyW z?)^}D?ZxGHRP!#@TJQi#uAmYwkHpTbYxRFe&>Ty?^AiCUWws%|->NA2PGp4=enq)7 zd)B5?sjn~E``OeYqLN_ebib+dsN6O-RmBs-d@UBQsAGAzWu-1d+Bo3BQ8%w0?xe>w7Yq)kKPFWYeh)F> zu4uyDzMbJ%$XOL=XZ{qvt0>}zh~kip#b=g0E1Z$4qgUeHefaT+$4s66G$}ZkJ?!cj_^>?k?78Mm%DRRTOoa?2bogKWyoumYaa@wV+GrO&jqs zDam9h-1nBXuU|6RHB*XV7~9zo?8r`(QVL8fD%!}@yFI+_-7wE@bf_Mt3<%1NnN`{LQrPmHL#K=rR@R;oB$FHd}?G-8UE zCLQ6{@)%{iB|^?LT;`00K}!#;-R6OTfwc9QPfopoF4c6d!m0+7(e3kXZPHPetc_>| zlKcEZ#Po&NI=Axe9R1_cjw_5;KkTM^ye^pkc|2wmMH4b_*G7@XO(*r1!S$nK70paS zbUXF2H*3u5@<(!anYVe=ceNx>KiZr>qBPIfchviNBK+C*fi%r_ZxRlz%LKja3#aS& zVu@z-ptJ`qiZ4yNUU#sN$beHYu4=Qe4tNR>6BRygZ8;lUJU@9Zbke!Zx^|*$ZAkI= zNLO%m06)!Zz+;nWDyP^!x^=108d14;Qic{@+za32I~JO!k$f~+ytf;pvXuF#fN>C* zgdL_XTGBvGvK~dp6vi5C=C?>J11=C3ie}5s^6ouLJBQL-iPV0>rdlXIOo(lguwPK{ z1WzY64v+loU~LLk+Xztu z^v@r%QW=*am)ZB_5LZDM+lf?)Vl1zYBz5Qh7=RxnmxP#o_e#Rw`Wqc4d89dptfzP0 z1b)E-i$)IZ!C;X*^ATsE-$J3BV8^mji8hvsEhZEOQz#y&wI}*7=nMjhK0)?IalH+{ z(A~SCj{cW93140|PMWRFTj3E~uS7WuIbIE57r!azdNmp9)#q-cdlFFfe*h_t=rG|2 z#+{!FdsG=XsT+G@67vx!QG+4>2R(^G5D{qq1|8Z9ffJF+w0+ToVQS4Xu7x5ndMx4n z`{hbccXy$yL?9Tv*t}KUV>VZrXF!4W#WAPUh|V_A8C0gmW4dA*W&Uw!GJd@3lt@Lo z`-<1}4Y`D-55X~FrAi03Pfs-J*J~qrn%96S_V;oxWy6FJHUWsB(rs&E27Oex`$^CaMPG}Ng86$ix^B; z5@Yclq3%5{IyBPF$!2R@N>OnD3Sl8Dx?-D6cWAA3X zIZhv5Ex3>+?}x*E4X7=$Rssvew}q;n?b*Lr%1q|J;&svSP=k^vU~q8I{mr|ni@|_! zZ{yTiDyjSn{?;XmKDd?%v%F|R)>X;M}JD4+S7e|LQEpo;xg~VKltwg)4i;U409F3JaZ5ZfBdKm zwn41XCb2id{!?E%Tq;LgnHRrs@~Mzj`59wGo4d%uxID~cp~QHHl-OPrp==#xyi?@z zGWq=Z>CVw0Cz(n4MV;q4I&ZliJ}C89zG4>kfi|9p{M_iyD$6{H7unjA&k-q4JVXfE z7So-SZNP2mSE#rfx}I$%_>*5 z99ecin+Ah8x7tDNI)BK5%Tc35?`*xb+sr+QcSPq!)l-(P1jgSgF7~&aI`ljsaj@Ti z>!v~|GJM^CC`ElK>G15n%DaotUv9*C-DXb8pv*i%5QO&)jAR*s5`u{Y2u%!W)(dpM6*(;Ls8dn>jd&W^>C z_^L)>jE9bbK+>&zv~xzw(540RKBMQYfF8(*D;Kz;Yo+X1i3!?)T-K(joo)*k&+-Yhtd1u+YU^9kkSwQm^R zb!G_uOyK$%pgS^&v3o+ErB(WlDjBb7H_R1;mbSXwU0G1lXYN9^-5SIh-HuAVWm<&I zsko99o=1Ab=s;N$c*V&&y|Wr~=UX=gFnIy5N6fB+4W8rc;2EWL-zQagJe-eS z(;C!LBFGCU7Z=ORIClMJ!e>#LLov4QS{#7MN;4qmW@)mG(^{E)goT0lXy^}3xY1-0 zHX(o8KstvLuCfVYdbmv{&3Jns&WUE2in>M7w2>S%g}HCt@Q1jY zmI%HXEpx`GS)LuMBho!5WAPe`Ya;eQ z&m|Le%zx|qs2ouM!3z3T@Lu1mdWRh%b&V;qBl24-qc*}c@=vZ})V50KPA_?uTz;#? zn)-!Z>B7G-9va&7A=bW?KQxe8#ALDSG2~WFmJ;$nnnj>#$u-DB&4QY3j$gfMj7nRGiM!%Da)ID>gf@Q>0$+C2`p zF3u_0VwF4?|E&G*i9gZ0N|}g={&K4@g*#KL2w@51_XZ}(fdKx7wELH?@=w|)8%Yl1 z9FXR;luF)Fr&Ct@KK|?EJY&sh^V9x65zL({(tPn9z~ao#@y?=>7y62s&h=UFz)xp( zvVQ~b_`c`nF{-Y9;w8JnZ7c?rkh*~~LH_^F5-8ki$B4sipz`&7QLp2>tAX-hN_WSb zX|UoZ3C!HmWGg6PU749N3?K*d2I^2=xtrAw5ndR4u%F@Bp0F(#U_T#WsC-D2&ILvp z@Bd$nGWLH7@xG@X%O(YS-#u4fsD}r5$e)JH{xUI3mkz~>1d2-XLtx8L>Mz#Ja}CbjkwH(RKTSjg1W(ejYTa*|h~4l9ft-1CAE$!E_U#c7fH}BNqs4 z-aW=elC2qT%-30T?;fKgZ&`EN>Qolp!r}`8#O7%4i~rE1@}6_P;%}f4{qf_)G@}?> zwCSi_jqcX%TkYSd(*hdlcs*@xr(LMet@X{i$AT9~J5+t$-DOvTJ%siYv6BfC@lj}3 zDFB;wYu)z&vejx)ivq9)!{VhAxTi} zypcLVjoF{Pc+ZZovW)thX);qoaR)nt&Oa_~a^ZPf z@6F<)nSQj7+VRdHSaEuS@f5g>30@`g0-zr4qPG9DDDzR$k8w%w%1cz0q%&G)iaLt@ zyN-fSWh|cj6L14Ps+YyHXQ8#phVO=WTmD^^@%V>F__?~Xj<@5-CnvYg#(rqU_|~^M zbpjq~ICaOfG^bxF#MSYbsuOT#^W@a-SGQPkQ-yDEy{5{`P( z`AzD_j}Vxg0kw{FfqJw_oaha6$_I)**1i2V5;wI0{1wQ+5`3h%c0x7yPRVLQ?0F2;sP zbB@S<Rd5*l1gX>*^uTvDp)L_3}tA|T)gpYCB zsI1aXNpk28qxa|QbZ!%Oz2TGwbr7JGfNyRBR5L{V>eugJF_bYr^R zjps;n9dX{R$M_QQYpS-g@|$<4x8A+Upx1pqJjtU$0s89!ybmW*gwBOiy~^~(b9z$! zn}HUa?&7NYJ2F0VOz&YSXPVuY=s#EsU8+C=DjD<2%gueD@X2c5;5%IzHz~rh>^Y)!7AbmM>ZoIZ^iQ>AA?8?z0iLqh11tNvk}ZEp;9Ldh4zf)RrWRQmy5$)z(9iFAM?5sl|w^Ah2P%_+)UrPpP@|_9(^k# z-lQI0vs5;*xmXh|Z?9rre0v6I@|T~=qhcs5bK)skpf z=2Cm*o6lxv$A{*@YuM9MWLb$YtRu`oi_2U z3KyitYtM4r-J@y0&|m20Oe6Tl<8*VKoECf4nNHd{u!>}6Zfb_lt)Gug8cA>mTaHtUX6z(OOIL^1=5D9GHWh`!^t5j+)d4cn*ugP9x+b!%oEUN0=5PnObgu(E*(ns*T^b-5d-sWV!hyGTCGRIfJ zY`u}|5<3uby#SGj!M0aoqCvR%SjVF@w{I@H!M5+C{5t`i4YNB_$Nv}~igTJw7sr$j zPK*;2_4x(&Y&vQ*lsgZNtMVUvn#nGtK1R%=|GZq9tYm3yIAQo7tOm@)lq+AB$SyQF zAvO#`<&!mTe9PAmmb4xnxzf)UxV>LHxieN_NnJW9r>8j6hW|@~m;M@c&BW3k^;x+q zcGDGIPcVG_r$WqoCZcTUR5;YK4KXLkbI(t{oU{6#qBU?h%xV4fseE0SB5OMr;q0$p zp9fd`%EvVWyBS9=R##|2mg#gLLKR7xz*6&cGnxhfT%da!I0|t%5 z70qZPthkO01B$et;e9JRpyZ5_d2qX{{rXBJ+moT8A!o1v)Hj$DvnYwvPENQM(`R*i z-tIOZ#BCYeq1sDXOQJ(?#u=ZiOKVwchW zT_;yHCME{%Ea0~X4ez6LB~G~_wCk74ek5e?R7OrtyPb)GFA3Z?=cLn>DqOZqOEkfs zE2(SOwI(?DTzes00m+v_);o{bVj>>qsjyJ@z4(0Hoj>^2V$DCf08&Uy9}kUQZwlcI z#TT;(sWe4#39hk2n->Yad)i-jujrK~Yeh%3&(B?SqH${EFpfMu+Ap&Dl{a9KVID1W zoiZ(;6YdW->f`;bCr^OuDZp8&ngh%+ojOmQPN6K#+;4tR%i3`j9xi6j6x_11wr+E= z*8v;pjH0ZDC6qELEn#sv7BksPQSksuY`^#18@oWIP|BWTuyW#m%5!<&y|%~9Svy$3 zo*ITkMY{P_`Qqb4LmYD4G3|H8O%LX_Toztjs7g$i?lc$8R;jd|^*4{3=z9SU ztQdRZO%kh|#ve4t*7`~`ZzXlXsMmR&PMqoUIQq_A{zfL<&e?enTtV9;GD#;FobQId z_|^RwSF{O?{(PD{RA;|?kY+}FfKdP?X99|Cv$Cs}JS1*mc_7E5y_j_cOiJy9DDT*Q zQ?Uj=+xJiu-`p(Rg&nfE{HB;^Pq(J8S;xnoL}9AFz$blGWC1m!rR=BVopv7wmLVcc zpfem3=h3aOdmys7xFd|`4BS8qaJS?yZVWA>HOwMMsS*xr;{ zAhbw!`f}B{aF53Wxa+S`rPlD0>E_3DEtm+<@KS{oI0@~+GeT(htwuwtmvqjRt~q2Z z9Dll-W|$}VTrl(y*Ozj~d65h2dtX}RFvkhKX^;pkJjQNEm!^CMmbFNf-|TebQyO*d z&0D3X#J3-6AO_dUv0jlDx+$2B(TLNHSq+e{KU6u^tN&PDEBIMgfC~Rg16BRr*FlA= za_fl7eW2~JCtTFOy?6&p^bWQYw*RvV``GFHrd3URV&NwUlkZdr(%nPc;BWbx>HARD zo_wF6x4Hqp6cTZEt5n*f{fE@nJ<8LJJ4@5T#vj~nhq{!Tl8QQ;+2WCqj9fPgWLk=s z?4oSdFL!#B;zup&4C~4batpC(mdE$!d-vp(@JP=12zpQXUe*69`R@AN;lzHTKWl3S zSG$CheoJ-yL_mh)K{*yaF(}0GRk~xs?V?z&F&33~wd_26(r0?u*8{9$Ry90Y6?jPz zn>%s6823ZScFSSI|NTCN^_!<@plk)}HI@mOBTpx5bInKKFQ#>lr{J`E(Sg*TiGVKS zV>yHndmXm>j{BoLa#!nOonMGolg_}WO|O|I~ST`o116$kkKnXWUJHN(UGi==F@!FLB!L_ zXZfhZjk~@d@x9I|zc#qk&PG`}rh+=T!YQI;h+19!q`-7v=gr3Vt}eZkV@&K0wa*QE z=x2#tnM#ktCby=mR#+KA;^rR^VE5!QBGlbVRbOn%O-tQa`N*^-M(oDBq(Eg#s`H{* z=;yqvvvsXaz|?g3Q@nGUgy&T&L)Z!Ni5sZAx%hcS-TA|XJJkjeDT+S>kn{=UANTbH zc26!KV(46Y%5Fk#m8Tx-9R~bfv}ZA7ZrNUdy29No#tFeEfrD&*MIqiiwB__tbBy?; z_tFGg8b324$v1oieeQ*Q3FL(n8urJCM zy@AF1;UH#~&r}`}I9i~i{qrr!GJI#Bo09fxfn4DM*=nri%*0zm+PaBem4NxuAVyJz#b(B#Sv z^zQ)k`>Zct{rW9y>KDk~s6YbS3GQ#gr{@(t*Q!y0|-q~GU>Yc?a|KbY?}iXkJ*kE7qbRe+A=)u zjK@B)w0zi^wsHW=?YwJ%{4T#Vf(gV_U5kg)+=1DxSB-LJJE5y10&?C{$ijjwUcsCb8)5PmU!1*jCE6pKUqU z#{+>yfIMS4y%hvZbn((@g8r#dLd3Q@H!~9$4iWvT# z_`DVQ*@N=2I}=?-=nN1kdXOea!kpCVw|i!?2`@&_TJY8k)T^O3An!!(GX7+eK(gaG zv}&j|(Tu*X)s)Cv>$WRzdffKSN+WtpbN7oR>|u@2BjJmr4{oI0QWsCeafouRDnywJ zgru|7>#*zH+bp^6P z`s+V4KAG(9;Br@Kg8_fL+Qe)L9#>oy_h;G3^n^lE9o-t1eBoPBGGLuMY$d==oY<<7 zL1H*#x|8y+Ep8#fDkzSqwOo+eJvCI&J2n|lAlavuet>#GBkBK?h;vqcL!>SXeAkvY z%F$cN^3d_>4D^2@ z2_OD_8GR5HN%-K6v`jO|U?Eel;euI|gBxMWB1kP?-@&FmUjv%IRkF*wJk*1~@mDd1V=7bgZT}lrX;*GumL)vA3eW0Gup1=oR63&$k zp)K@%%!E$mU5w?OiOlzZQ|hhx2{Y?_@5p*A5jTRAkc^RjFvCht{DH)g%mJ-d2rlCT zIU}^~@H7=jxUqJ0=vE(4y<(R+;PsTO7%ltCS~%t1&vR;MTnR<+!v!F zPi1Rz3eBqv*60CgEv~4btXC2mSlOv%jy8e*8PK6(W#@~oAPFnF71o|i{7lyd@SvOA za2q);2e&k}m$AYOZ5M`h<#a{wluPcpo10)#7Y#MFe6@}KZ}c7dOE}Srz4UMqf^2Ag z&*;z6jm=)nY6+%jgFCgawY0PVE)14P8@5`mtgWe`QjmTDK8sl1B*sqloIF){kt48l ze~0k7qwrJ15+qJfpMH~PMVD6xtZtucq~HxrfSx%HI{7Lr@>|u~d-^s`kH-Fy2+_@d zf#py;91@{PtJ}?kMLwoQOW)}UxGe<0R>BQzi~){$Q+d(bg6&*S(=w%0{ru~R>p@=o z;;H+4W2sph~(SZ2fzYeVBQm9pPuXrOtS)$THRS-SbK z^4FGBt?w07Rk^%YZl&>JWo5mu!z4=TxfxA%)V2M4*OJKK+_KGX z$S;&2>8SNwO_FaZdBaPqd(fx`4#$iVLHQeeWjckVX!x^E&VXzmYm^KZ8*w6RX|!2tTUk*rmfz)aX9Cz2CmnDFh>9N3Amm4StqbEo>?e zFXFtzy@fK+(9_e?)P#TmWD|qEy}kK$Czl;wBMb>r1gdZZppskf@Fd%a=^R zSwA)|btAnBmiMsW$$xPSm;z+HYvD?;f9pV&_yRQ9f-JI=3o_ZH6**WSW&6I*+j)4H zU)^_~aG7d)`sCKz=dKDYQBTLpp}*L!y}m2NkuGeAQGeC4QUbnPr%!d(n!%1f+i6g^@=A)ll}ywVP$@2lWMPz=;a zrj(ITk51Ovkj>Pp_ep>MyYWPu@Lmpftr6jg4E;xrW?xxC2oZ#R(kbLPxDPK*C9zJ}yXH_#-Bi1Q z@Y2^>zb>ExX4x{9ydPhy3iJLaMcCx>aw7KyTk9h9o*4Fbz4zMa(AFo=F#hwaW zizO6m*AiC~ET(M>b)R@l+kZD++VhgUML%WdRKC2?(?+H2Vi58b6u?Bc)KE=VaydRI zArTvN7>*K3>e7l%Upat^)6bu#HM(8%c7A=2DOO0GxUwR z!uNC=QmT9>XJ>xfDmO^`&YjYTxI@Aig>yx~qi`PUC68O-Nyw6oo!vEGn~~0RxY$0P zQ@Hq>YI!O-kB2c#Im*j4b0tHm!Ri9WSWn=+Y>L97y+G|ZhP!-5+U7lE3Wu%*I%AHj z|FuoVjXjNcTD+vYRzN9VI;Qu9>vO)^Nfy(f0*?$be0en7QRImh{_~v{A$bvk_DJ;xvG{P9^)KA4Np1P^=gmy zHfBL{U{w@zpou!Xc-F7sHFP359py98{Z2p!E*34WlH9yBf#)T3z3*|dz7@X_&MZSA zlio!x!N4Gw;2<{nj*9MV<2B7E+lN8VFSQFqU$?Kjxy}{($p`zLJ^R_@Xq_v(8HYh@ z!??4@tiJ|$s+YK6nq>pNSf`!q*-K1_mZ3^V{3@HfQYS zT6cCFcJHq%cNNIX&Q^=Jhi-p4axbP?vK_t8`79(VgVmt^S$9#2Q!NLLqZviXiQ+Tm z*DP$`$q(Y3Yrd^!<_iDp0icQLmuASi$ZU;?q3-(hN)^^hdS4%NZB^Mfe3Uq>szP2T z+V};fr9a=Z&FKcMGS9>o)Dy!WA{-xH?AxIO{>iR}wEzPwEG@-oBeO?2SvFRGs|JGSc;eL9XnZ zqDpuioIY-d|LsaFdBgB881LbALdCG*vaVG`5Q*oNiNjA~HpFC-GAn z2=YZ-Eq0^dqQtQZ!qWnEAz@+I8=)2!T&aL?Fv}4vye~t%r%1hEVN}T6}xTrfgN5O>VPv zRy#E93T>}Zhi$$g-Q2l%wS?DDs3Cjvk_TlQFE2J#;eU)C=W9IcroPKrtLHuSdDhPt z5SYI~CE^_^{mri*>Ghw=ySyLIGAR(#bDq2DjJIgkXd50qz7FC(ecD++udsfLCelvhzRuTj>r{PIBz0UNi6QW= z-=pSZ*lJdVLXu5&_$Gg=yM515f5krF?(VMb2rtun(r5dkIGehdIrE!k0Uw{K>t~re zt`@E+!*erQXl`(J!i~P0(^55&dMoW}ld$Tcqk@wRarjjNyWZg2<2lEl>UW%fURrrF ze^zB!ll-u>s7hLZ9P_Z-EM`0viuEfSdlps15 z#rasagDC?xF|#Lq6J3~SNCN2%#`a&olj3eBuCj&j{S0wp zn22p9Cd1`~+@Nmq*4BgPn2+8IZM<*b05(47#^$Z|k&%(h2mP>%r(5F9_(LmKset~Z z+&xZbjXKE-hlDK%huju4*z+ORj4lDA$(4idCxDttuPKnCySut`Lm(M7VuE)`XAlBH z6OET(00hPet2?e@_-Myxi`()320?L>@?iN_nWx5bDbq!l&E3pqeaE##(;9M0mH+=x zQt*7BWhM#kZJ5}anZ~@&oG~7~olS*Ns2o42d{BCUEi64z+-sM^XPp z&gcFca?aY-y+qA;rd}OJ4WU`24atV0d@Y2>XR0)F1sZCg^OK_uzI@w8Vs~5DBSPok#{1tNs zTx;IDX~#eW^A)d<`+K+la>^E<9=W-<>NKPSJWlrJt%RWY3zut`Dg7&;nZTD6QLckV7Im_xa!-AdN^Abjs7KW6Oldvb%H*O^>L6e+B8b z@K4)l2ncszP~FkcQ{q)DPcvzu_vgi~Qc<A~`UY{!HsmUricfsd4BTK$O4L>paOGGj75h z_7~Y_8;%ryGB=P@c&|o<1!@fcisG8;*Iq=>YmStTTiu3wgh*11hmnNbqZC_!_3x`u zB5$PDlY^vawT3iVoy-mor|l2zDt3!Rg5MOOzOM;&&fAG+?h1#nTnoL-=5Jt3a{g(( zFG=&p!!yWK+v{2J=kQ%b>_MQQ*gbT=%XY!Y3&RdNG?k=KS@97=R}G@fbdcoBkapAZ zni1k8bHU#}ybrxsg@J?v2#N2#IuQGIFDA^%#r)BjZ}lTY3LJzGZMHRGRxlO7paoAE zs5m>V0XpKC<6dPrk`Ga-E`CZUV8 z{;9qD#K2}V(((Ac>zSCmK9Tx(=U}_v&1*VGviJcVeVaLWZIu7A6ijFp+ZPcEQj<=zwsRJ_`J^Pt= z_JMM{7D+2M!tY(0UGuer|22MxRR-R;mTbMbRH@7pYM&tpe`6R>`nvVR6T_!Ig#0|< zNeUNYTo6RbKaUIPH;5Q~QE=(3UsfPOO98tvay}4s)<`j{>q_ry_`P@Wn+{@rP4_pL zwCIYfH_z)1ztU-)u)i?S-z^`Xyzb;$_)q}`QY2|3slIoP;!H%IPMq=u z?hKJ70TT@GJ=onvHJfK^!SgW*y$t@vq4+z}fU1ai3&68yjeiP%rM(Bgjcm?mhBLyV zrf{|{4g3CVH6Ee@@Qb&7!JR`4gcptP{%VHk=`^-IRS5MkRgo=_B9eO(Q|j-!6N9c} zjA+j<>7u-NO%^sj-7H%zS>99I;QJ@}A){haG4jeC_rB^XZ_9nHd*{m&FQxasNYFMJ z47fGM!!{N87dLEiesT0#@b_bK7tZ-GF598Df4)x5CUf{g5l{1$bcKJ!X@@@1DxNssvd#9!g!!a-gO6TV%mPq$tmGA-u2% zqvsV&Fa7h<)DhR))^F8PEa26r!$X)-yX>a^VMDl!=y(Jp2&~ZIn@oROvj6!(BKB(C z-ya)kf`B1~o&edn^s?$IY}^oM-Dkjrhnh?>7VdtT3NbbNaV5SI2l;^8pd(kv9RJmh z5*{_|@4<4pn15oBe;%2+Iai=gpBq@3=+G5}Wm&5oX5>hATXOS$h^+F$TDj(wudDe1W$qK)dV)in@W00q@EEbI#ieWx%%Wt81+HhC>`{4r{e_U+#dh)y zLBgnm^ zI8sY0RLq~0+*E8n{w9WR3nBT5r`??7%J-lua=*vzlQQ9{!8DI#sK35ecJ!$KB3ER7 zE%3!3P`|}9_Q*ra?`%-@2Ro@){N3OGKr4QT;0||~ygA3agV~}PiyrRMXY=1wHg=8g zQfdu}_0^8PVsZbBZIn>MRITagq&^c9hR^IuOD_yBran>w-T3}2U*ry6KC{|bNSHl5 z$!|)(Ss}y%53_&1vB<0NY5El`;A;cZHkIIe5Cc{{NJJ3YX8i9+Ka_(0&K$`%W`vJI zY#0n%Q|Bp>nUJXvl_{bIeC<@H?E49?5z(1{JL2D^fOd_7|0)s*lg@kFaL)!&fL?`g zECo1IQDf)^KDiqOLJI);I~>m-N%bMfcC-KMY*4AZDWHw<3P5v&c6`wWN0byU9=0$H z_<=8K0EDe;w0Fv{lX<7W62pb*i{UR{t!SkUuoPgBL;iaWCifO$^zYOkN= znwMr#fkEql#(DQ3&~)}ui+bQPGboX)O~%P&t%9?+y`;>rk|?Ty&pF`F@@RMS%nvn3 z>aKFnNqpO?tHUE$(-W?!i}^EdvHo2(Ok-I4TBJ&4$Ft%oUCY=vt3=eMGlsQg$gqG4 z(YGYKDYJh)M~X^9S6!~pmn3`@7&1>llwYK`?rVHER+_KhKL5Q{R9-80{)ChoHMVsV zjrpPDFU4>Jny+y=Bx4je&%g0drT0KD@1m<_+Xk2%=vS1&pCIx{h^iwp0h=6EFh6F{ z+fo!Kk_;xzO1?J*V4<#rp?K>@16_;HKmjIM#8U*jhFsx z7w{|eu0hMO4O#^=%_{%pN=Q~A~8}R@y}`4`l%-aW+VsHuT3S_ z^6KFL;ppK;khADA&ea1WoCikA64FL$xW9yb1be0ELl}sK3w#WVnA41lZemdcPrb+g zpfG7AcINMdu>GI_mu?UL8L3TH-r=)4gKWT{!KoXby+hA)BII6b3K>EI9T^bWhXeka z@?3~0Gt3F^N=6za?ApI($3LcL$`q;?T6_gU#mHAWjn63)2cac|Z(+TfK>qApTIWH>Us!?Kz!dewBP-;y;2h*10EcqQN&b_ei83lZ# z0R4hYlOe~HS95RkmFBC~4tk(cueN_pY4WdAC3)QgbSoNsR?h%yw z|5SM>BYQj*o4r%baV|8 z{r0;{Q$&x=<32MkaX%du>$~9z4TvdxrbK+Ll)b>bW@*X1K;8bX35CRs2$zkS2e;#) zrxb8e{S63jr%zToSbBNY4OrY_WyPlh;egB92=96n*;&8W1Y@L$%rP96iRBlaqfK5l z9#SPo8y)j9BO4(%QKiucX18+Z^Vsf*gO_J zTju3EG-5kNJ&pAgdzY?q95q_@%#Q?+*L7wf*^O5*YIOsk2;5a@E{YfPxwu#*SJmre z|H&PaIe;6XKqK^n;w#beNt_~9K>FE?ybrG|@661K`6asV-B@-$iW2DYs+6elL^$u+ z&=?vR-1}{(p{4cw(X$dwu~%AUPwyy$jds;Ib!8Y-jRq}v?|hKeV~0Ef(bo=Q;r55& zuVvKszlTf44}?BH-jTHBp_!VwK;3uyTCAC?mam(emnWga1jk^YX}lMb zshU$VYOON}Zi4?`dso)gM3mZ-{z%fptsYT?*v^#jt0PIxo2B{L4MCW^QC;C*LXKaMlpIv_o4}>Q|JaapmN~o~@L* zkR75*mmi42!ktPOu)Jjte5BSDi^5>YyD?S9*+ zpmjPrIuJxRzU25H3(WESpq<0ab2id}i!=aL9qZZtag<1vh3=WE^})6!N_XXhGE3KQ ziTi4;vrinBr%IUU#PL}!&k-wtC!u#)q3%5kt#>g}ZM(VGyi4_pU1lVHT9x5w)CBtE zz^8g-&eTw~Yj1BSFvKINwaS59bwk zomY(&1laF0OTY>%VS}DoyB2G`vu)9~W8fN-*^S5VI~rL9ec5frNl57XJ)nD*o^XCl zfamGTh>L|UY#6jZI!g@`eQ5RJrcRB=4HI4&v)l9=;W8$7qMuf|Kp;Yf3aTLwTlbOF zQLxZ`eP2l!*(9Jjvd!RPq`7--=oWKd^URsYCl0;?>TxodoT&5 zGHp9UOaeG)F!8+v`}_Iy+YtfJk|zc7j!s)lEKjcU(x=g2+AIM|e9${5!y!O(PRK~V zr$C~DJm1J=T{mZE0TPrnJ(*N0h0>N1&64CLB^SzzTK!eUe(7xwU_#3D_v3OB0rFsd zb(1~-K>MD;zgH`Xd*Sd*p|DB8f@=cph$?&F8IMp|MNzOu`}^O}qlNQ091cm~FINv& zhahZ48KWa08UZTX@c*O`t}gg)@X3?XM*a5pd~dj29#cZ4^7YwQn~{pf_af@kd>l4Vj(B*K zRlC@HY;SLjvScs-&4&zh#Ttnvf#TD3&&|arfD8IhI5;$LtN7J9D zEAQayQ2ujPKl*{_zn?}SxTL@yA|Bp!^KKRN`3i&$m?4mqRVo!`gX_!}I18JFZ0~rG z%=P8VYQ6Z0OR(rDE{6+=RP@2m&Gn}xmH`5ZD5*M*Kopmh!f^N7&RIIZjzkzgt{?uZd;+Yv)HVP;O` zqUde>V^T$<0k&s2;VxKA`HWwnkiCaNsf8Ne_nc@H$zv zS5AZN&XA^Q7CoD&Nk^0r_Yis%H&I^G0Y8@_2oS4rdRhwo>{`-hey86|Q6UPTT*S zKEf2|KmzB`c}RWaRjz_t zNf6y>Vui)HJ@E9Tpm;kO7$q=>z-^Hbnuzo5mSv8BN8GGd1t!1Nr4`O1q_YuT=ruS) z!rU?Nwd4Mz2Xt&82sOEz>UM&WuQXFdAhehtDVjhmC3)OMy8z*^1Q!SG2cC!(xlS3L vw}Jd&;jtG_{hnIELo&ni|J~Oq=vHf#9kJM|vVM3AGeYdxYG<2i<9^{Eojd6` literal 36292 zcmdSBbyOVPyCvMXCrAjc2@ss%?h@SH-GXc5A$V|if&`b~4#B071POu0HCW?rQ@r=y z`Q1BrX3aN$eyr82=%TyMsUy#`pS^bxsiGu$V4w*~ z#@2f`RZ%MXH%CS4V?+x)V^jpERJ;@%fk6tjz0@m=FTwDy-9IzIkG8qstBUD5uMp*2 zk%@FXW%y1yJ-Tf#yG`CW!%;?jw(NoUnyIz@fR4lx5xyEjTzMCrm)d)B?vAiB+l~XO z>i2D1nY^h+#YwQe-)d>;aJ``qb_abuu>3tHr>whH2vK+$_ta~xt@GVrm3>8L?QbCX zKz^c|={9j=f7~ztA00$We@pV>DeTGZC`yAj`20`l@W*0hR}CMnbjrkxrYSV?G|2g@ zw=x5?k^$<+Pbv3g-(;*xY9t+9M`^`Ax-0OX1-{^7XX8XPwmuo$mye^8ehQqlmNM?# z^a1^G15w^}`K+aY++pydx4*QN9a?LW{XqP%V-CcOC!f+3EG*O@_`jma@8C7RqPc&< zVM9p$@tPMzunOlVhW!V0O^N &Q-E)db}oNXmqQ7qwiJ*#@Ne6+as}XH~ETzWOtC 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

DK2i}?e~dpU5E+x_UBSPetoNL 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, uw, sizeof(uw)); memcpy(&config->tx_uw[config->nuwbits - sizeof(uw)], uw, sizeof(uw)); config->data_mode = "streaming"; - config->amp_scale = 2.5 * 300E3; - config->clip_gain1 = 1.2; + config->amp_scale = 2.0 * 300E3; + config->clip_gain1 = 2.0; config->clip_gain2 = 1.0; config->rx_bpf_en = true; } else {