Morse Code Straight Key J-38 (Photo credit: Whiskeygonebad)

The other week I stumbled across a post about Morse keyers and decided to write some Python code for sending and understanding morse code. Before presenting the code listings, a little bit of history won’t go amiss.

The first Morse code message was sent on the 24th May, 1844, saying simply:

HAPPY .... .- .--. .--. -.--

MORSE -- --- .-. ... .

CODE -.-. --- -.. .

DAY -.. .- -.--

Remarkably, some 20 years later in 1866 the first transatlantic telegraph cable was completed and on March 10th, 1876, the first voice message was sent. This initial foray into international communications was dubbed the “Victorian Internet”.

The Python code you can download will allow you to generate Morse with a mouse or from a text file. There is also a program for investigating the relationship between the different codes for each alphanumeric character. The latter program will group the codes into 6 categories, which probably helps with learning. I haven’t found this grouping anywhere else so maybe it’s a little unconventional.

Installation (Windows Only)

Firstly, you need to install the lastest version of Python which you can download from here. Secondly, you should install pyglet (pip install pyglet). Thirdly, unpack the contents of this zip file and then change into the directory called morsecode2021:

Finally check out the four python files in the directory, morsecode2021, with your favourite code editor:

  • segment
  • play
  • keyer
  • translate

I’m using Sublime Text for editing and executing python code.

Programs

segment

This program segments the morse code for each character into 6 categories. The categories are:

palindromes

K -.-

R .-.

X -..-

P .--.

dits

E .

I ..

S ...

H ....

dahs

T -

M --

O ---

symmetric (dahs = dits)

A .-

N -.

C -.-.

Z --..

of length 3 with 1 dit or dah

D -..

G --.

W .--

U ..-

of length 4 with 1 dit or dah

B -...

L .-..

F ..-.

V ...-

J .---

Y -.--

Q --.-

The program code

import pyglet
import time
from collections import Counter


code_alpha = {'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': '--..'
             }


code_numeric = {'0': '-----',  '1': '.----',  '2': '..---',
                '3': '...--',  '4': '....-',  '5': '.....',
                '6': '-....',  '7': '--...',  '8': '---..',
                '9': '----.'
               }


def palindrome(s):
    return all(s[i] == s[~i] for i in range(len(s)))


def all_dits(s):
    return all(c == '.' for c in s)


def all_dahs(s):
    return all(c == '-' for c in s)


def equal(s):
    r = Counter(s)
    return r['.'] == r['-']


def onemore(s):
    r = Counter(s)
    return abs(r['.'] - r['-']) == 1


def print_tuple_list(l):
    print('\n\t', end='')
    for t in l:
        for val in t:
            print(val, end='')
        print('  ', end='')
    print('\n')


def print_result(palindromes, dits, dahs, equal_dd, l3, l4):

    print('palindromes: ')
    print_tuple_list(palindromes)
    print('only dits: ')
    print_tuple_list(dits)
    print('only dahs: ')
    print_tuple_list(dahs)
    print('dits = dahs: ')
    print_tuple_list(equal_dd)
    print('length 3 (1 dit or dah): ')
    print_tuple_list(l3)
    print('length 4 (1 dit or dah): ')
    print_tuple_list(l4)


def segment():
    palindromes = []
    dits = []
    dahs = []
    equal_dd = []
    l3 = []
    l4 = []

    for c  in code_alpha.items():
        morse = c[1]
        if all_dits(morse):
            dits.append(c)
        elif all_dahs(morse):
            dahs.append(c)
        elif palindrome(morse):
            palindromes.append(c)
        elif equal(morse):
            equal_dd.append(c)
        elif onemore(morse):
            l3.append(c)
        else:
            l4.append(c)

    print_result(palindromes, dits, dahs, equal_dd, l3, l4)

if __name__ == "__main__":

    segment()

play

This program plays all the alpha numeric characters.

import pyglet
import time


code_alpha = {'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': '--..'
             }


code_numeric = {'0': '-----',  '1': '.----',  '2': '..---',
                '3': '...--',  '4': '....-',  '5': '.....',
                '6': '-....',  '7': '--...',  '8': '---..',
                '9': '----.'
               }


def play_sound(filename, duration):
    source = pyglet.media.load(filename)
    player = pyglet.media.Player()
    player.queue(source)
    player.play()
    time.sleep(duration)


def render(char, morse, key):
    path = 'sound_files/'
    print(char, ' ', morse)
    play_sound(path + char + key + 'morse_code.ogg', 1.5)


def play_chatacters():

    for char, morse  in code_numeric.items():
        render(char, morse, '_number_')

    for char, morse  in code_alpha.items():
        render(char, morse, '_')


if __name__ == "__main__":

    play_chatacters()

keyer

This program allows you to generate Morse code through your sound card. Move the mouse cursor over the graphics window that opens and then use the left and right mouse buttons to generate dits and dahs respectively.

import pyglet
import time

"""
In terms of duration, one "dah" equals 3 "dits".
The pause between each "dah" or "dit" equals one "dit".
Example: when you send the letter 'f' you would do:
'di' (pause 1 dit) 'di' (pause 1 dit) 'dah' (pause 1 dit) dit.
The pause between each letter is 3 dits.
The pause between each word is 5 dits.
"""


def play_sound(filename, duration):
            source = pyglet.media.load(filename)
            player = pyglet.media.Player()
            player.queue(source)
            player.play()
            time.sleep(duration)


def keyer():

    path = 'sound_files/'

    E = path + 'E' + '_morse_code.ogg'
    T = path + 'T' + '_morse_code.ogg'

    window = pyglet.window.Window()

    @window.event
    def on_mouse_press(x, y, button, modifiers):
        if button == pyglet.window.mouse.LEFT:
            play_sound(E, 0.0)
        elif button == pyglet.window.mouse.RIGHT:
            play_sound(T, 0.0)

    @window.event
    def on_draw():
        window.clear()

    pyglet.app.run()


if __name__ == "__main__":

    keyer()

translate

This program reads in a text file and produces the equivalent Morse. The output produced sounds like Morse but might not be acceptable to a professional coder.

import sys
import time
import pyglet


code = {'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': '----.' 
        }

delay = 0.4
D3 = 3 * delay
D5 = 5 * delay


def play_sound(filename, duration):
    source = pyglet.media.load(filename)
    player = pyglet.media.Player()
    player.queue(source)
    player.play()
    time.sleep(duration)


def read_text(filename):
    with open(filename, 'r') as f:
        txt = f.read().strip()
    return txt


def translate():

    path = 'sound_files/'

    msg = read_text('morsecode.txt')

    print(msg)
    
    for char in msg:
        if char == ' ':
            print(' ' * 3, end=' ')
            time.sleep(D5)
        elif char.upper() in code.keys():
            print(code[char.upper()], end=' ')
            play_sound(path + char + '_morse_code.ogg', D3)


if __name__ == "__main__":

    translate()

All I can say is that if you like programming and/or just reading about Morse code then these programs offer a simple place to start.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s