""" Python interface to the C-language codec2 library. """ import ctypes import glob import os import sys from enum import Enum from threading import Lock from freedata_server import codec2_filter_coeff import numpy as np import structlog log = structlog.get_logger("codec2") # Enum for codec2 modes class FREEDV_MODE(Enum): """ Enumeration for codec2 modes and names """ signalling = 19 signalling_ack = 20 datac0 = 14 datac1 = 10 datac3 = 12 datac4 = 18 datac13 = 19 datac14 = 20 data_ofdm_200 = 21200 data_ofdm_250 = 21250 data_ofdm_500 = 21500 data_ofdm_1700 = 211700 data_ofdm_2438 = 2124381 data_vhf_1 = 201 # data_qam_2438 = 2124382 # qam16c2 = 22 class FREEDV_MODE_USED_SLOTS(Enum): """ Enumeration for codec2 used slots """ sig0 = [False, False, True, False, False] sig1 = [False, False, True, False, False] datac0 = [False, False, True, False, False] datac1 = [False, True, True, True, False] datac3 = [False, False, True, False, False] datac4 = [False, False, True, False, False] datac13 = [False, False, True, False, False] datac14 = [False, False, True, False, False] data_ofdm_200 = [False, False, True, False, False] data_ofdm_250 = [False, False, True, False, False] data_ofdm_500 = [False, False, True, False, False] data_ofdm_1700 = [False, True, True, True, False] data_ofdm_2438 = [True, True, True, True, True] data_qam_2438 = [True, True, True, True, True] qam16c2 = [True, True, True, True, True] # Function for returning the mode value def freedv_get_mode_value_by_name(mode: str) -> int: """ Get the codec2 mode by entering its string Args: mode: String representation of the codec2 mode. Returns: int """ return FREEDV_MODE[mode.lower()].value # Function for returning the mode name def freedv_get_mode_name_by_value(mode: int) -> str: """ Get the codec2 mode name as string Args: mode: Integer value of the codec2 mode. Returns: string """ return FREEDV_MODE(mode).name # Determine the base directory to search for the codec2 shared library. # # In normal (non-frozen) execution this is simply the directory containing # this script, and that's where "lib/codec2/*" lives relative to # freedata_server/codec2.py. # # When compiled by Nuitka into a standalone binary however, data files added # via --include-data-dir/--include-data-files (e.g. "lib=lib") are placed # relative to the *distribution* directory (next to the produced .exe), not # relative to this module's own (nested) package directory. Using # os.path.dirname(__file__) in that case points at "/freedata_server" # while the actual DLL ends up at "/lib/codec2/libcodec2.dll" - a # sibling directory, not a child - so the glob below never finds it. # # Nuitka exposes the correct directory via the compiled-only global # `__compiled__.containing_dir`, which always points at the distribution # directory regardless of platform or nesting. See: # https://nuitka.net/user-documentation/common-issue-solutions.html#standalone-finding-files try: script_dir = __compiled__.containing_dir # type: ignore[name-defined] except NameError: script_dir = os.path.dirname(os.path.abspath(__file__)) # Use script_dir to construct the paths for file search if sys.platform == "linux": files = glob.glob(os.path.join(script_dir, "**", "*libcodec2*"), recursive=True) elif sys.platform == "darwin": files = glob.glob(os.path.join(script_dir, "**", "*libcodec2*.dylib"), recursive=True) elif sys.platform in ["win32", "win64"]: files = glob.glob(os.path.join(script_dir, "**", "*libcodec2*.dll"), recursive=True) else: files = [] api = None for file in files: try: api = ctypes.CDLL(file) log.info("[C2 ] Libcodec2 loaded", path=file) break except OSError as _: pass # log.info("[C2 ] Error: Libcodec2 found but not loaded", path=file, e=err) # Quit module if codec2 cant be loaded if api is None or "api" not in locals(): log.critical("[C2 ] Error: Libcodec2 not loaded - Exiting") sys.exit(1) # log.info("[C2 ] Libcodec2 loaded...", path=file) # ctypes function init # api.freedv_set_tuning_range.restype = ctypes.c_int # api.freedv_set_tuning_range.argype = [ctypes.c_void_p, ctypes.c_float, ctypes.c_float] api.freedv_open.argype = [ctypes.c_int] # type: ignore api.freedv_open.restype = ctypes.c_void_p api.freedv_set_sync.argype = [ctypes.c_void_p, ctypes.c_int] # type: ignore api.freedv_set_sync.restype = ctypes.c_void_p api.freedv_open_advanced.argtype = [ctypes.c_int, ctypes.c_void_p] # type: ignore api.freedv_open_advanced.restype = ctypes.c_void_p api.freedv_get_bits_per_modem_frame.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_bits_per_modem_frame.restype = ctypes.c_int api.freedv_get_modem_extended_stats.argtype = [ctypes.c_void_p, ctypes.c_void_p] api.freedv_get_modem_extended_stats.restype = ctypes.c_int api.freedv_nin.argtype = [ctypes.c_void_p] # type: ignore api.freedv_nin.restype = ctypes.c_int api.freedv_rawdatarx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore api.freedv_rawdatarx.restype = ctypes.c_int api.freedv_rawdatatx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore api.freedv_rawdatatx.restype = ctypes.c_int api.freedv_rawdatapostambletx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore api.freedv_rawdatapostambletx.restype = ctypes.c_int api.freedv_rawdatapreambletx.argtype = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p] # type: ignore api.freedv_rawdatapreambletx.restype = ctypes.c_int api.freedv_get_n_max_modem_samples.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_n_max_modem_samples.restype = ctypes.c_int api.freedv_set_frames_per_burst.argtype = [ctypes.c_void_p, ctypes.c_int] # type: ignore api.freedv_set_frames_per_burst.restype = ctypes.c_void_p api.freedv_get_rx_status.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_rx_status.restype = ctypes.c_int api.freedv_get_modem_stats.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] # type: ignore api.freedv_get_modem_stats.restype = ctypes.c_int api.freedv_get_n_tx_postamble_modem_samples.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_n_tx_postamble_modem_samples.restype = ctypes.c_int api.freedv_get_n_tx_preamble_modem_samples.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_n_tx_preamble_modem_samples.restype = ctypes.c_int api.freedv_get_n_tx_modem_samples.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_n_tx_modem_samples.restype = ctypes.c_int api.freedv_get_n_max_modem_samples.argtype = [ctypes.c_void_p] # type: ignore api.freedv_get_n_max_modem_samples.restype = ctypes.c_int api.freedv_ofdm_print_info.argtype = [ctypes.c_void_p] # type: ignore api.freedv_ofdm_print_info.restype = ctypes.c_void_p api.FREEDV_FS_8000 = 8000 # type: ignore # ------- MODEM STATS STRUCTURES MODEM_STATS_NC_MAX = 50 + 1 * 2 MODEM_STATS_NR_MAX = 320 * 2 MODEM_STATS_ET_MAX = 8 MODEM_STATS_EYE_IND_MAX = 160 MODEM_STATS_NSPEC = 512 MODEM_STATS_MAX_F_HZ = 4000 MODEM_STATS_MAX_F_EST = 4 class MODEMSTATS(ctypes.Structure): """Modem statistics structure""" _fields_ = [ ("Nc", ctypes.c_int), ("snr_est", ctypes.c_float), ("rx_symbols", (ctypes.c_float * MODEM_STATS_NR_MAX) * MODEM_STATS_NC_MAX), ("nr", ctypes.c_int), ("sync", ctypes.c_int), ("foff", ctypes.c_float), ("rx_timing", ctypes.c_float), ("clock_offset", ctypes.c_float), ("sync_metric", ctypes.c_float), ("pre", ctypes.c_int), ("post", ctypes.c_int), ("uw_fails", ctypes.c_int), ("rx_eye", (ctypes.c_float * MODEM_STATS_ET_MAX) * MODEM_STATS_EYE_IND_MAX), ("neyetr", ctypes.c_int), # How many eye traces are plotted ("neyesamp", ctypes.c_int), # How many samples in the eye diagram ("f_est", (ctypes.c_float * MODEM_STATS_MAX_F_EST)), ("fft_buf", (ctypes.c_float * MODEM_STATS_NSPEC * 2)), ("fft_cfg", ctypes.c_void_p), ] # Return code flags for freedv_get_rx_status() function api.FREEDV_RX_TRIAL_SYNC = 0x1 # type: ignore # demodulator has trial sync api.FREEDV_RX_SYNC = 0x2 # type: ignore # demodulator has sync api.FREEDV_RX_BITS = 0x4 # type: ignore # data bits have been returned api.FREEDV_RX_BIT_ERRORS = 0x8 # type: ignore # FEC may not have corrected all bit errors (not all parity checks OK) api.rx_sync_flags_to_text = [ # type: ignore "----", "---T", "--S-", "--ST", "-B--", "-B-T", "-BS-", "-BST", "E---", "E--T", "E-S-", "E-ST", "EB--", "EB-T", "EBS-", "EBST", ] # Audio buffer --------------------------------------------------------- class audio_buffer: """ Thread-safe audio buffer, which fits the needs of codec2 made by David Rowe, VK5DGR """ # A buffer of int16 samples, using a fixed length numpy array self.buffer for storage # self.nbuffer is the current number of samples in the buffer def __init__(self, size): log.debug("[C2 ] Creating audio buffer", size=size) self.size = size self.buffer = np.zeros(size, dtype=np.int16) self.nbuffer = 0 self.mutex = Lock() def push(self, samples): """ Push new data to buffer Args: samples: Returns: Nothing """ self.mutex.acquire() # Add samples at the end of the buffer assert self.nbuffer + len(samples) <= self.size self.buffer[self.nbuffer : self.nbuffer + len(samples)] = samples self.nbuffer += len(samples) self.mutex.release() def pop(self, size): """ get data from buffer in size of NIN Args: size: Returns: Nothing """ self.mutex.acquire() # Remove samples from the start of the buffer self.nbuffer -= size self.buffer[: self.nbuffer] = self.buffer[size : size + self.nbuffer] assert self.nbuffer >= 0 self.mutex.release() # Resampler --------------------------------------------------------- # Oversampling rate api.FDMDV_OS_48 = 6 # type: ignore # Number of oversampling taps at 48kHz api.FDMDV_OS_TAPS_48K = 48 # type: ignore # Number of oversampling filter taps at 8kHz api.FDMDV_OS_TAPS_48_8K = api.FDMDV_OS_TAPS_48K // api.FDMDV_OS_48 # type: ignore api.fdmdv_8_to_48_short.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] # type: ignore api.fdmdv_48_to_8_short.argtype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] # type: ignore class resampler: """ Re-sampler class """ # Re-sample an array of variable length, we just store the filter memories here MEM8 = api.FDMDV_OS_TAPS_48_8K MEM48 = api.FDMDV_OS_TAPS_48K def __init__(self): log.debug("[C2 ] Create 48<->8 kHz resampler") self.filter_mem8 = np.zeros(self.MEM8, dtype=np.int16) self.filter_mem48 = np.zeros(self.MEM48) def resample48_to_8(self, in48): """ Audio resampler integration from codec2 Downsample audio from 48000Hz to 8000Hz Args: in48: input data as np.int16 Returns: Downsampled 8000Hz data as np.int16 """ assert in48.dtype == np.int16 # Length of input vector must be an integer multiple of api.FDMDV_OS_48 assert len(in48) % api.FDMDV_OS_48 == 0 # type: ignore # Concatenate filter memory and input samples in48_mem = np.zeros(self.MEM48 + len(in48), dtype=np.int16) in48_mem[: self.MEM48] = self.filter_mem48 in48_mem[self.MEM48 :] = in48 # In C: pin48=&in48_mem[MEM48] pin48 = ctypes.byref(np.ctypeslib.as_ctypes(in48_mem), 2 * self.MEM48) n8 = int(len(in48) / api.FDMDV_OS_48) # type: ignore out8 = np.zeros(n8, dtype=np.int16) api.fdmdv_48_to_8_short(out8.ctypes, pin48, n8) # type: ignore # Store memory for next time self.filter_mem48 = in48_mem[: self.MEM48] return out8 def resample8_to_48(self, in8): """ Audio resampler integration from codec2 Re-sample audio from 8000Hz to 48000Hz Args: in8: input data as np.int16 Returns: 48000Hz audio as np.int16 """ assert in8.dtype == np.int16 # Concatenate filter memory and input samples in8_mem = np.zeros(self.MEM8 + len(in8), dtype=np.int16) in8_mem[: self.MEM8] = self.filter_mem8 in8_mem[self.MEM8 :] = in8 # In C: pin8=&in8_mem[MEM8] pin8 = ctypes.byref(np.ctypeslib.as_ctypes(in8_mem), 2 * self.MEM8) out48 = np.zeros(api.FDMDV_OS_48 * len(in8), dtype=np.int16) # type: ignore api.fdmdv_8_to_48_short(out48.ctypes, pin8, len(in8)) # type: ignore # Store memory for next time self.filter_mem8 = in8_mem[: self.MEM8] return out48 def open_instance(mode: int) -> ctypes.c_void_p: data_custom = 21 if mode in [ FREEDV_MODE.data_ofdm_200.value, FREEDV_MODE.data_ofdm_250.value, FREEDV_MODE.data_ofdm_500.value, FREEDV_MODE.data_ofdm_1700.value, FREEDV_MODE.data_ofdm_2438.value, ]: # if mode in [FREEDV_MODE.data_ofdm_500.value, FREEDV_MODE.data_ofdm_2438.value, FREEDV_MODE.data_qam_2438]: custom_params = ofdm_configurations[mode] return ctypes.cast( api.freedv_open_advanced( data_custom, ctypes.byref(custom_params), ), ctypes.c_void_p, ) elif mode in [FREEDV_MODE.data_vhf_1.value]: fsk_custom = 9 custom_params = fsk_configurations[mode] return ctypes.cast( api.freedv_open_advanced( fsk_custom, ctypes.byref(custom_params), ), ctypes.c_void_p, ) else: if mode not in [data_custom]: return ctypes.cast(api.freedv_open(mode), ctypes.c_void_p) def get_bytes_per_frame(mode: int) -> int: """ Provide bytes per frame information for accessing from data handler :param mode: Codec2 mode to query :type mode: int or str :return: Bytes per frame of the supplied codec2 data mode :rtype: int """ freedv = open_instance(mode) # TODO add close session # get number of bytes per frame for mode return int(api.freedv_get_bits_per_modem_frame(freedv) / 8) MAX_UW_BITS = 64 # 192 class OFDM_CONFIG(ctypes.Structure): _fields_ = [ ("tx_centre", ctypes.c_float), # TX Centre Audio Frequency ("rx_centre", ctypes.c_float), # RX Centre Audio Frequency ("fs", ctypes.c_float), # Sample Frequency ("rs", ctypes.c_float), # Symbol Rate ("ts", ctypes.c_float), # Symbol duration ("tcp", ctypes.c_float), # Cyclic Prefix duration ("timing_mx_thresh", ctypes.c_float), # Threshold for timing metrics ("nc", ctypes.c_int), # Number of carriers ("ns", ctypes.c_int), # Number of Symbol frames ("np", ctypes.c_int), # Number of freedata_server frames per packet ("bps", ctypes.c_int), # Bits per Symbol ("txtbits", ctypes.c_int), # Number of auxiliary data bits ("nuwbits", ctypes.c_int), # Number of unique word bits ("bad_uw_errors", ctypes.c_int), # Threshold for bad unique word detection ("ftwindowwidth", ctypes.c_int), # Filter window width ("edge_pilots", ctypes.c_int), # Edge pilots configuration ("state_machine", ctypes.c_char_p), # Name of sync state machine used ("codename", ctypes.c_char_p), # LDPC codename ("tx_uw", ctypes.c_uint8 * MAX_UW_BITS), # User defined unique word ("amp_est_mode", ctypes.c_int), # Amplitude estimator algorithm mode ("tx_bpf_en", ctypes.c_bool), # TX BPF enable flag ("rx_bpf_en", ctypes.c_bool), # RX BPF enable flag ("tx_bpf_proto", ctypes.POINTER(ctypes.c_float)), # low pass prototype for complex BPF ("tx_bpf_proto_n", ctypes.c_int), # number of taps in low pass prototype ("foff_limiter", ctypes.c_bool), # Frequency offset limiter enable flag ("amp_scale", ctypes.c_float), # Amplitude scale factor ("clip_gain1", ctypes.c_float), # Pre-clipping gain ("clip_gain2", ctypes.c_float), # Post-clipping gain ("clip_en", ctypes.c_bool), # Clipping enable flag ("mode", ctypes.c_char * 16), # OFDM mode in string form ("data_mode", ctypes.c_char_p), # Data mode ("streaming", "burst", etc.) ("fmin", ctypes.c_float), # Minimum frequency for tuning range ("fmax", ctypes.c_float), # Maximum frequency for tuning range ] class FREEDV_ADVANCED(ctypes.Structure): """Advanced structure for fsk and ofdm modes""" _fields_ = [ ("interleave_frames", ctypes.c_int), ("M", ctypes.c_int), ("Rs", ctypes.c_int), ("Fs", ctypes.c_int), ("first_tone", ctypes.c_int), ("tone_spacing", ctypes.c_int), ("codename", ctypes.c_char_p), ("config", ctypes.POINTER(OFDM_CONFIG)), ] class FREEDV_ADVANCED_FSK(ctypes.Structure): """Advanced structure for fsk and ofdm modes""" _fields_ = [ ("interleave_frames", ctypes.c_int), ("M", ctypes.c_int), ("Rs", ctypes.c_int), ("Fs", ctypes.c_int), ("first_tone", ctypes.c_int), ("tone_spacing", ctypes.c_int), ("codename", ctypes.c_char_p), ] api.freedv_open_advanced.argtypes = [ctypes.c_int, ctypes.POINTER(FREEDV_ADVANCED)] api.freedv_open_advanced.restype = ctypes.c_void_p def create_default_ofdm_config(): ofdm_default_config = OFDM_CONFIG( tx_centre=1500.0, rx_centre=1500.0, fs=8000.0, rs=62.5, ts=0.016, tcp=0.006, timing_mx_thresh=0.10, nc=9, ns=5, np=29, bps=2, txtbits=0, nuwbits=40, bad_uw_errors=10, ftwindowwidth=80, edge_pilots=False, state_machine="data".encode("utf-8"), codename="H_1024_2048_4f".encode("utf-8"), tx_uw=(ctypes.c_uint8 * MAX_UW_BITS)(*([0] * MAX_UW_BITS)), amp_est_mode=1, tx_bpf_en=False, rx_bpf_en=False, tx_bpf_proto=codec2_filter_coeff.testFilter, tx_bpf_proto_n=int(ctypes.sizeof(codec2_filter_coeff.testFilter) / ctypes.sizeof(ctypes.c_float)), foff_limiter=False, amp_scale=300e3, clip_gain1=2.2, clip_gain2=0.8, clip_en=False, mode="CUSTOM".encode("utf-8"), data_mode="streaming".encode("utf-8"), fmin=-50.0, fmax=50.0, ) return FREEDV_ADVANCED( interleave_frames=0, M=2, Rs=100, Fs=8000, first_tone=1000, tone_spacing=200, codename="H_256_512_4".encode("utf-8"), config=ctypes.pointer(ofdm_default_config), ) def create_tx_uw(nuwbits, uw_sequence): """ Creates a tx_uw ctypes array filled with the uw_sequence up to nuwbits. If uw_sequence is shorter than nuwbits, the rest of the array is filled with zeros. :param nuwbits: The number of bits for the tx_uw array, should not exceed MAX_UW_BITS. :param uw_sequence: List of integers representing the unique word sequence. :return: A ctypes array representing the tx_uw. """ # Ensure nuwbits does not exceed MAX_UW_BITS if nuwbits > MAX_UW_BITS: raise ValueError(f"nuwbits exceeds MAX_UW_BITS: {MAX_UW_BITS}") tx_uw_array = (ctypes.c_uint8 * MAX_UW_BITS)(*([0] * MAX_UW_BITS)) for i in range(min(len(uw_sequence), MAX_UW_BITS)): tx_uw_array[i] = uw_sequence[i] return tx_uw_array def create_default_fsk_config(): return FREEDV_ADVANCED( interleave_frames=0, M=2, Rs=100, Fs=8000, first_tone=1000, tone_spacing=200, codename="H_256_512_4".encode("utf-8"), config=None, ) def get_centered_first_tone(config, center=1500): """ Calculate and return the first tone frequency so that the set of tones is centered at the given center frequency. The tones are assumed to be spaced equally based on config.tone_spacing, and config.M is the total number of tones. Args: config: A configuration object with the following attributes: - M (int): Total number of tones. - tone_spacing (float): Spacing between consecutive tones. center (float): Desired center frequency (default is 1500 Hz). Returns: float: The computed value for the first tone. """ return int(center - ((config.M - 1) * config.tone_spacing) // 2) data_vhf_1_config = create_default_fsk_config() data_vhf_1_config.interleave_frames = 1 data_vhf_1_config.M = 4 data_vhf_1_config.Rs = 200 data_vhf_1_config.tone_spacing = 400 data_vhf_1_config.codename = "H_256_512_4".encode("utf-8") data_vhf_1_config.first_tone = get_centered_first_tone(data_vhf_1_config) # ---------------- OFDM 500 Hz Bandwidth ---------------# # DATAC13 # OFDM 200 data_ofdm_200_config = create_default_ofdm_config() data_ofdm_200_config.config.contents.ns = 5 data_ofdm_200_config.config.contents.np = 18 data_ofdm_200_config.config.contents.tcp = 0.006 data_ofdm_200_config.config.contents.ts = 0.016 data_ofdm_200_config.config.contents.rs = 1.0 / data_ofdm_200_config.config.contents.ts data_ofdm_200_config.config.contents.nc = 3 data_ofdm_200_config.config.contents.timing_mx_thresh = 0.45 data_ofdm_200_config.config.contents.bad_uw_errors = 18 data_ofdm_200_config.config.contents.codename = "H_256_512_4".encode("utf-8") data_ofdm_200_config.config.contents.amp_scale = 2.5 * 300e3 data_ofdm_200_config.config.contents.nuwbits = 48 data_ofdm_200_config.config.contents.tx_uw = create_tx_uw( data_ofdm_200_config.config.contents.nuwbits, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], ) data_ofdm_200_config.config.contents.clip_gain1 = 1.2 data_ofdm_200_config.config.contents.clip_gain2 = 1.0 data_ofdm_200_config.config.contents.tx_bpf_en = False data_ofdm_200_config.config.contents.tx_bpf_proto = codec2_filter_coeff.generate_filter_coefficients(8000, 400, 101) data_ofdm_200_config.config.contents.tx_bpf_proto_n = 101 # TODO sizeof(filtP200S400) / sizeof(float); # DATAC4 # OFDM 250 data_ofdm_250_config = create_default_ofdm_config() data_ofdm_250_config.config.contents.ns = 5 data_ofdm_250_config.config.contents.np = 47 data_ofdm_250_config.config.contents.tcp = 0.006 data_ofdm_250_config.config.contents.ts = 0.016 data_ofdm_250_config.config.contents.rs = 1.0 / data_ofdm_250_config.config.contents.ts data_ofdm_250_config.config.contents.nc = 4 data_ofdm_250_config.config.contents.timing_mx_thresh = 0.5 data_ofdm_250_config.config.contents.bad_uw_errors = 12 data_ofdm_250_config.config.contents.codename = "H_1024_2048_4f".encode("utf-8") data_ofdm_250_config.config.contents.amp_scale = 2 * 300e3 data_ofdm_250_config.config.contents.nuwbits = 32 data_ofdm_250_config.config.contents.tx_uw = create_tx_uw( data_ofdm_250_config.config.contents.nuwbits, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], ) data_ofdm_250_config.config.contents.clip_gain1 = 1.2 data_ofdm_250_config.config.contents.clip_gain2 = 1.0 data_ofdm_250_config.config.contents.tx_bpf_en = True data_ofdm_250_config.config.contents.tx_bpf_proto = codec2_filter_coeff.generate_filter_coefficients(8000, 400, 101) data_ofdm_250_config.config.contents.tx_bpf_proto_n = 101 # TODO sizeof(filtP200S400) / sizeof(float); # OFDM 500 data_ofdm_500_config = create_default_ofdm_config() data_ofdm_500_config.config.contents.ns = 5 data_ofdm_500_config.config.contents.np = 32 data_ofdm_500_config.config.contents.tcp = 0.006 data_ofdm_500_config.config.contents.ts = 0.016 data_ofdm_500_config.config.contents.rs = 1.0 / data_ofdm_500_config.config.contents.ts data_ofdm_500_config.config.contents.nc = 8 data_ofdm_500_config.config.contents.timing_mx_thresh = 0.1 data_ofdm_500_config.config.contents.bad_uw_errors = 18 data_ofdm_500_config.config.contents.codename = "H_1024_2048_4f".encode("utf-8") data_ofdm_500_config.config.contents.amp_scale = 300e3 # 290E3 data_ofdm_500_config.config.contents.nuwbits = 56 data_ofdm_500_config.config.contents.tx_uw = create_tx_uw( data_ofdm_500_config.config.contents.nuwbits, [0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1], ) data_ofdm_500_config.config.contents.clip_gain1 = 2.5 # 2.8 data_ofdm_500_config.config.contents.clip_gain2 = 1.0 # 0.9 data_ofdm_500_config.config.contents.tx_bpf_en = True data_ofdm_500_config.config.contents.tx_bpf_proto = codec2_filter_coeff.generate_filter_coefficients(8000, 600, 100) data_ofdm_500_config.config.contents.tx_bpf_proto_n = 100 # DATAC1 # OFDM1700 data_ofdm_1700_config = create_default_ofdm_config() data_ofdm_1700_config.config.contents.ns = 5 data_ofdm_1700_config.config.contents.np = 38 data_ofdm_1700_config.config.contents.tcp = 0.006 data_ofdm_1700_config.config.contents.ts = 0.016 data_ofdm_1700_config.config.contents.nc = 27 data_ofdm_1700_config.config.contents.nuwbits = 16 data_ofdm_1700_config.config.contents.timing_mx_thresh = 0.10 data_ofdm_1700_config.config.contents.bad_uw_errors = 6 data_ofdm_1700_config.config.contents.codename = b"H_4096_8192_3d" data_ofdm_1700_config.config.contents.clip_gain1 = 2.7 data_ofdm_1700_config.config.contents.clip_gain2 = 0.8 data_ofdm_1700_config.config.contents.amp_scale = 145e3 data_ofdm_1700_config.config.contents.tx_bpf_en = False data_ofdm_1700_config.config.contents.tx_bpf_proto = codec2_filter_coeff.generate_filter_coefficients(8000, 2000, 100) data_ofdm_1700_config.config.contents.tx_bpf_proto_n = 100 data_ofdm_1700_config.config.contents.tx_uw = create_tx_uw( data_ofdm_1700_config.config.contents.nuwbits, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0] ) """ # DATAC3 data_ofdm_500_config = create_default_ofdm_config() data_ofdm_500_config.config.contents.ns = 5 data_ofdm_500_config.config.contents.np = 29 data_ofdm_500_config.config.contents.tcp = 0.006 data_ofdm_500_config.config.contents.ts = 0.016 data_ofdm_500_config.config.contents.rs = 1.0 / data_ofdm_500_config.config.contents.ts data_ofdm_500_config.config.contents.nc = 9 data_ofdm_500_config.config.contents.nuwbits = 40 data_ofdm_500_config.config.contents.timing_mx_thresh = 0.10 data_ofdm_500_config.config.contents.bad_uw_errors = 10 data_ofdm_500_config.config.contents.codename = b"H_1024_2048_4f" data_ofdm_500_config.config.contents.clip_gain1 = 2.2 data_ofdm_500_config.config.contents.clip_gain2 = 0.8 data_ofdm_500_config.config.contents.tx_uw = create_tx_uw(data_ofdm_500_config.config.contents.nuwbits, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0]) """ # ---------------- OFDM 2438 Hz Bandwidth 16200,9720 ---------------# data_ofdm_2438_config = create_default_ofdm_config() data_ofdm_2438_config.config.contents.ns = 5 data_ofdm_2438_config.config.contents.np = 52 data_ofdm_2438_config.config.contents.tcp = 0.004 data_ofdm_2438_config.config.contents.ts = 0.016 data_ofdm_2438_config.config.contents.rs = 1.0 / data_ofdm_2438_config.config.contents.ts data_ofdm_2438_config.config.contents.nc = 39 data_ofdm_2438_config.config.contents.nuwbits = 24 data_ofdm_2438_config.config.contents.timing_mx_thresh = 0.10 data_ofdm_2438_config.config.contents.bad_uw_errors = 8 data_ofdm_2438_config.config.contents.amp_est_mode = 0 data_ofdm_2438_config.config.contents.amp_scale = 106e3 data_ofdm_2438_config.config.contents.codename = "H_16200_9720".encode("utf-8") data_ofdm_2438_config.config.contents.clip_gain1 = 3.3 data_ofdm_2438_config.config.contents.clip_gain2 = ( 1.5 # 0.8 - a test in real world shows, 0.8 seems to be not enough for decoding. Lets test this some more time. ) data_ofdm_2438_config.config.contents.timing_mx_thresh = 0.10 data_ofdm_2438_config.config.contents.tx_uw = create_tx_uw( data_ofdm_2438_config.config.contents.nuwbits, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1], ) data_ofdm_2438_config.config.contents.tx_bpf_en = True data_ofdm_2438_config.config.contents.tx_bpf_proto = codec2_filter_coeff.generate_filter_coefficients(8000, 2700, 100) data_ofdm_2438_config.config.contents.tx_bpf_proto_n = 100 # ---------------- QAM 2438 Hz Bandwidth ---------------# """ data_qam_2438_config = create_default_ofdm_config() data_qam_2438_config.config.contents.bps = 4 data_qam_2438_config.config.contents.ns = 5 data_qam_2438_config.config.contents.np = 26 data_qam_2438_config.config.contents.tcp = 0.005 data_qam_2438_config.config.contents.ts = 0.018 data_qam_2438_config.config.contents.rs = 1.0 / data_qam_2438_config.config.contents.ts data_qam_2438_config.config.contents.nc = 39 data_qam_2438_config.config.contents.nuwbits = 162 data_qam_2438_config.config.contents.timing_mx_thresh = 0.10 data_qam_2438_config.config.contents.bad_uw_errors = 50 data_qam_2438_config.config.contents.amp_est_mode = 0 data_qam_2438_config.config.contents.amp_scale = 145E3 data_qam_2438_config.config.contents.codename = b"H_16200_9720" data_qam_2438_config.config.contents.clip_gain1 = 2.7 data_qam_2438_config.config.contents.clip_gain2 = 0.8 data_qam_2438_config.config.contents.timing_mx_thresh = 0.10 data_qam_2438_config.config.contents.tx_uw = create_tx_uw(162, [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0]) """ ofdm_configurations = { FREEDV_MODE.data_ofdm_200.value: data_ofdm_200_config, FREEDV_MODE.data_ofdm_250.value: data_ofdm_250_config, FREEDV_MODE.data_ofdm_500.value: data_ofdm_500_config, FREEDV_MODE.data_ofdm_1700.value: data_ofdm_1700_config, FREEDV_MODE.data_ofdm_2438.value: data_ofdm_2438_config, # FREEDV_MODE.data_qam_2438.value: data_qam_2438_config, } fsk_configurations = {FREEDV_MODE.data_vhf_1.value: data_vhf_1_config}