Skip to content

Morse Code Translator

Abstract

Morse code was the first global digital encoding — used over telegraph wires, ship-to-shore radio, and emergency signaling for over a century. In this project you build a Python translator that converts text to Morse code and back, plays the code as audio beeps using realistic dot-dash-space timing, and supports the full International Morse alphabet including punctuation. Then we add a Tkinter GUI, cross-platform audio (no more Windows-only winsoundwinsound), visual signaling with screen flashes, and an audio decoder that listens to Morse and transcribes it.

You will learn:

  • The exact mapping between characters and Morse symbols.
  • How to invert a dictionary cleanly for reverse lookup.
  • Realistic Morse timing (the 1:3:1:3:7 rule).
  • Cross-platform audio generation with NumPy and sounddevice.
  • How to encode and decode digital signals — a foundation for any communications project.

Prerequisites

  • Python 3.6 or above.
  • A text editor or IDE.
  • Speakers (for audio playback).
  • Familiarity with dictionaries and string iteration.

Install Dependencies

The basic version uses only built-ins. For cross-platform audio:

install
pip install numpy sounddevice
install
pip install numpy sounddevice

Getting Started

Create the project

  1. Create folder morse-code-translatormorse-code-translator.
  2. Inside, create morsecodetranslator.pymorsecodetranslator.py.

Write the code

Morse TranslatorSource
Morse Translator
# Morse Code Translator
 
# Importing modules
import winsound
import time
import sys
 
# Defining variables
morse_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': '----.', ' ': ' ', ',': '--..--', '.': '.-.-.-', '?': '..--..',
    '/': '-..-.', '-': '-....-', '(': '-.--.', ')': '-.--.-'
}
 
# Defining functions
def translate_to_morse_code(text):
    morse_code_text = ''
    for letter in text:
        morse_code_text += morse_code[letter.upper()] + ' '
    return morse_code_text
 
def translate_to_text(morse_code_text):
    text = ''
    morse_code_text += ' '
    letter = ''
    for symbol in morse_code_text:
        if symbol != ' ':
            i = 0
            letter += symbol
        else:
            i += 1
            if i == 2:
                text += ' '
            else:
                text += list(morse_code.keys())[list(morse_code.values()).index(letter)]
                letter = ''
    return text
    
def play_morse_code(morse_code_text):
    for symbol in morse_code_text:
        if symbol == '.':
            winsound.Beep(1000, 100)
        elif symbol == '-':
            winsound.Beep(1000, 300)
        else:
            time.sleep(0.5)
            
def main():
    print('Morse Code Translator')
    print('1. Translate to Morse Code')
    print('2. Translate to Text')
    print('3. Play Morse Code')
    print('4. Exit')
    choice = input('Enter your choice: ')
    if choice == '1':
        text = input('Enter the text to translate to Morse Code: ')
        morse_code_text = translate_to_morse_code(text)
        print('Morse Code: ' + morse_code_text)
        main()
    elif choice == '2':
        morse_code_text = input('Enter the Morse Code to translate to Text: ')
        text = translate_to_text(morse_code_text)
        print('Text: ' + text)
        main()
    elif choice == '3':
        morse_code_text = input('Enter the Morse Code to play: ')
        play_morse_code(morse_code_text)
        main()
    elif choice == '4':
        sys.exit()
    else:
        print('Invalid choice')
        main()
        
# Calling main function
main()
 
Morse Translator
# Morse Code Translator
 
# Importing modules
import winsound
import time
import sys
 
# Defining variables
morse_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': '----.', ' ': ' ', ',': '--..--', '.': '.-.-.-', '?': '..--..',
    '/': '-..-.', '-': '-....-', '(': '-.--.', ')': '-.--.-'
}
 
# Defining functions
def translate_to_morse_code(text):
    morse_code_text = ''
    for letter in text:
        morse_code_text += morse_code[letter.upper()] + ' '
    return morse_code_text
 
def translate_to_text(morse_code_text):
    text = ''
    morse_code_text += ' '
    letter = ''
    for symbol in morse_code_text:
        if symbol != ' ':
            i = 0
            letter += symbol
        else:
            i += 1
            if i == 2:
                text += ' '
            else:
                text += list(morse_code.keys())[list(morse_code.values()).index(letter)]
                letter = ''
    return text
    
def play_morse_code(morse_code_text):
    for symbol in morse_code_text:
        if symbol == '.':
            winsound.Beep(1000, 100)
        elif symbol == '-':
            winsound.Beep(1000, 300)
        else:
            time.sleep(0.5)
            
