diff --git a/src/urh/awre/engines/LengthEngine.py b/src/urh/awre/engines/LengthEngine.py index 413a6fa834..9dc2a697fb 100644 --- a/src/urh/awre/engines/LengthEngine.py +++ b/src/urh/awre/engines/LengthEngine.py @@ -3,9 +3,9 @@ import numpy as np -from urh.awre.CommonRange import CommonRange, EmptyCommonRange +from urh.awre.CommonRange import CommonRange from urh.awre.engines.Engine import Engine -from urh.cythonext import awre_util +from urh.cythonext import util class LengthEngine(Engine): @@ -177,7 +177,7 @@ def choose_high_scored_ranges(self, scored_ranges: dict, bitvectors_by_n_gram_le @staticmethod def score_bits(bits: np.ndarray, target_length: int, position: int, byteorder="big"): - value = awre_util.bit_array_to_number(bits, len(bits)) + value = util.bit_array_to_number(bits, len(bits)) if byteorder == "little": if len(bits) > 8 and len(bits) % 8 == 0: n = len(bits) // 8 diff --git a/src/urh/cli/urh_cli.py b/src/urh/cli/urh_cli.py index 6034c37d2d..6c8f941fd9 100755 --- a/src/urh/cli/urh_cli.py +++ b/src/urh/cli/urh_cli.py @@ -89,7 +89,7 @@ def build_modulator_from_args(arguments: argparse.Namespace): result.carrier_freq_hz = float(arguments.carrier_frequency) result.carrier_amplitude = float(arguments.carrier_amplitude) result.carrier_phase_deg = float(arguments.carrier_phase) - result.samples_per_bit = int(arguments.bit_length) + result.samples_per_symbol = int(arguments.bit_length) if arguments.modulation_type == "ASK": if arguments.parameter_zero.endswith("%"): @@ -222,7 +222,7 @@ def modulate_messages(messages, modulator): return None cli_progress_bar(0, len(messages), title="Modulating") - nsamples = sum(int(len(msg.encoded_bits) * modulator.samples_per_bit + msg.pause) for msg in messages) + nsamples = sum(int(len(msg.encoded_bits) * modulator.samples_per_symbol + msg.pause) for msg in messages) buffer = IQArray(None, dtype=np.float32, n=nsamples) pos = 0 for i, msg in enumerate(messages): diff --git a/src/urh/controller/GeneratorTabController.py b/src/urh/controller/GeneratorTabController.py index 2ce866ba7d..47ff6736e6 100644 --- a/src/urh/controller/GeneratorTabController.py +++ b/src/urh/controller/GeneratorTabController.py @@ -102,7 +102,7 @@ def modulators(self): @property def total_modulated_samples(self) -> int: - return sum(int(len(msg.encoded_bits) * self.__get_modulator_of_message(msg).samples_per_bit + msg.pause) + return sum(int(len(msg.encoded_bits) * self.__get_modulator_of_message(msg).samples_per_symbol + msg.pause) for msg in self.table_model.protocol.messages) @modulators.setter @@ -213,7 +213,7 @@ def bootstrap_modulator(self, protocol: ProtocolAnalyzer): return modulator = self.modulators[0] - modulator.samples_per_bit = protocol.messages[0].bit_len + modulator.samples_per_symbol = protocol.messages[0].bit_len if protocol.signal: modulator.sample_rate = protocol.signal.sample_rate @@ -528,7 +528,7 @@ def refresh_estimated_time(self): return avg_msg_len = numpy.mean([len(msg.encoded_bits) for msg in c.messages]) - avg_bit_len = numpy.mean([m.samples_per_bit for m in self.modulators]) + avg_bit_len = numpy.mean([m.samples_per_symbol for m in self.modulators]) avg_sample_rate = numpy.mean([m.sample_rate for m in self.modulators]) pause_samples = sum(c.pauses) nsamples = c.num_messages * avg_msg_len * avg_bit_len + pause_samples diff --git a/src/urh/controller/dialogs/ModulatorDialog.py b/src/urh/controller/dialogs/ModulatorDialog.py index e9faf89f4c..c0795265ca 100644 --- a/src/urh/controller/dialogs/ModulatorDialog.py +++ b/src/urh/controller/dialogs/ModulatorDialog.py @@ -126,7 +126,7 @@ def set_ui_for_current_modulator(self): self.ui.comboBoxModulationType.setCurrentIndex(index) self.ui.doubleSpinBoxCarrierFreq.setValue(self.current_modulator.carrier_freq_hz) self.ui.doubleSpinBoxCarrierPhase.setValue(self.current_modulator.carrier_phase_deg) - self.ui.spinBoxBitLength.setValue(self.current_modulator.samples_per_bit) + self.ui.spinBoxBitLength.setValue(self.current_modulator.samples_per_symbol) self.ui.spinBoxSampleRate.setValue(self.current_modulator.sample_rate) self.ui.spinBoxParameter0.setValue(self.current_modulator.param_for_zero) self.ui.spinBoxParameter1.setValue(self.current_modulator.param_for_one) @@ -374,7 +374,7 @@ def on_carrier_phase_changed(self): @pyqtSlot() def on_bit_len_changed(self): - self.current_modulator.samples_per_bit = self.ui.spinBoxBitLength.value() + self.current_modulator.samples_per_symbol = self.ui.spinBoxBitLength.value() self.draw_carrier() self.draw_data_bits() self.draw_modulated() diff --git a/src/urh/controller/dialogs/SimulatorDialog.py b/src/urh/controller/dialogs/SimulatorDialog.py index 61774edc44..cb8a2dfbd2 100644 --- a/src/urh/controller/dialogs/SimulatorDialog.py +++ b/src/urh/controller/dialogs/SimulatorDialog.py @@ -12,6 +12,7 @@ from urh.controller.widgets.SniffSettingsWidget import SniffSettingsWidget from urh.dev.BackendHandler import BackendHandler from urh.dev.EndlessSender import EndlessSender +from urh.signalprocessing.IQArray import IQArray from urh.simulator.Simulator import Simulator from urh.simulator.SimulatorConfiguration import SimulatorConfiguration from urh.ui.SimulatorScene import SimulatorScene @@ -415,10 +416,12 @@ def on_checkbox_capture_full_rx_clicked(self): @pyqtSlot() def on_btn_save_rx_clicked(self): rx_device = self.simulator.sniffer.rcv_device - if isinstance(rx_device.data, np.ndarray): - filename = FileOperator.get_save_file_name("simulation_capture.complex") + if isinstance(rx_device.data, np.ndarray) or isinstance(rx_device.data, IQArray): + data = IQArray(rx_device.data[:rx_device.current_index]) + filename = FileOperator.save_data_dialog("simulation_capture", data, + sample_rate=rx_device.sample_rate, parent=self) if filename: - rx_device.data[:rx_device.current_index].tofile(filename) + data.tofile(filename) self.rx_file_saved.emit(filename) @pyqtSlot() diff --git a/src/urh/cythonext/awre_util.pyx b/src/urh/cythonext/awre_util.pyx index 833bfb7b9f..7f5a75d9d9 100644 --- a/src/urh/cythonext/awre_util.pyx +++ b/src/urh/cythonext/awre_util.pyx @@ -7,12 +7,10 @@ from libc.math cimport floor, ceil, pow from libc.stdlib cimport malloc, free from libcpp cimport bool -from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, int32_t, int8_t, int64_t - -from array import array - +from libc.stdint cimport uint8_t, uint32_t, int32_t, int64_t from urh.cythonext.util import crc +from urh.cythonext.util cimport bit_array_to_number cpdef set find_longest_common_sub_sequence_indices(np.uint8_t[::1] seq1, np.uint8_t[::1] seq2): cdef unsigned int i, j, longest = 0, counter = 0, len_bits1 = len(seq1), len_bits2 = len(seq2) @@ -302,19 +300,6 @@ cpdef list find_occurrences(np.uint8_t[::1] a, np.uint8_t[::1] b, return result -cpdef unsigned long long bit_array_to_number(uint8_t[::1] bits, int64_t end, int64_t start=0) nogil: - if end < 1: - return 0 - - cdef long long i, acc = 1 - cdef unsigned long long result = 0 - - for i in range(start, end): - result += bits[end-1-i+start] * acc - acc *= 2 - - return result - cpdef np.ndarray[np.int32_t, ndim=2, mode="c"] create_seq_number_difference_matrix(list bitvectors, int n_gram_length): """ Create the difference matrix e.g. diff --git a/src/urh/cythonext/signal_functions.pyx b/src/urh/cythonext/signal_functions.pyx index 38e0d0327f..c1b85c5200 100644 --- a/src/urh/cythonext/signal_functions.pyx +++ b/src/urh/cythonext/signal_functions.pyx @@ -4,11 +4,13 @@ import cython import numpy as np from libcpp cimport bool -from urh.cythonext.util cimport IQ, iq +from libc.stdint cimport uint8_t, uint16_t, uint32_t, int64_t +from urh.cythonext.util cimport IQ, iq, bit_array_to_number from cython.parallel import prange from libc.math cimport atan2, sqrt, M_PI + cdef extern from "math.h" nogil: float cosf(float x) float sinf(float x) @@ -43,134 +45,79 @@ cdef get_numpy_dtype(iq cython_type): else: raise ValueError("dtype {} not supported for modulation".format(cython.typeof(cython_type))) -cpdef modulate_fsk(unsigned char[:] bit_array, unsigned long pause, unsigned long start, - float a, float freq0, float freq1, float phi, float sample_rate, - long long samples_per_bit, iq iq_type): - cdef long long i = 0, j = 0, index = 0 - cdef float t = 0, f = 0, arg = 0, f_next = 0, phase = 0 - cdef long long total_samples = int(len(bit_array) * samples_per_bit + pause) - - result = np.zeros((total_samples, 2), dtype=get_numpy_dtype(iq_type)) - cdef iq[:, ::1] result_view = result - cdef float* phases = malloc(total_samples * sizeof(float)) - - for i in range(0, samples_per_bit): - phases[i] = phi - - cdef long long num_bits = len(bit_array) - with cython.cdivision: - for i in range(1, num_bits): - phase = phases[i*samples_per_bit-1] - - # We need to correct the phase on transitions between 0 and 1 - if bit_array[i-1] != bit_array[i]: - t = (i*samples_per_bit+start-1) / sample_rate - f = freq0 if bit_array[i-1] == 0 else freq1 - f_next = freq0 if bit_array[i] == 0 else freq1 - phase = (phase + 2 * M_PI * t * (f - f_next)) % (2 * M_PI) +cpdef modulate_c(uint8_t[:] bits, uint32_t samples_per_symbol, str modulation_type, + float[:] parameters, uint16_t bits_per_symbol, + float carrier_amplitude, float carrier_frequency, float carrier_phase, float sample_rate, + uint32_t pause, uint32_t start, iq iq_type, + float gauss_bt=0.5, float filter_width=1.0): + cdef int64_t i = 0, j = 0, index = 0, s_i = 0 + cdef uint32_t total_symbols = int(len(bits) // bits_per_symbol) + cdef int64_t total_samples = total_symbols * samples_per_symbol + pause - for j in range(i*samples_per_bit, (i+1)*samples_per_bit): - phases[j] = phase + cdef float a = carrier_amplitude, f = carrier_frequency, phi = carrier_phase + cdef float t = 0, arg = 0 - cdef long long loop_end = total_samples-pause - for i in prange(0, loop_end, nogil=True, schedule="static"): - t = (i+start) / sample_rate - index = (i/samples_per_bit) - f = freq0 if bit_array[index] == 0 else freq1 - - arg = 2 * M_PI * f * t + phases[i] - result_view[i, 0] = (a * cosf(arg)) - result_view[i, 1] = (a * sinf(arg)) + result = np.zeros((total_samples, 2), dtype=get_numpy_dtype(iq_type)) + cdef iq[:, ::1] result_view = result - # We need to correct the phase on transitions between 0 and 1 - # if i < loop_end - 1 and (i+1) % samples_per_bit == 0: - # index = ((i+1)/samples_per_bit) - # f_next = freq0 if bit_array[index] == 0 else freq1 - # phi += 2 * M_PI * t * (f - f_next) - # phi = phi % (2 * M_PI) + cdef bool is_fsk = modulation_type.lower() == "fsk" + cdef bool is_ask = modulation_type.lower() == "ask" + cdef bool is_psk = modulation_type.lower() == "psk" + cdef bool is_gfsk = modulation_type.lower() == "gfsk" - free(phases) - return result + assert is_fsk or is_ask or is_psk or is_gfsk -cpdef modulate_ask(unsigned char[:] bit_array, unsigned long pause, unsigned long start, - double a0, double a1, double f, double phi, double sample_rate, - unsigned long samples_per_bit, iq iq_type): - cdef long long i = 0, index = 0 - cdef float t = 0, a = 0, arg = 0 - cdef long long total_samples = int(len(bit_array) * samples_per_bit + pause) + cdef np.ndarray[np.float32_t, ndim=2] gauss_filtered_freqs_phases + if is_gfsk: + gauss_filtered_freqs_phases = get_gauss_filtered_freqs_phases(bits, parameters, total_symbols, + samples_per_symbol, sample_rate, carrier_phase, + start, gauss_bt, filter_width) - result = np.zeros((total_samples, 2), dtype=get_numpy_dtype(iq_type)) - cdef iq[:, ::1] result_view = result + for s_i in prange(0, total_symbols, schedule="static", nogil=True): + index = bit_array_to_number(bits, end=(s_i+1)*bits_per_symbol, start=s_i*bits_per_symbol) + a = carrier_amplitude + f = carrier_frequency + phi = carrier_phase - cdef long long loop_end = total_samples-pause - for i in prange(0, loop_end, nogil=True, schedule="static"): - index = (i/samples_per_bit) - a = a0 if bit_array[index] == 0 else a1 + if is_ask: + a = parameters[index] + if a == 0: + continue + elif is_fsk: + f = parameters[index] + elif is_psk: + phi = parameters[index] - if a > 0: + for i in range(s_i * samples_per_symbol, (s_i+1)*samples_per_symbol): t = (i+start) / sample_rate + if is_gfsk: + f = gauss_filtered_freqs_phases[i, 0] + phi = gauss_filtered_freqs_phases[i, 1] arg = 2 * M_PI * f * t + phi + result_view[i, 0] = (a * cosf(arg)) result_view[i, 1] = (a * sinf(arg)) return result -cpdef modulate_psk(unsigned char[:] bit_array, unsigned long pause, unsigned long start, - double a, double f, double phi0, double phi1, double sample_rate, - unsigned long samples_per_bit, iq iq_type): - cdef long long i = 0, index = 0 - cdef float t = 0, phi = 0, arg = 0 - cdef long long total_samples = int(len(bit_array) * samples_per_bit + pause) - - result = np.zeros((total_samples, 2), dtype=get_numpy_dtype(iq_type)) - cdef iq[:, ::1] result_view = result - - cdef long long loop_end = total_samples-pause - for i in prange(0, loop_end, nogil=True, schedule="static"): - index = (i/samples_per_bit) - phi = phi0 if bit_array[index] == 0 else phi1 - t = (i+start) / sample_rate - arg = 2 * M_PI * f * t + phi - result_view[i, 0] = (a * cosf(arg)) - result_view[i, 1] = (a * sinf(arg)) - - return result +cdef np.ndarray[np.float32_t, ndim=2] get_gauss_filtered_freqs_phases(uint8_t[:] bits, float[:] parameters, + uint32_t num_symbols, uint32_t samples_per_symbol, + float sample_rate, float phi, uint32_t start, + float gauss_bt, float filter_width): + cdef int64_t i, s_i, index, num_values = num_symbols * samples_per_symbol + cdef np.ndarray[np.float32_t, ndim=1] frequencies = np.empty(num_values, dtype=np.float32) + cdef uint16_t bits_per_symbol = int(len(bits) // num_symbols) + for s_i in range(0, num_symbols): + index = bit_array_to_number(bits, end=(s_i+1)*bits_per_symbol, start=s_i*bits_per_symbol) -cdef np.ndarray[np.float32_t, ndim=1] gauss_fir(float sample_rate, unsigned long samples_per_bit, - float bt=.5, float filter_width=1.0): - """ + for i in range(s_i * samples_per_symbol, (s_i+1)*samples_per_symbol): + frequencies[i] = parameters[index] - :param filter_width: Filter width - :param bt: normalized 3-dB bandwidth-symbol time product - :return: - """ - # http://onlinelibrary.wiley.com/doi/10.1002/9780470041956.app2/pdf - cdef np.ndarray[np.float32_t] k = np.arange(-int(filter_width * samples_per_bit), - int(filter_width * samples_per_bit) + 1, - dtype=np.float32) - cdef float ts = samples_per_bit / sample_rate # symbol time - cdef np.ndarray[np.float32_t] h = np.sqrt((2 * np.pi) / (np.log(2))) * bt / ts * np.exp( - -(((np.sqrt(2) * np.pi) / np.sqrt(np.log(2)) * bt * k / samples_per_bit) ** 2)) - return h / h.sum() - -cpdef modulate_gfsk(unsigned char[:] bit_array, unsigned long pause, unsigned long start, - double a, double freq0, double freq1, double phi, double sample_rate, - unsigned long samples_per_bit, double gauss_bt, double filter_width, iq iq_type): - cdef long long i = 0, index = 0 - cdef long long total_samples = int(len(bit_array) * samples_per_bit + pause) - - cdef np.ndarray[np.float32_t, ndim=1] frequencies = np.empty(total_samples - pause, dtype=np.float32) - cdef long long loop_end = total_samples-pause - - for i in prange(0, loop_end, nogil=True, schedule="static"): - index = (i/samples_per_bit) - frequencies[i] = freq0 if bit_array[index] == 0 else freq1 - - cdef np.ndarray[np.float32_t, ndim=1] t = np.arange(start, start + total_samples - pause, dtype=np.float32) / sample_rate - cdef np.ndarray[np.float32_t, ndim=1] gfir = gauss_fir(sample_rate, samples_per_bit, + cdef np.ndarray[np.float32_t, ndim=1] t = np.arange(start, start + num_values, dtype=np.float32) / sample_rate + cdef np.ndarray[np.float32_t, ndim=1] gfir = gauss_fir(sample_rate, samples_per_symbol, bt=gauss_bt, filter_width=filter_width) if len(frequencies) >= len(gfir): @@ -179,23 +126,30 @@ cpdef modulate_gfsk(unsigned char[:] bit_array, unsigned long pause, unsigned lo # Prevent dimension crash later, because gaussian finite impulse response is longer then param_vector frequencies = np.convolve(gfir, frequencies, mode="same")[:len(frequencies)] - result = np.zeros((total_samples, 2), dtype=get_numpy_dtype(iq_type)) - cdef iq[:, ::1] result_view = result - - cdef np.ndarray[np.float32_t, ndim=1] phases = np.empty(len(frequencies), dtype=np.float32) + cdef np.ndarray[np.float32_t, ndim=1] phases = np.zeros(len(frequencies), dtype=np.float32) phases[0] = phi for i in range(0, len(phases) - 1): # Correct the phase to prevent spiky jumps phases[i + 1] = 2 * M_PI * t[i] * (frequencies[i] - frequencies[i + 1]) + phases[i] - cdef np.ndarray[np.float32_t, ndim=1] arg = (2 * M_PI * frequencies * t + phases) + return np.column_stack((frequencies, phases)) - cdef long long stop = max(0, total_samples-pause) - for i in prange(0, stop, nogil=True, schedule="static"): - result_view[i, 0] = (a * cosf(arg[i])) - result_view[i, 1] = (a * sinf(arg[i])) +cdef np.ndarray[np.float32_t, ndim=1] gauss_fir(float sample_rate, uint32_t samples_per_symbol, + float bt=.5, float filter_width=1.0): + """ - return result + :param filter_width: Filter width + :param bt: normalized 3-dB bandwidth-symbol time product + :return: + """ + # http://onlinelibrary.wiley.com/doi/10.1002/9780470041956.app2/pdf + cdef np.ndarray[np.float32_t] k = np.arange(-int(filter_width * samples_per_symbol), + int(filter_width * samples_per_symbol) + 1, + dtype=np.float32) + cdef float ts = samples_per_symbol / sample_rate # symbol time + cdef np.ndarray[np.float32_t] h = np.sqrt((2 * np.pi) / (np.log(2))) * bt / ts * np.exp( + -(((np.sqrt(2) * np.pi) / np.sqrt(np.log(2)) * bt * k / samples_per_symbol) ** 2)) + return h / h.sum() cdef float calc_costa_alpha(float bw, float damp=1 / sqrt(2)) nogil: # BW in range((2pi/200), (2pi/100)) diff --git a/src/urh/cythonext/util.pxd b/src/urh/cythonext/util.pxd index c9d0701682..dc02ba2751 100644 --- a/src/urh/cythonext/util.pxd +++ b/src/urh/cythonext/util.pxd @@ -5,4 +5,8 @@ ctypedef fused iq: unsigned short float -ctypedef iq[:, ::1] IQ \ No newline at end of file +ctypedef iq[:, ::1] IQ + +from libc.stdint cimport uint64_t, uint8_t, int64_t + +cpdef uint64_t bit_array_to_number(uint8_t[:] bits, int64_t end, int64_t start=*) nogil \ No newline at end of file diff --git a/src/urh/cythonext/util.pyx b/src/urh/cythonext/util.pyx index 5f69278009..20638d4b59 100644 --- a/src/urh/cythonext/util.pyx +++ b/src/urh/cythonext/util.pyx @@ -6,7 +6,7 @@ import numpy as np # because it can lead to OS X error: https://github.com/jopohl/urh/issues/273 # np.import_array() -from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, int64_t from libc.stdlib cimport malloc, calloc, free from cython.parallel import prange from libc.math cimport log10,pow @@ -44,6 +44,19 @@ cpdef np.ndarray[np.float32_t, ndim=2] arr2decibel(np.ndarray[np.complex64_t, nd result[i, j] = factor * log10(arr[i, j].real * arr[i, j].real + arr[i, j].imag * arr[i, j].imag) return result +cpdef uint64_t bit_array_to_number(uint8_t[:] bits, int64_t end, int64_t start=0) nogil: + if end < 1: + return 0 + + cdef long long i, acc = 1 + cdef unsigned long long result = 0 + + for i in range(start, end): + result += bits[end-1-i+start] * acc + acc *= 2 + + return result + cpdef uint64_t arr_to_number(uint8_t[:] inpt, bool reverse = False, unsigned int start = 0): cdef uint64_t result = 0 cdef unsigned int i, len_inpt = len(inpt) diff --git a/src/urh/dev/EndlessSender.py b/src/urh/dev/EndlessSender.py index c0c293a59b..e897362a40 100644 --- a/src/urh/dev/EndlessSender.py +++ b/src/urh/dev/EndlessSender.py @@ -58,7 +58,7 @@ def push_data(self, data: np.ndarray): msg = Message([1, 0] * 16 + [1, 1, 0, 0] * 8 + [0, 0, 1, 1] * 8 + [1, 0, 1, 1, 1, 0, 0, 1, 1, 1] * 4, 0, MessageType("empty_message_type")) modulator = Modulator("test_modulator") - modulator.samples_per_bit = 1000 + modulator.samples_per_symbol = 1000 modulator.carrier_freq_hz = 55e3 logger.debug("Starting endless sender") diff --git a/src/urh/signalprocessing/Modulator.py b/src/urh/signalprocessing/Modulator.py index e55b427132..40530114af 100644 --- a/src/urh/signalprocessing/Modulator.py +++ b/src/urh/signalprocessing/Modulator.py @@ -1,5 +1,6 @@ import array import locale +import math import xml.etree.ElementTree as ET import numpy as np @@ -28,17 +29,19 @@ def __init__(self, name: str): self.carrier_amplitude = 1 self.carrier_phase_deg = 0 self.data = [True, False, True, False] - self.samples_per_bit = 100 + self.samples_per_symbol = 100 self.default_sample_rate = 10 ** 6 self.__sample_rate = None self.modulation_type = 0 + + self.bits_per_symbol = 1 + self.name = name self.gauss_bt = 0.5 # bt product for gaussian filter (GFSK) self.gauss_filter_width = 1 # filter width for gaussian filter (GFSK) - self.param_for_zero = 0 # Freq, Amplitude (0..100%) or Phase (0..360) - self.param_for_one = 100 # Freq, Amplitude (0..100%) or Phase (0..360) + self.parameters = array.array("f", [0, 100]) # Freq, Amplitude (0..100%) or Phase (0..360) def __eq__(self, other): return self.carrier_freq_hz == other.carrier_freq_hz and \ @@ -46,10 +49,10 @@ def __eq__(self, other): self.carrier_phase_deg == other.carrier_phase_deg and \ self.name == other.name and \ self.modulation_type == other.modulation_type and \ - self.samples_per_bit == other.samples_per_bit and \ + self.samples_per_symbol == other.samples_per_symbol and \ + self.bits_per_symbol == other.bits_per_symbol and \ self.sample_rate == other.sample_rate and \ - self.param_for_one == other.param_for_one and \ - self.param_for_zero == other.param_for_zero + self.parameters == other.parameters @staticmethod def get_dtype(): @@ -61,6 +64,30 @@ def get_dtype(): else: return np.float32 + @property + def is_binary_modulation(self): + return self.bits_per_symbol == 1 + + @property + def param_for_zero(self): + assert self.is_binary_modulation + return self.parameters[0] + + @param_for_zero.setter + def param_for_zero(self, value): + assert self.is_binary_modulation + self.parameters[0] = value + + @property + def param_for_one(self): + assert self.is_binary_modulation + return self.parameters[1] + + @param_for_one.setter + def param_for_one(self, value): + assert self.is_binary_modulation + self.parameters[1] = value + @property def sample_rate(self): if self.__sample_rate is not None: @@ -90,7 +117,7 @@ def carrier_phase_str(self): @property def bit_len_str(self): - return self.get_value_with_suffix(self.samples_per_bit, unit="") + return self.get_value_with_suffix(self.samples_per_symbol, unit="") @property def sample_rate_str(self): @@ -124,7 +151,7 @@ def param_for_one_str(self): @property def carrier_data(self): - num_samples = len(self.display_bits) * self.samples_per_bit + num_samples = len(self.display_bits) * self.samples_per_symbol carrier_phase_rad = self.carrier_phase_deg * (np.pi / 180) t = (np.arange(0, num_samples) / self.sample_rate).astype(np.float32) arg = (2 * np.pi * self.carrier_freq_hz * t + carrier_phase_rad).astype(np.float32) @@ -134,12 +161,12 @@ def carrier_data(self): @property def data_scene(self) -> QGraphicsScene: - n = self.samples_per_bit * len(self.display_bits) + n = self.samples_per_symbol * len(self.display_bits) y = np.ones(n, dtype=np.float32) for i, bit in enumerate(self.display_bits): if bit == "0": - y[i*self.samples_per_bit:(i+1)*self.samples_per_bit] = -1.0 + y[i*self.samples_per_symbol:(i + 1) * self.samples_per_symbol] = -1.0 x = np.arange(0, n).astype(np.int64) @@ -173,49 +200,33 @@ def modulate(self, data=None, pause=0, start=0) -> IQArray: dtype = self.get_dtype() a = self.carrier_amplitude * IQArray.min_max_for_dtype(dtype)[1] - # add a variable here to prevent it from being garbage collected in multithreaded cases (Python 3.4) - result = np.zeros(0, dtype=dtype) - type_code = "b" if dtype == np.int8 else "h" if dtype == np.int16 else "f" type_val = array.array(type_code, [0])[0] - if mod_type == "FSK": - result = signal_functions.modulate_fsk(data, pause, start, a, - self.param_for_zero, self.param_for_one, - self.carrier_phase_deg * (np.pi / 180), self.sample_rate, - self.samples_per_bit, type_val) - elif mod_type == "ASK": - a0 = a * (self.param_for_zero / 100) - a1 = a * (self.param_for_one / 100) - result = signal_functions.modulate_ask(data, pause, start, a0, a1, - self.carrier_freq_hz, - self.carrier_phase_deg * (np.pi / 180), self.sample_rate, - self.samples_per_bit, type_val) + parameters = self.parameters + if mod_type == "ASK": + parameters = array.array("f", [a*p/100 for p in parameters]) elif mod_type == "PSK": - phi0 = self.param_for_zero * (np.pi / 180) - phi1 = self.param_for_one * (np.pi / 180) - result = signal_functions.modulate_psk(data, pause, start, a, - self.carrier_freq_hz, - phi0, phi1, self.sample_rate, - self.samples_per_bit, type_val) - elif mod_type == "GFSK": - result = signal_functions.modulate_gfsk(data, pause, start, a, - self.param_for_zero, self.param_for_one, - self.carrier_phase_deg * (np.pi / 180), self.sample_rate, - self.samples_per_bit, self.gauss_bt, self.gauss_filter_width, - type_val) - + parameters = array.array("f", [p * (math.pi / 180) for p in parameters]) + + result = signal_functions.modulate_c(data, self.samples_per_symbol, + mod_type, parameters, self.bits_per_symbol, + a, self.carrier_freq_hz, + self.carrier_phase_deg * (np.pi / 180), + self.sample_rate, pause, start, type_val, + self.gauss_bt, self.gauss_filter_width) return IQArray(result) def to_xml(self, index: int) -> ET.Element: root = ET.Element("modulator") for attr, val in vars(self).items(): - if attr not in ("data", "_Modulator__sample_rate", "default_sample_rate"): + if attr not in ("data", "_Modulator__sample_rate", "default_sample_rate", "parameters"): root.set(attr, str(val)) root.set("sample_rate", str(self.__sample_rate)) root.set("index", str(index)) + root.set("parameters", ",".join(map(str, self.parameters))) return root @@ -241,10 +252,16 @@ def from_xml(tag: ET.Element): setattr(result, attrib, str(value)) elif attrib == "modulation_type": setattr(result, attrib, Formatter.str2val(value, int, 0)) - elif attrib == "samples_per_bit": - setattr(result, attrib, Formatter.str2val(value, int, 100)) + elif attrib == "samples_per_bit" or attrib == "samples_per_symbol": + # samples_per_bit as legacy support for older project files + result.samples_per_symbol = Formatter.str2val(value, int, 100) elif attrib == "sample_rate": result.sample_rate = Formatter.str2val(value, float, 1e6) if value != "None" else None + elif attrib == "parameters": + try: + result.parameters = array.array("f", map(float, value.split(","))) + except ValueError: + continue else: setattr(result, attrib, Formatter.str2val(value, float, 1)) return result diff --git a/tests/PlotTests.py b/tests/PlotTests.py index 2c83e05c18..06f3df1fbe 100644 --- a/tests/PlotTests.py +++ b/tests/PlotTests.py @@ -15,7 +15,7 @@ class PlotTests(unittest.TestCase): def test_plot(self): modulator = Modulator("gfsk") modulator.modulation_type_str = "GFSK" - modulator.samples_per_bit = 100 + modulator.samples_per_symbol = 100 modulator.sample_rate = 1e6 modulator.param_for_one = 20e3 modulator.param_for_zero = 10e3 diff --git a/tests/auto_interpretation/test_message_segmentation.py b/tests/auto_interpretation/test_message_segmentation.py index 3a5c1dc4de..64b7b7c099 100644 --- a/tests/auto_interpretation/test_message_segmentation.py +++ b/tests/auto_interpretation/test_message_segmentation.py @@ -50,7 +50,7 @@ def test_segmentation_ask_50(self): modulator.modulation_type_str = "ASK" modulator.param_for_zero = 50 modulator.param_for_one = 100 - modulator.samples_per_bit = 100 + modulator.samples_per_symbol = 100 msg1 = modulator.modulate("1010101111", pause=10000) msg2 = modulator.modulate("1010101110010101", pause=20000) diff --git a/tests/cli/test_cli_logic.py b/tests/cli/test_cli_logic.py index 42f959b08d..4f2463d4b3 100644 --- a/tests/cli/test_cli_logic.py +++ b/tests/cli/test_cli_logic.py @@ -12,7 +12,7 @@ class TestCLILogic(unittest.TestCase): def test_cli_modulate_messages(self): modulator = Modulator("test") modulator.sample_rate = 2e3 - modulator.samples_per_bit = 100 + modulator.samples_per_symbol = 100 modulator.modulation_type_str = "ASK" modulator.param_for_zero = 0 modulator.param_for_one = 100 diff --git a/tests/cli/test_cli_parsing.py b/tests/cli/test_cli_parsing.py index f752b37ac9..8495bd94fb 100644 --- a/tests/cli/test_cli_parsing.py +++ b/tests/cli/test_cli_parsing.py @@ -29,7 +29,7 @@ def test_build_modulator_from_args(self): modulator = urh_cli.build_modulator_from_args(args) self.assertEqual(modulator.modulation_type_str, "ASK") self.assertEqual(modulator.sample_rate, 2e6) - self.assertEqual(modulator.samples_per_bit, 24) + self.assertEqual(modulator.samples_per_symbol, 24) self.assertEqual(modulator.param_for_zero, 0) self.assertEqual(modulator.param_for_one, 100) self.assertEqual(modulator.carrier_freq_hz, 1337e3) diff --git a/tests/performance/simulator_perfomance.py b/tests/performance/simulator_perfomance.py index d36a4773e8..500f48971f 100644 --- a/tests/performance/simulator_perfomance.py +++ b/tests/performance/simulator_perfomance.py @@ -131,7 +131,7 @@ def test_performance(self): simulator.start() modulator = Modulator("test_modulator") - modulator.samples_per_bit = 100 + modulator.samples_per_symbol = 100 modulator.carrier_freq_hz = 55e3 # yappi.start() diff --git a/tests/test_generator.py b/tests/test_generator.py index 23eca32348..3fd6c931f1 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -53,7 +53,7 @@ def test_generation(self): # Generate Datafile modulator = gframe.modulators[0] modulator.modulation_type = 0 - modulator.samples_per_bit = 300 + modulator.samples_per_symbol = 300 buffer = gframe.prepare_modulation_buffer(gframe.total_modulated_samples, show_error=False) modulated_data = gframe.modulate_data(buffer) filename = os.path.join(QDir.tempPath(), "test_generator.complex") diff --git a/tests/test_modulator.py b/tests/test_modulator.py index 7acd5d6af0..dfd8bae4c8 100644 --- a/tests/test_modulator.py +++ b/tests/test_modulator.py @@ -7,6 +7,7 @@ import numpy as np from PyQt5.QtCore import QDir +from urh.cythonext.signal_functions import modulate_c from urh.signalprocessing.Modulator import Modulator from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer from urh.signalprocessing.Signal import Signal @@ -15,10 +16,10 @@ class TestModulator(unittest.TestCase): def setUp(self): self.modulation_data = array.array("B", [True, False, False, False, True, True, False, True]) - self.samples_per_bit = 100 + self.samples_per_symbol = 100 self.pause = 1000 - self.total_samples = len(self.modulation_data) * self.samples_per_bit + self.pause + self.total_samples = len(self.modulation_data) * self.samples_per_symbol + self.pause def test_ask_fsk_psk_modulation(self): modulations = ["ASK", "FSK", "PSK"] @@ -29,7 +30,7 @@ def test_ask_fsk_psk_modulation(self): filename = "{0}_mod.complex".format(modulation) filename = os.path.join(tmp_dir, filename) modulator.modulation_type = i - modulator.samples_per_bit = self.samples_per_bit + modulator.samples_per_symbol = self.samples_per_symbol if modulation == "ASK": modulator.param_for_zero = 0 @@ -45,7 +46,7 @@ def test_ask_fsk_psk_modulation(self): signal = Signal(filename, modulation) signal.modulation_type = i - signal.bit_len = self.samples_per_bit + signal.bit_len = self.samples_per_symbol if modulation == "ASK": signal.qad_center = 0.5 elif modulation == "FSK": @@ -63,7 +64,7 @@ def test_gfsk(self): modulator = Modulator("gfsk") modulator.modulation_type_str = "FSK" - modulator.samples_per_bit = 100 + modulator.samples_per_symbol = 100 modulator.sample_rate = 1e6 modulator.param_for_one = 20e3 modulator.param_for_zero = -10e3 @@ -84,3 +85,31 @@ def test_performance(self): modulator.modulate([True] * 1000, pause=10000000) elapsed = time.time() - t self.assertLess(elapsed, 0.5) + + def test_c_modulation_method_ask(self): + bits = array.array("B", [1, 0, 1, 0, 1, 1, 0, 0, 0, 1]) + parameters = array.array("f", [0, 0.25, 0.5, 1]) + result = modulate_c(bits, 100, "ASK", parameters, 2, 1, 40e3, 0, 1e6, 1000, 0, parameters[0]) + + #result.tofile("/tmp/test.complex") + + def test_c_modulation_method_fsk(self): + bits = array.array("B", [1, 0, 1, 0, 1, 1, 0, 0, 0, 1]) + parameters = array.array("f", [-10e3, 10e3]) + result = modulate_c(bits, 100, "FSK", parameters, 1, 1, 40e3, 0, 1e6, 1000, 0, parameters[0]) + + #result.tofile("/tmp/test_fsk.complex") + + def test_c_modulation_method_psk(self): + bits = array.array("B", [0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1]) + parameters = array.array("f", [np.pi/4, 3*np.pi/4, 5*np.pi/4, 7*np.pi/4]) + result = modulate_c(bits, 100, "PSK", parameters, 2, 1, 40e3, 0, 1e6, 1000, 0, parameters[0]) + + # result.tofile("/tmp/test_psk.complex") + + def test_c_modulation_method_gfsk(self): + bits = array.array("B", [1, 0, 1, 0, 1, 1, 0, 0, 0, 1]) + parameters = array.array("f", [-10e3, 10e3]) + result = modulate_c(bits, 100, "GFSK", parameters, 1, 1, 40e3, 0, 1e6, 1000, 0, parameters[0]) + + # result.tofile("/tmp/test_gfsk.complex") diff --git a/tests/test_modulator_gui.py b/tests/test_modulator_gui.py index 9f7c8a6fea..0fe85c03b7 100644 --- a/tests/test_modulator_gui.py +++ b/tests/test_modulator_gui.py @@ -59,7 +59,7 @@ def test_edit_data(self): self.dialog.ui.spinBoxBitLength.setValue(1337) self.dialog.ui.spinBoxBitLength.editingFinished.emit() - self.assertEqual(self.dialog.current_modulator.samples_per_bit, 1337) + self.assertEqual(self.dialog.current_modulator.samples_per_symbol, 1337) self.dialog.ui.spinBoxSampleRate.setValue(5e6) self.dialog.ui.spinBoxSampleRate.editingFinished.emit() diff --git a/tests/test_protocol_sniffer.py b/tests/test_protocol_sniffer.py index 184e5b1dae..0d097ae347 100644 --- a/tests/test_protocol_sniffer.py +++ b/tests/test_protocol_sniffer.py @@ -43,7 +43,7 @@ def test_protocol_sniffer(self): data = ["101010", "000111", "1111000"] pause = 10 * bit_len modulator = Modulator("test") - modulator.samples_per_bit = bit_len + modulator.samples_per_symbol = bit_len modulator.sample_rate = sample_rate modulator.modulation_type = modulation_type modulator.param_for_one = 20e3