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:
pip install numpy sounddevicepip install numpy sounddeviceGetting Started
Create the project
- Create folder
morse-code-translatormorse-code-translator. - Inside, create
morsecodetranslator.pymorsecodetranslator.py.
Write the code
Morse Translator
Source# 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 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
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: .... . .-.. .-.. ---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
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 lineMORSE = {
"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 lineThe 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
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)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
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)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:
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)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
| Problem | Cause | Fix |
|---|---|---|
KeyError: 'h'KeyError: 'h' | Forgot to uppercase | ch = ch.upper()ch = ch.upper() |
| Wrong character on decode | Whitespace mismatch (multiple spaces) | morse.split()morse.split() (splits on any whitespace) |
| Reverse lookup wrong character | Manual inversion left bugs | Use {v: k for k, v in MORSE.items()}{v: k for k, v in MORSE.items()} |
| Words run together on decode | No // separator | Use " / "" / " for word breaks |
| Audio harsh and clicky | Square-wave beeps | Use a sine wave (NumPy) |
| Only works on Windows | winsoundwinsound | Use sounddevicesounddevice + NumPy |
Variations to Try
1. Visual flasher
Replace audio with a Tkinter window that flashes:
def flash(seconds, color="white"):
label.config(bg=color); root.update(); time.sleep(seconds)
label.config(bg="black"); root.update(); time.sleep(UNIT)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
| Phrase | Morse | Meaning |
|---|---|---|
| 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
winsoundwinsoundwith 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 coffeeWas this page helpful?
Let us know how we did
