3 # MorseDistanceModulator - represent Morse symbols using Distance Modulation
 
   5 # Copyright (C) 2015  Antonio Ospite <ao2@ao2.it>
 
   7 # This program is free software: you can redistribute it and/or modify
 
   8 # it under the terms of the GNU General Public License as published by
 
   9 # the Free Software Foundation, either version 3 of the License, or
 
  10 # (at your option) any later version.
 
  12 # This program is distributed in the hope that it will be useful,
 
  13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  15 # GNU General Public License for more details.
 
  17 # You should have received a copy of the GNU General Public License
 
  18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
  21 class SymbolTime(object):
 
  23     In theory the symbol distance (the distance to discriminate symbols) can
 
  24     be arbitrary, and it's only bounded below by the stability of the period
 
  25     time, but in practice it can be necessary to wait a predefined minimum
 
  26     amount of time between periods because of technological limits of the
 
  27     transmitting devices, we call this time the "minimum inter-symbol
 
  31     # pylint: disable=too-few-public-methods
 
  32     def __init__(self, period_min, period_max, multiplier,
 
  33                  min_inter_symbol_distance=0.0):
 
  34         assert multiplier >= 0
 
  35         if (period_min == period_max) and (min_inter_symbol_distance == 0):
 
  36             raise ValueError("If (period_min == period_max) a non-zero",
 
  37                              "inter-symbol distance MUST be specified")
 
  39         symbol_distance = 2 * (period_max - period_min)
 
  40         if symbol_distance == 0:
 
  41             symbol_distance = min_inter_symbol_distance
 
  43         # The time the transmitter has to wait to disambiguate between this
 
  44         # symbol and a different one.
 
  45         self.dist = min_inter_symbol_distance + symbol_distance * multiplier
 
  47         # The minimum time which represents the symbol at the receiving end.
 
  48         self.min = min_inter_symbol_distance + period_min + \
 
  49             symbol_distance * multiplier
 
  51         # The maximum time which represents the symbol at the receiving end
 
  52         self.max = min_inter_symbol_distance + period_min + \
 
  53             symbol_distance * (multiplier + 1)
 
  56 class MorseDistanceModulator(object):
 
  57     def __init__(self, period_min, period_max, pulse_min, pulse_max,
 
  58                  inter_symbol_distance):
 
  59         self.set_parameters(period_min, period_max,
 
  61                             inter_symbol_distance)
 
  63     def set_parameters(self, period_min, period_max, pulse_min, pulse_max,
 
  64                        inter_symbol_distance):
 
  65         self.period_min = period_min
 
  66         self.period_max = period_max
 
  67         self.pulse_min = pulse_min
 
  68         self.pulse_max = pulse_max
 
  69         self.inter_symbol_distance = inter_symbol_distance
 
  71         self.dot_time = SymbolTime(period_min,
 
  73                                    inter_symbol_distance)
 
  74         self.dash_time = SymbolTime(period_min,
 
  76                                     inter_symbol_distance)
 
  77         self.signalspace_time = SymbolTime(period_min,
 
  79                                            inter_symbol_distance)
 
  80         self.wordspace_time = SymbolTime(period_min,
 
  82                                          inter_symbol_distance)
 
  83         self.eom_time = SymbolTime(period_min,
 
  85                                    inter_symbol_distance)
 
  87     def symbol_to_distance(self, symbol):
 
  89             return self.dot_time.dist
 
  91             return self.dash_time.dist
 
  93             return self.signalspace_time.dist
 
  94         elif symbol == "/" or symbol == " / ":
 
  95             return self.wordspace_time.dist
 
  97             return self.eom_time.dist
 
  99         raise ValueError("Unexpected symbol %s" % symbol)
 
 101     def is_same_period(self, distance):
 
 102         return distance > self.pulse_min and distance <= self.pulse_max
 
 104     def distance_to_symbol(self, distance):
 
 105         if distance > self.dot_time.min and \
 
 106            distance <= self.dot_time.max:
 
 109         if distance > self.dash_time.min and \
 
 110            distance <= self.dash_time.max:
 
 113         if distance > self.signalspace_time.min and \
 
 114            distance <= self.signalspace_time.max:
 
 117         if distance > self.wordspace_time.min and \
 
 118            distance <= self.wordspace_time.max:
 
 121         if distance > self.eom_time.min:
 
 124         raise ValueError("Unexpected distance %.2f" % distance)
 
 126     def modulate(self, morse):
 
 127         signals = morse.split(' ')
 
 129         for i, signal in enumerate(signals):
 
 130             for symbol in signal:
 
 131                 distances.append(self.symbol_to_distance(symbol))
 
 133             # Transmit a signal separator only when strictly necessary.
 
 135             # Avoid it in these cases:
 
 136             #  - after the last symbol, because EOM is going to ne transmitted
 
 137             #    anyway and that will mark the end of the last symbol.
 
 138             #  - between words, because the word separator act as a symbol
 
 140             if i != len(signals) - 1 and signals[i + 1] != "/" and signal != "/":
 
 141                 distances.append(self.symbol_to_distance(" "))
 
 143         distances.append(self.symbol_to_distance("EOM"))
 
 145         # Since the Morse signals are encoded in the distance between calls, an
 
 146         # extra call is needed in order for receiver actually get the EOM and
 
 147         # see that the transmission has terminated.
 
 158     min_inter_symbol_distance = pulse_max
 
 160     modulator = MorseDistanceModulator(period_min, period_max,
 
 161                                        pulse_min, pulse_max,
 
 162                                        min_inter_symbol_distance)
 
 164     print(modulator.modulate(morse))
 
 166 if __name__ == "__main__":