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__":