def main():
    print('Morse Code Translator')
    print('1. Translate to Morse Code')
    print('2. Translate to Text')
    print('3. Play Morse Code')
    print('4. Exit')
    choice = input('Enter your choice: ')
    if choice == '1':
        text = input('Enter the text to translate to Morse Code: ')
        morse_code_text = translate_to_morse_code(text)
        print('Morse Code: ' + morse_code_text)
        main()
    elif choice == '2':
        morse_code_text = input('Enter the Morse Code to translate to Text: ')
        text = translate_to_text(morse_code_text)
        print('Text: ' + text)
        main()
    elif choice == '3':
        morse_code_text = input('Enter the Morse Code to play: ')
        play_morse_code(morse_code_text)
        main()
    elif choice == '4':
        sys.exit()
    else:
        print('Invalid choice')
        main()
        
# Calling main function
main()
 

Run it

command
C:\Users\Your Name\morse-code-translator> python morsecodetranslator.py
1. Translate to Morse Code
2. Translate to Text
3. Play Morse Code
4. Exit
Choice: 1
Text: HELLO
Morse: .... . .-.. .-.. ---
command
C:\Users\Your Name\morse-code-translator> python morsecodetranslator.py
1. Translate to Morse Code
2. Translate to Text
3. Play Morse Code
4. Exit
Choice: 1
Text: HELLO
Morse: .... . .-.. .-.. ---

The Morse Code Table

codes.py
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": "----.",
    ".": ".-.-.-", ",": "--..--", "?": "..--..", "/": "-..-.",
    "(": "-.--.",  ")": "-.--.-", "-": "-....-",
}
TEXT = {v: k for k, v in MORSE.items()}     # reverse lookup, built in one line
codes.py
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": "----.",
    ".": ".-.-.-", ",": "--..--", "?": "..--..", "/": "-..-.",
    "(": "-.--.",  ")": "-.--.-", "-": "-....-",
}
TEXT = {v: k for k, v in MORSE.items()}     # reverse lookup, built in one line

The dictionary-comprehension {v: k for k, v in MORSE.items()}{v: k for k, v in MORSE.items()} is the cleanest way to invert a dict.

Step-by-Step Explanation

1. Text → Morse

encode.py
def to_morse(text: str) -> str:
    parts = []
    for ch in text.upper():
        if ch == " ":
            parts.append("/")             # word separator
        elif ch in MORSE:
            parts.append(MORSE[ch])
        # silently skip unknown characters
    return " ".join(parts)
encode.py
def to_morse(text: str) -> str:
    parts = []
    for ch in text.upper():
        if ch == " ":
            parts.append("/")             # word separator
        elif ch in MORSE:
            parts.append(MORSE[ch])
        # silently skip unknown characters
    return " ".join(parts)
  • Letters are separated by one space.
  • Words are separated by " / "" / " (a slash with spaces around it) — the International convention.

2. Morse → Text

decode.py
def from_morse(morse: str) -> str:
    out = []
    for word in morse.split(" / "):
        letters = []
        for code in word.split():
            if code in TEXT:
                letters.append(TEXT[code])
            else:
                letters.append("?")        # unknown code
        out.append("".join(letters))
    return " ".join(out)
decode.py
def from_morse(morse: str) -> str:
    out = []
    for word in morse.split(" / "):
        letters = []
        for code in word.split():
            if code in TEXT:
                letters.append(TEXT[code])
            else:
                letters.append("?")        # unknown code
        out.append("".join(letters))
    return " ".join(out)

Two split levels: words on //, letters on whitespace.

3. Audio playback

The original used Windows-only winsoundwinsound. Here is the cross-platform version using NumPy + sounddevice:

play.py
import numpy as np
import sounddevice as sd
 
UNIT = 0.08            # seconds per "dit" (dot)
FREQ = 700             # Hz
 
def tone(seconds):
    t = np.linspace(0, seconds, int(seconds * 44100), endpoint=False)
    wave = 0.4 * np.sin(2 * np.pi * FREQ * t)
    sd.play(wave, 44100); sd.wait()
 
def silence(seconds):
    sd.play(np.zeros(int(seconds * 44100)), 44100); sd.wait()
 
def play_morse(morse: str):
    for sym in morse:
        if   sym == ".": tone(UNIT)
        elif sym == "-": tone(UNIT * 3)
        elif sym == " ": silence(UNIT * 3)
        elif sym == "/": silence(UNIT * 7)
        # intra-character gap (between dots/dashes of a letter) = 1 unit
        silence(UNIT)
play.py
import numpy as np
import sounddevice as sd
 
UNIT = 0.08            # seconds per "dit" (dot)
FREQ = 700             # Hz
 
def tone(seconds):
    t = np.linspace(0, seconds, int(seconds * 44100), endpoint=False)
    wave = 0.4 * np.sin(2 * np.pi * FREQ * t)
    sd.play(wave, 44100); sd.wait()
 
def silence(seconds):
    sd.play(np.zeros(int(seconds * 44100)), 44100); sd.wait()
 
def play_morse(morse: str):
    for sym in morse:
        if   sym == ".": tone(UNIT)
        elif sym == "-": tone(UNIT * 3)
        elif sym == " ": silence(UNIT * 3)
        elif sym == "/": silence(UNIT * 7)
        # intra-character gap (between dots/dashes of a letter) = 1 unit
        silence(UNIT)

