-
Notifications
You must be signed in to change notification settings - Fork 0
/
morse.py
149 lines (137 loc) · 5.9 KB
/
morse.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""
Morse kodiranje i dekodiranje
python3 morse.py enc "HELLO FRIEND" 0.06 output.wav
enkodira "HELLO FRIEND" koristeci 0.06s za trajanje tacke i upisuje rezultat u output.wav
python3 morse.py dec input.wav
dekodira sadrzaj fajla input.wav i ispisuje rezultat
"""
import audiolib as a
import sys
# top level params, can be tuned
freq = 300.0 # frequency
# morse code dictionary, space maps to space for convenience
morse = {'A': '.-', 'B': '-...', 'C': '-.-.',
'D': '-..', 'E': '.', 'F': '..-.',
'G': '--.', 'H': '....', 'I': '..',
'J': '.---', 'K': '-.-', 'L': '.-..',
'M': '--', 'N': '-.', 'O': '---',
'P': '.--.', 'Q': '--.-', 'R': '.-.',
'S': '...', 'T': '-', 'U': '..-',
'V': '...-', 'W': '.--', 'X': '-..-',
'Y': '-.--', 'Z': '--..',
'0': '-----', '1': '.----', '2': '..---',
'3': '...--', '4': '....-', '5': '.....',
'6': '-....', '7': '--...', '8': '---..',
'9': '----.',
' ': ' ', '.': '.-.-.-', ',': '--..--', ':': '---...'
}
# encodes the message using tick_len as the unit length and creates wav_filename.wav
def encode(message, tick_len, wav_filename):
print('Message:\"' + message + '\"')
# converts the message to text morse
# after encoding we get 1 space between letters and 3 between words
encoded_message = ''.join([symbol for letter in ' '.join(message) for symbol in morse[letter]])
print('Text morse:\"' + encoded_message + '\"')
# builds the array of frames
frames_per_tick = int(a.framerate * tick_len)
frames = []
phase = 0
# the main ecnoding loop
for symbol in encoded_message:
if symbol == ' ':
new_frames, phase = a.create_frames(0, 2*frames_per_tick, phase)
frames.extend(new_frames) # letter break = 3, word break = 7
if symbol == '.':
new_frames, phase = a.create_frames(0, 1*frames_per_tick, phase)
frames.extend(new_frames) # symbol break = 1
new_frames, phase = a.create_frames(freq, 1*frames_per_tick, phase)
frames.extend(new_frames) # dit = 1
elif symbol == '-':
new_frames, phase = a.create_frames(0, 1*frames_per_tick, phase)
frames.extend(new_frames) # symbol break = 1
new_frames, phase = a.create_frames(freq, 3*frames_per_tick, phase)
frames.extend(new_frames) # dah = 3
# writes to file
a.write_wav(frames, wav_filename)
print('Wrote to file:\"' + wav_filename + '\"')
# adds one block to the array of blocks
def add_block(blocks, curr_block):
blocks.append(curr_block)
# if there is a really short useless block, removes it and merges two surrounding ones
if len(blocks) >= 3 and abs(blocks[-2]) == 1:
blocks.pop()
blocks.pop()
blocks[-1] += curr_block+1
# goes through frames and extracts blocks of sines/silences
def extract_blocks(frames):
# tries to adapt to various tick_lenghts
# kicks really short blocks out (probably noise)
blocks = []
curr_block = 0 # silent blocks are represented by negative values
last_frame = 0
for frame in frames:
if frame == 0: # silent frame
# registers the end of the sine block
if curr_block > 0:
add_block(blocks, curr_block)
curr_block = 0;
curr_block -= 1
else: # sine frame
# registers the end of the silent block
if curr_block < 0:
add_block(blocks, curr_block)
curr_block = 0;
curr_block += 1
# last block
add_block(blocks, curr_block)
# if last blocks is +-1, delete it
if len(blocks) and abs(blocks[-1]) == 1:
blocks.pop()
return blocks
# decodes wav_filename.wav
# > ugly hacks that work should get full marks
def decode(wav_filename):
# opens input file and gets frames
frames = a.read_wav(wav_filename)
print('Read from file:\"' + wav_filename + '\"')
# goes through frames and extracts blocks of sines/silences
blocks = extract_blocks(frames)
# determines the lengths of 5 possible atoms: word_break, letter_break, symbol_break, dit, dah
block_lengths = sorted(set(blocks))
if len(block_lengths) != 5:
print(block_lengths)
raise Exception('more or less than 5 different block lengths, oops')
# yes, I know what you're thinking, another crazy assumption
# we can be clever with less than 5 different block lengths but let's
# just leave it for now and assume that all 5 block types will be present
# we have 5 sorted block lengths
len_to_atom = {
block_lengths[0]: '# #', # word_break
block_lengths[1]: '#', # letter_break
block_lengths[2]: '', # symbol_break, not needed anymore
block_lengths[3]: '.', # dit
block_lengths[4]: '-' # dah
}
encoded_message = ''.join([len_to_atom[block] for block in blocks])
print('Text morse:\"' + encoded_message.replace('#', ' ') + '\"') # debug print
# makes an inverse mapping and decodes text morse to plaintext
inv_morse = {v: k for k, v in morse.items()}
message = ''.join([inv_morse[letter] for letter in encoded_message.split('#')])
print('Message:\"' + message + '\"') # solution
# main function, takes care of parsing the command line arguments
def main():
if len(sys.argv) < 2:
raise Exception('no arguments')
if sys.argv[1] == 'enc':
if len(sys.argv) != 5:
raise Exception('enc expects exactly three arguments')
encode(sys.argv[2], float(sys.argv[3]), sys.argv[4])
elif sys.argv[1] == 'dec':
if len(sys.argv) != 3:
raise Exception('dec expects exactly one argument')
decode(sys.argv[2])
else:
raise Exception('enc/dec are only allowed commands')
# entry point
if __name__ == "__main__":
main()