#!/usr/bin/env python3 # # MorseDistanceModulator - represent Morse symbols using Distance Modulation # # Copyright (C) 2015 Antonio Ospite # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . class SymbolTime(object): """ In theory the symbol distance (the distance to discriminate symbols) can be arbitrary, and it's only bounded below by the stability of the period time, but in practice it can be necessary to wait a predefined minimum amount of time between periods because of technological limits of the transmitting devices, we call this time the "minimum inter-symbol distance". """ # pylint: disable=too-few-public-methods def __init__(self, period_min, period_max, multiplier, min_inter_symbol_distance=0.0): assert multiplier >= 0 if (period_min == period_max) and (min_inter_symbol_distance == 0): raise ValueError("If (period_min == period_max) a non-zero", "min_inter_symbol_distance MUST be specified") symbol_distance = 2 * (period_max - period_min) if symbol_distance == 0: symbol_distance = min_inter_symbol_distance # The time the transmitter has to wait to disambiguate between this # symbol and a different one. self.dist = min_inter_symbol_distance + symbol_distance * multiplier # The minimum time which represents the symbol at the receiving end. self.min = min_inter_symbol_distance + period_min + \ symbol_distance * multiplier # The maximum time which represents the symbol at the receiving end self.max = min_inter_symbol_distance + period_min + \ symbol_distance * (multiplier + 1) class MorseDistanceModulator(object): def __init__(self, period_min, period_max, pulse_min, pulse_max, inter_symbol_distance): self.set_parameters(period_min, period_max, pulse_min, pulse_max, inter_symbol_distance) def set_parameters(self, period_min, period_max, pulse_min, pulse_max, inter_symbol_distance): self.period_min = period_min self.period_max = period_max self.pulse_min = pulse_min self.pulse_max = pulse_max self.inter_symbol_distance = inter_symbol_distance self.dot_time = SymbolTime(period_min, period_max, 0, inter_symbol_distance) self.dash_time = SymbolTime(period_min, period_max, 1, inter_symbol_distance) self.signalspace_time = SymbolTime(period_min, period_max, 2, inter_symbol_distance) self.wordspace_time = SymbolTime(period_min, period_max, 3, inter_symbol_distance) self.eom_time = SymbolTime(period_min, period_max, 4, inter_symbol_distance) def symbol_to_distance(self, symbol): if symbol == ".": return self.dot_time.dist elif symbol == "-": return self.dash_time.dist elif symbol == " ": return self.signalspace_time.dist elif symbol == "/" or symbol == " / ": return self.wordspace_time.dist elif symbol == "EOM": return self.eom_time.dist raise ValueError("Unexpected symbol %s" % symbol) def is_same_period(self, distance): return distance > self.pulse_min and distance <= self.pulse_max def distance_to_symbol(self, distance): if distance > self.dot_time.min and \ distance <= self.dot_time.max: return "." if distance > self.dash_time.min and \ distance <= self.dash_time.max: return "-" if distance > self.signalspace_time.min and \ distance <= self.signalspace_time.max: return " " if distance > self.wordspace_time.min and \ distance <= self.wordspace_time.max: return "/" if distance > self.eom_time.min: return "EOM" raise ValueError("Unexpected distance %.2f" % distance) def modulate(self, morse): signals = morse.split(' ') distances = [] for i, signal in enumerate(signals): for symbol in signal: distances.append(self.symbol_to_distance(symbol)) # Transmit a signal separator only when strictly necessary. # # Avoid it in these cases: # - after the last symbol, because EOM is going to ne transmitted # anyway and that will mark the end of the last symbol. # - between words, because the word separator act as a symbol # separator too. if i != len(signals) - 1 and signals[i + 1] != "/" and signal != "/": distances.append(self.symbol_to_distance(" ")) distances.append(self.symbol_to_distance("EOM")) # Since the Morse symbols are encoded in the distance between periods, # an extra pulse is needed in order for the receiver to actually get # the EOM and see that the transmission has terminated. distances.append(0) return distances def test_modulate(): period_min = 7 period_max = 15 pulse_min = 4.9 pulse_max = 5.2 min_inter_symbol_distance = pulse_max morse = "... -- ..." modulator = MorseDistanceModulator(period_min, period_max, pulse_min, pulse_max, min_inter_symbol_distance) print(modulator.modulate(morse)) if __name__ == "__main__": test_modulate()