A clean, smooth sine wave instead of harsh beeps.

Real Morse Timing — The 1:3:1:3:7 Rule

International Morse defines all durations as multiples of one “unit”:

  • Dot (·): 1 unit on.
  • Dash (−): 3 units on.
  • Intra-character gap (between dots/dashes of the same letter): 1 unit off.
  • Inter-character gap (between letters): 3 units off.
  • Inter-word gap (between words): 7 units off.

A speed of “20 WPM” (words per minute) means roughly 60 ms per unit. Slow learners use 100 ms; experts use 30 ms.

Common Mistakes

ProblemCauseFix
KeyError: 'h'KeyError: 'h'Forgot to uppercasech = ch.upper()ch = ch.upper()
Wrong character on decodeWhitespace mismatch (multiple spaces)morse.split()morse.split() (splits on any whitespace)
Reverse lookup wrong characterManual inversion left bugsUse {v: k for k, v in MORSE.items()}{v: k for k, v in MORSE.items()}
Words run together on decodeNo // separatorUse " / "" / " for word breaks
Audio harsh and clickySquare-wave beepsUse a sine wave (NumPy)
Only works on WindowswinsoundwinsoundUse sounddevicesounddevice + NumPy

Variations to Try

1. Visual flasher

Replace audio with a Tkinter window that flashes:

flash.py
def flash(seconds, color="white"):
    label.config(bg=color); root.update(); time.sleep(seconds)
    label.config(bg="black"); root.update(); time.sleep(UNIT)
flash.py
def flash(seconds, color="white"):
    label.config(bg=color); root.update(); time.sleep(seconds)
    label.config(bg="black"); root.update(); time.sleep(UNIT)

Useful for signaling at sea or for accessibility.

2. Tkinter GUI

A window with a text input, output box, and “To Morse” / “To Text” / “Play” / “Stop” buttons. See Currency Exchange Rate Calculator GUI for the pattern.

3. WAV file export

Concatenate the audio array and save as output.wavoutput.wav with scipy.io.wavfile.writescipy.io.wavfile.write.

4. Audio decoder

Use sounddevice.recsounddevice.rec to capture mic input. Detect peaks above a threshold, measure their duration, and classify as dot/dash. Match gaps to find letter and word boundaries. The hardest part is choosing a sensible threshold — adaptive amplitude detection helps.

5. Adjustable speed

A ScaleScale widget controls WPM. Recalculate UNIT = 1.2 / wpmUNIT = 1.2 / wpm on every change.

6. Prosigns

Support <SK><SK> (end of contact), <AR><AR> (end of message), <BT><BT> (new paragraph), <KN><KN> (named station only).

7. Practice mode

Generate random words; player types what they hear. Score and high-score tracking.

8. Light-pulse output

Pair with a Raspberry Pi and an LED — physically blink the Morse code.

9. Network transmission

Send Morse over a TCP socket between two computers — a working “telegraph network”.

10. Multi-language

Cyrillic, Greek, Japanese Wabun all have their own Morse mappings.

Common Morse Phrases

PhraseMorseMeaning
SOS... --- ...... --- ...International distress signal (note: not “Save Our Souls” — chosen for being unambiguous)
CQ-.-. --.--.-. --.-“Calling any station”
73--... ...----... ...--“Best regards” — ham radio sign-off
88---.. ---..---.. ---..“Love and kisses”
QTH--.- - ....--.- - ....“What is your location?”
QRT--.- .-. ---.- .-. -“Stop transmitting”

Real-World Applications

  • Amateur radio (ham) — Morse is still active on shortwave bands.
  • Aviation — VOR/NDB navigation beacons identify themselves in Morse.
  • Emergency signaling — light/sound SOS without electronics.
  • Accessibility — single-switch input device for people with severe motor disabilities.
  • Education — teaching encoding, timing, and digital communication concepts.

Educational Value

  • Dictionary inversion — one of the most common Python idioms.
  • Encoding & decoding — generalizes to any symbol-substitution cipher.
  • Audio generation — sine waves, sample rates, NumPy basics.
  • Timing-sensitive code — what real-time means in practice.
  • Cross-platform thinking — Windows vs. Linux vs. macOS audio APIs.

Next Steps

  • Replace winsoundwinsound with the NumPy + sounddevice version above.
  • Add a Tkinter GUI with text fields and a “play” button.
  • Build an audio decoder that listens to Morse and transcribes it.
  • Add adjustable WPM.
  • Combine with Basic Music Player for richer audio.

Conclusion

You built a translator that handles letters, digits, punctuation, and audio playback — the same building blocks every digital communication protocol uses. From the perspective of encoding theory, Morse is the simplest “variable-length prefix code” you can study, and the same principles power UTF-8 and Huffman coding. Full source on GitHub. Find more encoding projects on Python Central Hub.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did