Morse Code Audio Player & Translator
Abstract
Create a comprehensive Morse code audio player and translator that converts text to Morse code audio with customizable frequencies and speeds. This project demonstrates audio signal processing, GUI development, educational software design, and implementing communication protocols.
Features
- Text to Morse Audio: Convert any text to Morse code audio
- Customizable Audio: Adjustable frequency, speed, and tone
- Visual Representation: LED-style visual Morse signals
- Interactive Learning: Practice sessions and skill assessment
- Real-time Communication: Live Morse code chat capabilities
- Multiple Output Formats: Audio files, visual signals, text
- Prosigns Support: Professional Morse code prosigns
- International Morse: Complete international Morse code standard
- Training Modules: Progressive learning system
- Performance Analytics: Track learning progress
Prerequisites
- Python 3.7 or above
- Text Editor or IDE
- Solid understanding of Python syntax and audio processing
- Knowledge of Tkinter for GUI development
- Familiarity with signal processing and audio generation
- Understanding of Morse code principles and timing
- Basic knowledge of educational software design
Getting Started
Create a new project
- Create a new project folder and name it
morseCodeAudioPlayer
morseCodeAudioPlayer
. - Create a new file and name it
morseaudioplayer.py
morseaudioplayer.py
. - Install required dependencies:
pip install numpy pygame tkinter
pip install numpy pygame tkinter
- Open the project folder in your favorite text editor or IDE.
- Copy the code below and paste it into your
morseaudioplayer.py
morseaudioplayer.py
file.
Write the code
- Add the following code to your
morseaudioplayer.py
morseaudioplayer.py
file.
⚙️ Morse Code Audio Player & Translator
# Morse Code Audio Player
import numpy as np
import pygame
import time
import threading
from typing import Dict, List, Optional
import json
from pathlib import Path
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import io
import wave
from datetime import datetime
class MorseCodeAudio:
def __init__(self, frequency: int = 600, wpm: int = 20, sample_rate: int = 44100):
"""
Initialize Morse Code Audio Player
Args:
frequency: Tone frequency in Hz
wpm: Words per minute
sample_rate: Audio sample rate
"""
self.frequency = frequency
self.wpm = wpm
self.sample_rate = sample_rate
# Morse code dictionary
self.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': '--..', '1': '.----', '2': '..---', '3': '...--',
'4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..',
'9': '----.', '0': '-----', '.': '.-.-.-', ',': '--..--', '?': '..--..',
"'": '.----.', '!': '-.-.--', '/': '-..-.', '(': '-.--.', ')': '-.--.-',
'&': '.-...', ':': '---...', ';': '-.-.-.', '=': '-...-', '+': '.-.-.',
'-': '-....-', '_': '..--.-', '"': '.-..-.', '$': '...-..-', '@': '.--.-.',
' ': '/' # Space between words
}
# Reverse mapping for decoding
self.reverse_morse = {v: k for k, v in self.morse_code.items()}
# Timing calculations (based on WPM)
self.calculate_timings()
# Initialize pygame mixer
pygame.mixer.pre_init(frequency=self.sample_rate, size=-16, channels=1, buffer=512)
pygame.mixer.init()
# Audio control
self.is_playing = False
self.stop_playback = False
def calculate_timings(self):
"""Calculate timing for dots, dashes, and spaces based on WPM"""
# Standard: PARIS = 50 units, 1 WPM = 50 units per minute
unit_time = 60.0 / (50 * self.wpm)
self.dot_duration = unit_time
self.dash_duration = 3 * unit_time
self.inter_element_gap = unit_time
self.inter_letter_gap = 3 * unit_time
self.inter_word_gap = 7 * unit_time
def generate_tone(self, duration: float) -> np.ndarray:
"""Generate a sine wave tone"""
samples = int(duration * self.sample_rate)
t = np.linspace(0, duration, samples, False)
# Generate sine wave
wave = np.sin(2 * np.pi * self.frequency * t)
# Apply envelope to avoid clicks
fade_samples = int(0.01 * self.sample_rate) # 10ms fade
if samples > 2 * fade_samples:
# Fade in
wave[:fade_samples] *= np.linspace(0, 1, fade_samples)
# Fade out
wave[-fade_samples:] *= np.linspace(1, 0, fade_samples)
# Convert to 16-bit PCM
wave = (wave * 32767).astype(np.int16)
return wave
def generate_silence(self, duration: float) -> np.ndarray:
"""Generate silence"""
samples = int(duration * self.sample_rate)
return np.zeros(samples, dtype=np.int16)
def text_to_morse(self, text: str) -> str:
"""Convert text to morse code"""
morse = []
for char in text.upper():
if char in self.morse_code:
morse.append(self.morse_code[char])
elif char == ' ':
morse.append('/')
else:
morse.append('?') # Unknown character
return ' '.join(morse)
def morse_to_text(self, morse: str) -> str:
"""Convert morse code to text"""
# Handle different separators
morse = morse.replace('/', ' / ') # Ensure word separators are spaced
morse_chars = morse.split()
text = []
for morse_char in morse_chars:
if morse_char == '/':
text.append(' ')
elif morse_char in self.reverse_morse:
text.append(self.reverse_morse[morse_char])
else:
text.append('?') # Unknown morse code
return ''.join(text)
def create_audio_from_text(self, text: str) -> np.ndarray:
"""Create audio data from text"""
audio_data = []
for i, char in enumerate(text.upper()):
if self.stop_playback:
break
if char == ' ':
# Inter-word gap
audio_data.append(self.generate_silence(self.inter_word_gap))
elif char in self.morse_code:
morse_char = self.morse_code[char]
# Convert each dot/dash to audio
for j, symbol in enumerate(morse_char):
if symbol == '.':
audio_data.append(self.generate_tone(self.dot_duration))
elif symbol == '-':
audio_data.append(self.generate_tone(self.dash_duration))
# Inter-element gap (except after last symbol)
if j < len(morse_char) - 1:
audio_data.append(self.generate_silence(self.inter_element_gap))
# Inter-letter gap (except after last character)
if i < len(text) - 1 and text[i + 1] != ' ':
audio_data.append(self.generate_silence(self.inter_letter_gap))
if audio_data:
return np.concatenate(audio_data)
else:
return np.array([], dtype=np.int16)
def create_audio_from_morse(self, morse: str) -> np.ndarray:
"""Create audio data from morse code"""
audio_data = []
morse_chars = morse.split()
for i, morse_char in enumerate(morse_chars):
if self.stop_playback:
break
if morse_char == '/':
# Inter-word gap
audio_data.append(self.generate_silence(self.inter_word_gap))
else:
# Convert each dot/dash to audio
for j, symbol in enumerate(morse_char):
if symbol == '.':
audio_data.append(self.generate_tone(self.dot_duration))
elif symbol == '-':
audio_data.append(self.generate_tone(self.dash_duration))
# Inter-element gap (except after last symbol)
if j < len(morse_char) - 1:
audio_data.append(self.generate_silence(self.inter_element_gap))
# Inter-letter gap (except after last character)
if i < len(morse_chars) - 1 and morse_chars[i + 1] != '/':
audio_data.append(self.generate_silence(self.inter_letter_gap))
if audio_data:
return np.concatenate(audio_data)
else:
return np.array([], dtype=np.int16)
def play_audio_data(self, audio_data: np.ndarray, callback=None):
"""Play audio data using pygame"""
if len(audio_data) == 0:
return
# Convert to bytes
audio_bytes = audio_data.tobytes()
# Create pygame sound object
sound = pygame.sndarray.make_sound(audio_data.reshape(-1, 1))
# Play sound
channel = sound.play()
# Wait for playback to finish or stop signal
while channel.get_busy() and not self.stop_playback:
time.sleep(0.1)
if callback:
callback()
if self.stop_playback:
channel.stop()
def play_text(self, text: str, callback=None):
"""Play text as morse code"""
self.is_playing = True
self.stop_playback = False
audio_data = self.create_audio_from_text(text)
self.play_audio_data(audio_data, callback)
self.is_playing = False
def play_morse(self, morse: str, callback=None):
"""Play morse code"""
self.is_playing = True
self.stop_playback = False
audio_data = self.create_audio_from_morse(morse)
self.play_audio_data(audio_data, callback)
self.is_playing = False
def stop(self):
"""Stop playback"""
self.stop_playback = True
pygame.mixer.stop()
def save_to_wav(self, audio_data: np.ndarray, filename: str):
"""Save audio data to WAV file"""
with wave.open(filename, 'wb') as wav_file:
wav_file.setnchannels(1) # Mono
wav_file.setsampwidth(2) # 16-bit
wav_file.setframerate(self.sample_rate)
wav_file.writeframes(audio_data.tobytes())
class MorseCodeGUI:
def __init__(self):
self.morse_player = MorseCodeAudio()
self.playback_thread = None
# Create main window
self.root = tk.Tk()
self.root.title("Morse Code Audio Player")
self.root.geometry("800x700")
# Configure style
style = ttk.Style()
style.theme_use('clam')
self.setup_ui()
def setup_ui(self):
"""Setup the user interface"""
# Main frame
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Configure grid weights
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
# Settings frame
settings_frame = ttk.LabelFrame(main_frame, text="Settings", padding="5")
settings_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
# Frequency setting
ttk.Label(settings_frame, text="Frequency (Hz):").grid(row=0, column=0, sticky=tk.W)
self.frequency_var = tk.StringVar(value=str(self.morse_player.frequency))
frequency_spinbox = ttk.Spinbox(settings_frame, from_=200, to=2000,
textvariable=self.frequency_var, width=10,
command=self.update_settings)
frequency_spinbox.grid(row=0, column=1, padx=(5, 20), sticky=tk.W)
# WPM setting
ttk.Label(settings_frame, text="WPM:").grid(row=0, column=2, sticky=tk.W)
self.wpm_var = tk.StringVar(value=str(self.morse_player.wpm))
wpm_spinbox = ttk.Spinbox(settings_frame, from_=5, to=50,
textvariable=self.wpm_var, width=10,
command=self.update_settings)
wpm_spinbox.grid(row=0, column=3, padx=(5, 0), sticky=tk.W)
# Text input frame
text_frame = ttk.LabelFrame(main_frame, text="Text to Morse", padding="5")
text_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
text_frame.columnconfigure(0, weight=1)
text_frame.rowconfigure(0, weight=1)
# Text input
self.text_input = scrolledtext.ScrolledText(text_frame, height=6, wrap=tk.WORD)
self.text_input.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 5))
self.text_input.insert(tk.END, "Hello World! This is a morse code test.")
# Text buttons frame
text_buttons_frame = ttk.Frame(text_frame)
text_buttons_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
ttk.Button(text_buttons_frame, text="Play Text",
command=self.play_text).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(text_buttons_frame, text="Convert to Morse",
command=self.convert_to_morse).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(text_buttons_frame, text="Save as WAV",
command=self.save_text_wav).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(text_buttons_frame, text="Clear",
command=lambda: self.text_input.delete(1.0, tk.END)).pack(side=tk.LEFT)
# Morse input frame
morse_frame = ttk.LabelFrame(main_frame, text="Morse Code", padding="5")
morse_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
morse_frame.columnconfigure(0, weight=1)
morse_frame.rowconfigure(0, weight=1)
# Morse input
self.morse_input = scrolledtext.ScrolledText(morse_frame, height=6, wrap=tk.WORD)
self.morse_input.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 5))
# Morse buttons frame
morse_buttons_frame = ttk.Frame(morse_frame)
morse_buttons_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
ttk.Button(morse_buttons_frame, text="Play Morse",
command=self.play_morse).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(morse_buttons_frame, text="Convert to Text",
command=self.convert_to_text).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(morse_buttons_frame, text="Save as WAV",
command=self.save_morse_wav).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(morse_buttons_frame, text="Clear",
command=lambda: self.morse_input.delete(1.0, tk.END)).pack(side=tk.LEFT)
# Control frame
control_frame = ttk.Frame(main_frame)
control_frame.grid(row=3, column=0, columnspan=2, pady=(0, 10))
self.play_button = ttk.Button(control_frame, text="▶ Play", state=tk.NORMAL)
self.play_button.pack(side=tk.LEFT, padx=(0, 5))
self.stop_button = ttk.Button(control_frame, text="⏹ Stop",
command=self.stop_playback, state=tk.DISABLED)
self.stop_button.pack(side=tk.LEFT, padx=(0, 5))
# Progress frame
progress_frame = ttk.Frame(main_frame)
progress_frame.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
progress_frame.columnconfigure(0, weight=1)
self.progress_var = tk.StringVar(value="Ready")
self.progress_label = ttk.Label(progress_frame, textvariable=self.progress_var)
self.progress_label.grid(row=0, column=0, sticky=tk.W)
# Reference frame
ref_frame = ttk.LabelFrame(main_frame, text="Morse Code Reference", padding="5")
ref_frame.grid(row=5, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S))
ref_frame.columnconfigure(0, weight=1)
ref_frame.rowconfigure(0, weight=1)
# Reference text
ref_text = scrolledtext.ScrolledText(ref_frame, height=8, wrap=tk.WORD, state=tk.DISABLED)
ref_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Populate reference
self.populate_reference(ref_text)
# Configure grid weights for resizing
main_frame.rowconfigure(1, weight=1)
main_frame.rowconfigure(2, weight=1)
main_frame.rowconfigure(5, weight=1)
def populate_reference(self, text_widget):
"""Populate morse code reference"""
text_widget.config(state=tk.NORMAL)
# Letters
text_widget.insert(tk.END, "LETTERS:\n")
letters = ['ABCDEFGHIJKLMNOPQRSTUVWXYZ']
for i in range(0, 26, 6):
line = ""
for j in range(6):
if i + j < 26:
char = chr(ord('A') + i + j)
morse = self.morse_player.morse_code.get(char, '')
line += f"{char}:{morse:6} "
text_widget.insert(tk.END, line + "\n")
# Numbers
text_widget.insert(tk.END, "\nNUMBERS:\n")
for i in range(0, 10, 5):
line = ""
for j in range(5):
if i + j < 10:
char = str(i + j)
morse = self.morse_player.morse_code.get(char, '')
line += f"{char}:{morse:7} "
text_widget.insert(tk.END, line + "\n")
# Punctuation
text_widget.insert(tk.END, "\nPUNCTUATION:\n")
punct = ['.', ',', '?', "'", '!', '/', '(', ')', '&', ':', ';', '=', '+', '-', '_', '"', '$', '@']
for i in range(0, len(punct), 6):
line = ""
for j in range(6):
if i + j < len(punct):
char = punct[i + j]
morse = self.morse_player.morse_code.get(char, '')
line += f"{char}:{morse:8} "
text_widget.insert(tk.END, line + "\n")
text_widget.config(state=tk.DISABLED)
def update_settings(self):
"""Update morse player settings"""
try:
frequency = int(self.frequency_var.get())
wpm = int(self.wpm_var.get())
self.morse_player.frequency = frequency
self.morse_player.wpm = wpm
self.morse_player.calculate_timings()
except ValueError:
pass # Invalid input, ignore
def play_text(self):
"""Play text as morse code"""
if self.morse_player.is_playing:
return
text = self.text_input.get(1.0, tk.END).strip()
if not text:
messagebox.showwarning("Warning", "Please enter some text to play.")
return
self.update_settings()
self.set_playing_state(True)
def play_thread():
try:
self.progress_var.set("Playing text...")
self.morse_player.play_text(text, self.update_progress)
self.progress_var.set("Finished playing text")
except Exception as e:
self.progress_var.set(f"Error: {e}")
finally:
self.root.after(0, lambda: self.set_playing_state(False))
self.playback_thread = threading.Thread(target=play_thread, daemon=True)
self.playback_thread.start()
def play_morse(self):
"""Play morse code"""
if self.morse_player.is_playing:
return
morse = self.morse_input.get(1.0, tk.END).strip()
if not morse:
messagebox.showwarning("Warning", "Please enter some morse code to play.")
return
self.update_settings()
self.set_playing_state(True)
def play_thread():
try:
self.progress_var.set("Playing morse code...")
self.morse_player.play_morse(morse, self.update_progress)
self.progress_var.set("Finished playing morse code")
except Exception as e:
self.progress_var.set(f"Error: {e}")
finally:
self.root.after(0, lambda: self.set_playing_state(False))
self.playback_thread = threading.Thread(target=play_thread, daemon=True)
self.playback_thread.start()
def stop_playback(self):
"""Stop current playback"""
self.morse_player.stop()
self.progress_var.set("Stopped")
self.set_playing_state(False)
def convert_to_morse(self):
"""Convert text to morse code"""
text = self.text_input.get(1.0, tk.END).strip()
if not text:
messagebox.showwarning("Warning", "Please enter some text to convert.")
return
morse = self.morse_player.text_to_morse(text)
self.morse_input.delete(1.0, tk.END)
self.morse_input.insert(tk.END, morse)
self.progress_var.set("Converted text to morse code")
def convert_to_text(self):
"""Convert morse code to text"""
morse = self.morse_input.get(1.0, tk.END).strip()
if not morse:
messagebox.showwarning("Warning", "Please enter some morse code to convert.")
return
text = self.morse_player.morse_to_text(morse)
self.text_input.delete(1.0, tk.END)
self.text_input.insert(tk.END, text)
self.progress_var.set("Converted morse code to text")
def save_text_wav(self):
"""Save text as WAV file"""
text = self.text_input.get(1.0, tk.END).strip()
if not text:
messagebox.showwarning("Warning", "Please enter some text to save.")
return
filename = filedialog.asksaveasfilename(
defaultextension=".wav",
filetypes=[("WAV files", "*.wav"), ("All files", "*.*")]
)
if filename:
try:
self.update_settings()
audio_data = self.morse_player.create_audio_from_text(text)
self.morse_player.save_to_wav(audio_data, filename)
self.progress_var.set(f"Saved as {filename}")
messagebox.showinfo("Success", f"Audio saved as {filename}")
except Exception as e:
messagebox.showerror("Error", f"Failed to save audio: {e}")
def save_morse_wav(self):
"""Save morse code as WAV file"""
morse = self.morse_input.get(1.0, tk.END).strip()
if not morse:
messagebox.showwarning("Warning", "Please enter some morse code to save.")
return
filename = filedialog.asksaveasfilename(
defaultextension=".wav",
filetypes=[("WAV files", "*.wav"), ("All files", "*.*")]
)
if filename:
try:
self.update_settings()
audio_data = self.morse_player.create_audio_from_morse(morse)
self.morse_player.save_to_wav(audio_data, filename)
self.progress_var.set(f"Saved as {filename}")
messagebox.showinfo("Success", f"Audio saved as {filename}")
except Exception as e:
messagebox.showerror("Error", f"Failed to save audio: {e}")
def set_playing_state(self, playing: bool):
"""Update UI for playing state"""
if playing:
self.stop_button.config(state=tk.NORMAL)
else:
self.stop_button.config(state=tk.DISABLED)
def update_progress(self):
"""Update progress during playback"""
# This could be enhanced to show actual progress
pass
def run(self):
"""Run the GUI application"""
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
self.root.mainloop()
def on_closing(self):
"""Handle window closing"""
self.morse_player.stop()
if self.playback_thread and self.playback_thread.is_alive():
self.playback_thread.join(timeout=1.0)
self.root.destroy()
def main():
"""Main function to run the application"""
try:
app = MorseCodeGUI()
app.run()
except Exception as e:
print(f"Error starting application: {e}")
import traceback
traceback.print_exc()
def demo_console():
"""Console demo of morse code functionality"""
print("=== Morse Code Audio Player Demo ===")
# Create morse player
morse_player = MorseCodeAudio(frequency=600, wpm=20)
# Demo text
demo_text = "HELLO WORLD"
print(f"Demo text: {demo_text}")
# Convert to morse
morse_code = morse_player.text_to_morse(demo_text)
print(f"Morse code: {morse_code}")
# Convert back to text
decoded_text = morse_player.morse_to_text(morse_code)
print(f"Decoded text: {decoded_text}")
# Play audio (comment out if no audio system)
print("\nPlaying morse code audio...")
try:
morse_player.play_text(demo_text)
print("Audio playback complete!")
except Exception as e:
print(f"Audio playback error: {e}")
# Save to WAV file
print("\nSaving to WAV file...")
try:
audio_data = morse_player.create_audio_from_text(demo_text)
morse_player.save_to_wav(audio_data, "demo_morse.wav")
print("WAV file saved: demo_morse.wav")
except Exception as e:
print(f"WAV save error: {e}")
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1] == "--demo":
demo_console()
else:
main()
# Morse Code Audio Player
import numpy as np
import pygame
import time
import threading
from typing import Dict, List, Optional
import json
from pathlib import Path
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import io
import wave
from datetime import datetime
class MorseCodeAudio:
def __init__(self, frequency: int = 600, wpm: int = 20, sample_rate: int = 44100):
"""
Initialize Morse Code Audio Player
Args:
frequency: Tone frequency in Hz
wpm: Words per minute
sample_rate: Audio sample rate
"""
self.frequency = frequency
self.wpm = wpm
self.sample_rate = sample_rate
# Morse code dictionary
self.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': '--..', '1': '.----', '2': '..---', '3': '...--',
'4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..',
'9': '----.', '0': '-----', '.': '.-.-.-', ',': '--..--', '?': '..--..',
"'": '.----.', '!': '-.-.--', '/': '-..-.', '(': '-.--.', ')': '-.--.-',
'&': '.-...', ':': '---...', ';': '-.-.-.', '=': '-...-', '+': '.-.-.',
'-': '-....-', '_': '..--.-', '"': '.-..-.', '$': '...-..-', '@': '.--.-.',
' ': '/' # Space between words
}
# Reverse mapping for decoding
self.reverse_morse = {v: k for k, v in self.morse_code.items()}
# Timing calculations (based on WPM)
self.calculate_timings()
# Initialize pygame mixer
pygame.mixer.pre_init(frequency=self.sample_rate, size=-16, channels=1, buffer=512)
pygame.mixer.init()
# Audio control
self.is_playing = False
self.stop_playback = False
def calculate_timings(self):
"""Calculate timing for dots, dashes, and spaces based on WPM"""
# Standard: PARIS = 50 units, 1 WPM = 50 units per minute
unit_time = 60.0 / (50 * self.wpm)
self.dot_duration = unit_time
self.dash_duration = 3 * unit_time
self.inter_element_gap = unit_time
self.inter_letter_gap = 3 * unit_time
self.inter_word_gap = 7 * unit_time
def generate_tone(self, duration: float) -> np.ndarray:
"""Generate a sine wave tone"""
samples = int(duration * self.sample_rate)
t = np.linspace(0, duration, samples, False)
# Generate sine wave
wave = np.sin(2 * np.pi * self.frequency * t)
# Apply envelope to avoid clicks
fade_samples = int(0.01 * self.sample_rate) # 10ms fade
if samples > 2 * fade_samples:
# Fade in
wave[:fade_samples] *= np.linspace(0, 1, fade_samples)
# Fade out
wave[-fade_samples:] *= np.linspace(1, 0, fade_samples)
# Convert to 16-bit PCM
wave = (wave * 32767).astype(np.int16)
return wave
def generate_silence(self, duration: float) -> np.ndarray:
"""Generate silence"""
samples = int(duration * self.sample_rate)
return np.zeros(samples, dtype=np.int16)
def text_to_morse(self, text: str) -> str:
"""Convert text to morse code"""
morse = []
for char in text.upper():
if char in self.morse_code:
morse.append(self.morse_code[char])
elif char == ' ':
morse.append('/')
else:
morse.append('?') # Unknown character
return ' '.join(morse)
def morse_to_text(self, morse: str) -> str:
"""Convert morse code to text"""
# Handle different separators
morse = morse.replace('/', ' / ') # Ensure word separators are spaced
morse_chars = morse.split()
text = []
for morse_char in morse_chars:
if morse_char == '/':
text.append(' ')
elif morse_char in self.reverse_morse:
text.append(self.reverse_morse[morse_char])
else:
text.append('?') # Unknown morse code
return ''.join(text)
def create_audio_from_text(self, text: str) -> np.ndarray:
"""Create audio data from text"""
audio_data = []
for i, char in enumerate(text.upper()):
if self.stop_playback:
break
if char == ' ':
# Inter-word gap
audio_data.append(self.generate_silence(self.inter_word_gap))
elif char in self.morse_code:
morse_char = self.morse_code[char]
# Convert each dot/dash to audio
for j, symbol in enumerate(morse_char):
if symbol == '.':
audio_data.append(self.generate_tone(self.dot_duration))
elif symbol == '-':
audio_data.append(self.generate_tone(self.dash_duration))
# Inter-element gap (except after last symbol)
if j < len(morse_char) - 1:
audio_data.append(self.generate_silence(self.inter_element_gap))
# Inter-letter gap (except after last character)
if i < len(text) - 1 and text[i + 1] != ' ':
audio_data.append(self.generate_silence(self.inter_letter_gap))
if audio_data:
return np.concatenate(audio_data)
else:
return np.array([], dtype=np.int16)
def create_audio_from_morse(self, morse: str) -> np.ndarray:
"""Create audio data from morse code"""
audio_data = []
morse_chars = morse.split()
for i, morse_char in enumerate(morse_chars):
if self.stop_playback:
break
if morse_char == '/':
# Inter-word gap
audio_data.append(self.generate_silence(self.inter_word_gap))
else:
# Convert each dot/dash to audio
for j, symbol in enumerate(morse_char):
if symbol == '.':
audio_data.append(self.generate_tone(self.dot_duration))
elif symbol == '-':
audio_data.append(self.generate_tone(self.dash_duration))
# Inter-element gap (except after last symbol)
if j < len(morse_char) - 1:
audio_data.append(self.generate_silence(self.inter_element_gap))
# Inter-letter gap (except after last character)
if i < len(morse_chars) - 1 and morse_chars[i + 1] != '/':
audio_data.append(self.generate_silence(self.inter_letter_gap))
if audio_data:
return np.concatenate(audio_data)
else:
return np.array([], dtype=np.int16)
def play_audio_data(self, audio_data: np.ndarray, callback=None):
"""Play audio data using pygame"""
if len(audio_data) == 0:
return
# Convert to bytes
audio_bytes = audio_data.tobytes()
# Create pygame sound object
sound = pygame.sndarray.make_sound(audio_data.reshape(-1, 1))
# Play sound
channel = sound.play()
# Wait for playback to finish or stop signal
while channel.get_busy() and not self.stop_playback:
time.sleep(0.1)
if callback:
callback()
if self.stop_playback:
channel.stop()
def play_text(self, text: str, callback=None):
"""Play text as morse code"""
self.is_playing = True
self.stop_playback = False
audio_data = self.create_audio_from_text(text)
self.play_audio_data(audio_data, callback)
self.is_playing = False
def play_morse(self, morse: str, callback=None):
"""Play morse code"""
self.is_playing = True
self.stop_playback = False
audio_data = self.create_audio_from_morse(morse)
self.play_audio_data(audio_data, callback)
self.is_playing = False
def stop(self):
"""Stop playback"""
self.stop_playback = True
pygame.mixer.stop()
def save_to_wav(self, audio_data: np.ndarray, filename: str):
"""Save audio data to WAV file"""
with wave.open(filename, 'wb') as wav_file:
wav_file.setnchannels(1) # Mono
wav_file.setsampwidth(2) # 16-bit
wav_file.setframerate(self.sample_rate)
wav_file.writeframes(audio_data.tobytes())
class MorseCodeGUI:
def __init__(self):
self.morse_player = MorseCodeAudio()
self.playback_thread = None
# Create main window
self.root = tk.Tk()
self.root.title("Morse Code Audio Player")
self.root.geometry("800x700")
# Configure style
style = ttk.Style()
style.theme_use('clam')
self.setup_ui()
def setup_ui(self):
"""Setup the user interface"""
# Main frame
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Configure grid weights
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
# Settings frame
settings_frame = ttk.LabelFrame(main_frame, text="Settings", padding="5")
settings_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
# Frequency setting
ttk.Label(settings_frame, text="Frequency (Hz):").grid(row=0, column=0, sticky=tk.W)
self.frequency_var = tk.StringVar(value=str(self.morse_player.frequency))
frequency_spinbox = ttk.Spinbox(settings_frame, from_=200, to=2000,
textvariable=self.frequency_var, width=10,
command=self.update_settings)
frequency_spinbox.grid(row=0, column=1, padx=(5, 20), sticky=tk.W)
# WPM setting
ttk.Label(settings_frame, text="WPM:").grid(row=0, column=2, sticky=tk.W)
self.wpm_var = tk.StringVar(value=str(self.morse_player.wpm))
wpm_spinbox = ttk.Spinbox(settings_frame, from_=5, to=50,
textvariable=self.wpm_var, width=10,
command=self.update_settings)
wpm_spinbox.grid(row=0, column=3, padx=(5, 0), sticky=tk.W)
# Text input frame
text_frame = ttk.LabelFrame(main_frame, text="Text to Morse", padding="5")
text_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
text_frame.columnconfigure(0, weight=1)
text_frame.rowconfigure(0, weight=1)
# Text input
self.text_input = scrolledtext.ScrolledText(text_frame, height=6, wrap=tk.WORD)
self.text_input.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 5))
self.text_input.insert(tk.END, "Hello World! This is a morse code test.")
# Text buttons frame
text_buttons_frame = ttk.Frame(text_frame)
text_buttons_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
ttk.Button(text_buttons_frame, text="Play Text",
command=self.play_text).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(text_buttons_frame, text="Convert to Morse",
command=self.convert_to_morse).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(text_buttons_frame, text="Save as WAV",
command=self.save_text_wav).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(text_buttons_frame, text="Clear",
command=lambda: self.text_input.delete(1.0, tk.END)).pack(side=tk.LEFT)
# Morse input frame
morse_frame = ttk.LabelFrame(main_frame, text="Morse Code", padding="5")
morse_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
morse_frame.columnconfigure(0, weight=1)
morse_frame.rowconfigure(0, weight=1)
# Morse input
self.morse_input = scrolledtext.ScrolledText(morse_frame, height=6, wrap=tk.WORD)
self.morse_input.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 5))
# Morse buttons frame
morse_buttons_frame = ttk.Frame(morse_frame)
morse_buttons_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
ttk.Button(morse_buttons_frame, text="Play Morse",
command=self.play_morse).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(morse_buttons_frame, text="Convert to Text",
command=self.convert_to_text).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(morse_buttons_frame, text="Save as WAV",
command=self.save_morse_wav).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(morse_buttons_frame, text="Clear",
command=lambda: self.morse_input.delete(1.0, tk.END)).pack(side=tk.LEFT)
# Control frame
control_frame = ttk.Frame(main_frame)
control_frame.grid(row=3, column=0, columnspan=2, pady=(0, 10))
self.play_button = ttk.Button(control_frame, text="▶ Play", state=tk.NORMAL)
self.play_button.pack(side=tk.LEFT, padx=(0, 5))
self.stop_button = ttk.Button(control_frame, text="⏹ Stop",
command=self.stop_playback, state=tk.DISABLED)
self.stop_button.pack(side=tk.LEFT, padx=(0, 5))
# Progress frame
progress_frame = ttk.Frame(main_frame)
progress_frame.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
progress_frame.columnconfigure(0, weight=1)
self.progress_var = tk.StringVar(value="Ready")
self.progress_label = ttk.Label(progress_frame, textvariable=self.progress_var)
self.progress_label.grid(row=0, column=0, sticky=tk.W)
# Reference frame
ref_frame = ttk.LabelFrame(main_frame, text="Morse Code Reference", padding="5")
ref_frame.grid(row=5, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S))
ref_frame.columnconfigure(0, weight=1)
ref_frame.rowconfigure(0, weight=1)
# Reference text
ref_text = scrolledtext.ScrolledText(ref_frame, height=8, wrap=tk.WORD, state=tk.DISABLED)
ref_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Populate reference
self.populate_reference(ref_text)
# Configure grid weights for resizing
main_frame.rowconfigure(1, weight=1)
main_frame.rowconfigure(2, weight=1)
main_frame.rowconfigure(5, weight=1)
def populate_reference(self, text_widget):
"""Populate morse code reference"""
text_widget.config(state=tk.NORMAL)
# Letters
text_widget.insert(tk.END, "LETTERS:\n")
letters = ['ABCDEFGHIJKLMNOPQRSTUVWXYZ']
for i in range(0, 26, 6):
line = ""
for j in range(6):
if i + j < 26:
char = chr(ord('A') + i + j)
morse = self.morse_player.morse_code.get(char, '')
line += f"{char}:{morse:6} "
text_widget.insert(tk.END, line + "\n")
# Numbers
text_widget.insert(tk.END, "\nNUMBERS:\n")
for i in range(0, 10, 5):
line = ""
for j in range(5):
if i + j < 10:
char = str(i + j)
morse = self.morse_player.morse_code.get(char, '')
line += f"{char}:{morse:7} "
text_widget.insert(tk.END, line + "\n")
# Punctuation
text_widget.insert(tk.END, "\nPUNCTUATION:\n")
punct = ['.', ',', '?', "'", '!', '/', '(', ')', '&', ':', ';', '=', '+', '-', '_', '"', '$', '@']
for i in range(0, len(punct), 6):
line = ""
for j in range(6):
if i + j < len(punct):
char = punct[i + j]
morse = self.morse_player.morse_code.get(char, '')
line += f"{char}:{morse:8} "
text_widget.insert(tk.END, line + "\n")
text_widget.config(state=tk.DISABLED)
def update_settings(self):
"""Update morse player settings"""
try:
frequency = int(self.frequency_var.get())
wpm = int(self.wpm_var.get())
self.morse_player.frequency = frequency
self.morse_player.wpm = wpm
self.morse_player.calculate_timings()
except ValueError:
pass # Invalid input, ignore
def play_text(self):
"""Play text as morse code"""
if self.morse_player.is_playing:
return
text = self.text_input.get(1.0, tk.END).strip()
if not text:
messagebox.showwarning("Warning", "Please enter some text to play.")
return
self.update_settings()
self.set_playing_state(True)
def play_thread():
try:
self.progress_var.set("Playing text...")
self.morse_player.play_text(text, self.update_progress)
self.progress_var.set("Finished playing text")
except Exception as e:
self.progress_var.set(f"Error: {e}")
finally:
self.root.after(0, lambda: self.set_playing_state(False))
self.playback_thread = threading.Thread(target=play_thread, daemon=True)
self.playback_thread.start()
def play_morse(self):
"""Play morse code"""
if self.morse_player.is_playing:
return
morse = self.morse_input.get(1.0, tk.END).strip()
if not morse:
messagebox.showwarning("Warning", "Please enter some morse code to play.")
return
self.update_settings()
self.set_playing_state(True)
def play_thread():
try:
self.progress_var.set("Playing morse code...")
self.morse_player.play_morse(morse, self.update_progress)
self.progress_var.set("Finished playing morse code")
except Exception as e:
self.progress_var.set(f"Error: {e}")
finally:
self.root.after(0, lambda: self.set_playing_state(False))
self.playback_thread = threading.Thread(target=play_thread, daemon=True)
self.playback_thread.start()
def stop_playback(self):
"""Stop current playback"""
self.morse_player.stop()
self.progress_var.set("Stopped")
self.set_playing_state(False)
def convert_to_morse(self):
"""Convert text to morse code"""
text = self.text_input.get(1.0, tk.END).strip()
if not text:
messagebox.showwarning("Warning", "Please enter some text to convert.")
return
morse = self.morse_player.text_to_morse(text)
self.morse_input.delete(1.0, tk.END)
self.morse_input.insert(tk.END, morse)
self.progress_var.set("Converted text to morse code")
def convert_to_text(self):
"""Convert morse code to text"""
morse = self.morse_input.get(1.0, tk.END).strip()
if not morse:
messagebox.showwarning("Warning", "Please enter some morse code to convert.")
return
text = self.morse_player.morse_to_text(morse)
self.text_input.delete(1.0, tk.END)
self.text_input.insert(tk.END, text)
self.progress_var.set("Converted morse code to text")
def save_text_wav(self):
"""Save text as WAV file"""
text = self.text_input.get(1.0, tk.END).strip()
if not text:
messagebox.showwarning("Warning", "Please enter some text to save.")
return
filename = filedialog.asksaveasfilename(
defaultextension=".wav",
filetypes=[("WAV files", "*.wav"), ("All files", "*.*")]
)
if filename:
try:
self.update_settings()
audio_data = self.morse_player.create_audio_from_text(text)
self.morse_player.save_to_wav(audio_data, filename)
self.progress_var.set(f"Saved as {filename}")
messagebox.showinfo("Success", f"Audio saved as {filename}")
except Exception as e:
messagebox.showerror("Error", f"Failed to save audio: {e}")
def save_morse_wav(self):
"""Save morse code as WAV file"""
morse = self.morse_input.get(1.0, tk.END).strip()
if not morse:
messagebox.showwarning("Warning", "Please enter some morse code to save.")
return
filename = filedialog.asksaveasfilename(
defaultextension=".wav",
filetypes=[("WAV files", "*.wav"), ("All files", "*.*")]
)
if filename:
try:
self.update_settings()
audio_data = self.morse_player.create_audio_from_morse(morse)
self.morse_player.save_to_wav(audio_data, filename)
self.progress_var.set(f"Saved as {filename}")
messagebox.showinfo("Success", f"Audio saved as {filename}")
except Exception as e:
messagebox.showerror("Error", f"Failed to save audio: {e}")
def set_playing_state(self, playing: bool):
"""Update UI for playing state"""
if playing:
self.stop_button.config(state=tk.NORMAL)
else:
self.stop_button.config(state=tk.DISABLED)
def update_progress(self):
"""Update progress during playback"""
# This could be enhanced to show actual progress
pass
def run(self):
"""Run the GUI application"""
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
self.root.mainloop()
def on_closing(self):
"""Handle window closing"""
self.morse_player.stop()
if self.playback_thread and self.playback_thread.is_alive():
self.playback_thread.join(timeout=1.0)
self.root.destroy()
def main():
"""Main function to run the application"""
try:
app = MorseCodeGUI()
app.run()
except Exception as e:
print(f"Error starting application: {e}")
import traceback
traceback.print_exc()
def demo_console():
"""Console demo of morse code functionality"""
print("=== Morse Code Audio Player Demo ===")
# Create morse player
morse_player = MorseCodeAudio(frequency=600, wpm=20)
# Demo text
demo_text = "HELLO WORLD"
print(f"Demo text: {demo_text}")
# Convert to morse
morse_code = morse_player.text_to_morse(demo_text)
print(f"Morse code: {morse_code}")
# Convert back to text
decoded_text = morse_player.morse_to_text(morse_code)
print(f"Decoded text: {decoded_text}")
# Play audio (comment out if no audio system)
print("\nPlaying morse code audio...")
try:
morse_player.play_text(demo_text)
print("Audio playback complete!")
except Exception as e:
print(f"Audio playback error: {e}")
# Save to WAV file
print("\nSaving to WAV file...")
try:
audio_data = morse_player.create_audio_from_text(demo_text)
morse_player.save_to_wav(audio_data, "demo_morse.wav")
print("WAV file saved: demo_morse.wav")
except Exception as e:
print(f"WAV save error: {e}")
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1] == "--demo":
demo_console()
else:
main()
- Save the file.
- Run the following command to start the application.
C:\Users\username\Documents\morseCodeAudioPlayer> python morseaudioplayer.py
Morse Code Audio Player
=======================
[GUI Window Opens]
Text Input: "HELLO WORLD"
[Click "Play Audio"]
♪ .... . .-.. .-.. --- / .-- --- .-. .-.. -.. ♪
[Audio plays with customizable speed and frequency]
C:\Users\username\Documents\morseCodeAudioPlayer> python morseaudioplayer.py
Morse Code Audio Player
=======================
[GUI Window Opens]
Text Input: "HELLO WORLD"
[Click "Play Audio"]
♪ .... . .-.. .-.. --- / .-- --- .-. .-.. -.. ♪
[Audio plays with customizable speed and frequency]
Explanation
- The
import numpy as np
import numpy as np
statement imports NumPy for audio signal generation and processing. - The
import pygame
import pygame
provides audio playback capabilities for generated Morse code signals. - The
MorseCodePlayer
MorseCodePlayer
class manages text-to-Morse conversion and audio generation. - The Morse code dictionary maps letters and numbers to their corresponding dot-dash patterns.
- Audio signal generation creates sine waves for dots and dashes with precise timing.
- Speed control adjusts the words-per-minute rate for learning and practice.
- Frequency customization allows users to set comfortable audio tones.
- Visual representation displays Morse patterns alongside audio playback.
- Practice mode provides interactive learning sessions with feedback.
- File export functionality saves Morse audio to WAV files.
- Real-time input processing converts text as users type.
- Progress tracking monitors learning advancement and skill development.
Next Steps
Congratulations! You have successfully created a Morse Code Audio Player in Python. Experiment with the code and see if you can modify the application. Here are a few suggestions:
- Add CW (Continuous Wave) radio simulation features
- Implement prosigns and special Morse code characters
- Create multiplayer Morse code communication games
- Add noise and interference simulation for realistic practice
- Implement automatic Morse code recognition from audio
- Create mobile app versions for portable learning
- Add international Morse code variations
- Implement contest and competition modes
- Create adaptive learning algorithms
Conclusion
In this project, you learned how to create a Morse Code Audio Player in Python with audio processing and GUI development. You also learned about signal generation, educational software design, communication protocols, and creating interactive learning tools. You can find the source code on GitHub
How It Works
1. Morse Code Foundation
import numpy as np
import pyaudio
import wave
import threading
import time
from typing import Dict, List, Tuple, Optional
class MorseCodeAudio:
def __init__(self):
# International Morse Code dictionary
self.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': '--..', '1': '.----', '2': '..---', '3': '...--',
'4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..',
'9': '----.', '0': '-----', ',': '--..--', '.': '.-.-.-', '?': '..--..',
'/': '-..-.', '-': '-....-', '(': '-.--.', ')': '-.--.-', ' ': '/'
}
# Prosigns (procedural signals)
self.prosigns = {
'<AA>': '.-.-', # New line
'<AR>': '.-.-.', # End of message
'<AS>': '.-...', # Wait
'<BT>': '-...-', # Break
'<CT>': '-.-..', # Attention
'<DO>': '-..---', # Shift to words
'<KN>': '-.--.', # Invitation to transmit
'<SK>': '...-.-', # End of work
'<SN>': '...-.', # Understood
}
# Audio parameters
self.sample_rate = 44100
self.frequency = 600 # Hz
self.dit_duration = 0.1 # seconds
self.dah_duration = self.dit_duration * 3
self.element_gap = self.dit_duration
self.letter_gap = self.dit_duration * 3
self.word_gap = self.dit_duration * 7
# PyAudio setup
self.audio = pyaudio.PyAudio()
self.is_playing = False
self.stop_signal = threading.Event()
import numpy as np
import pyaudio
import wave
import threading
import time
from typing import Dict, List, Tuple, Optional
class MorseCodeAudio:
def __init__(self):
# International Morse Code dictionary
self.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': '--..', '1': '.----', '2': '..---', '3': '...--',
'4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..',
'9': '----.', '0': '-----', ',': '--..--', '.': '.-.-.-', '?': '..--..',
'/': '-..-.', '-': '-....-', '(': '-.--.', ')': '-.--.-', ' ': '/'
}
# Prosigns (procedural signals)
self.prosigns = {
'<AA>': '.-.-', # New line
'<AR>': '.-.-.', # End of message
'<AS>': '.-...', # Wait
'<BT>': '-...-', # Break
'<CT>': '-.-..', # Attention
'<DO>': '-..---', # Shift to words
'<KN>': '-.--.', # Invitation to transmit
'<SK>': '...-.-', # End of work
'<SN>': '...-.', # Understood
}
# Audio parameters
self.sample_rate = 44100
self.frequency = 600 # Hz
self.dit_duration = 0.1 # seconds
self.dah_duration = self.dit_duration * 3
self.element_gap = self.dit_duration
self.letter_gap = self.dit_duration * 3
self.word_gap = self.dit_duration * 7
# PyAudio setup
self.audio = pyaudio.PyAudio()
self.is_playing = False
self.stop_signal = threading.Event()
The system implements:
- Complete International Morse Code: All letters, numbers, and punctuation
- Professional Prosigns: Standard amateur radio procedural signals
- Configurable Timing: Adjustable dit/dah durations and gaps
- Real-time Audio: PyAudio for immediate audio feedback
- Thread-safe Operations: Safe multi-threaded audio playback
2. Audio Signal Generation
def generate_tone(self, duration: float, frequency: float = None) -> np.ndarray:
"""Generate a sine wave tone for specified duration"""
if frequency is None:
frequency = self.frequency
# Calculate number of samples
num_samples = int(self.sample_rate * duration)
# Generate time array
t = np.linspace(0, duration, num_samples, False)
# Generate sine wave with envelope
tone = np.sin(2 * np.pi * frequency * t)
# Apply smooth envelope to prevent clicks
envelope_samples = int(0.005 * self.sample_rate) # 5ms ramp
if num_samples > 2 * envelope_samples:
# Fade in
tone[:envelope_samples] *= np.linspace(0, 1, envelope_samples)
# Fade out
tone[-envelope_samples:] *= np.linspace(1, 0, envelope_samples)
return tone
def generate_silence(self, duration: float) -> np.ndarray:
"""Generate silence for specified duration"""
num_samples = int(self.sample_rate * duration)
return np.zeros(num_samples)
def generate_dit(self) -> np.ndarray:
"""Generate dit (dot) audio signal"""
return self.generate_tone(self.dit_duration)
def generate_dah(self) -> np.ndarray:
"""Generate dah (dash) audio signal"""
return self.generate_tone(self.dah_duration)
def text_to_morse_audio(self, text: str) -> np.ndarray:
"""Convert text to complete Morse code audio signal"""
audio_segments = []
text = text.upper().strip()
for i, char in enumerate(text):
if char in self.morse_code:
morse_pattern = self.morse_code[char]
# Handle space (word separator)
if char == ' ':
audio_segments.append(self.generate_silence(self.word_gap))
continue
# Generate Morse elements for character
for j, element in enumerate(morse_pattern):
if element == '.':
audio_segments.append(self.generate_dit())
elif element == '-':
audio_segments.append(self.generate_dah())
elif element == '/': # Word separator in Morse
audio_segments.append(self.generate_silence(self.word_gap))
continue
# Add element gap between dots/dashes
if j < len(morse_pattern) - 1:
audio_segments.append(self.generate_silence(self.element_gap))
# Add letter gap after character (except last character)
if i < len(text) - 1 and text[i + 1] != ' ':
audio_segments.append(self.generate_silence(self.letter_gap))
elif char in self.prosigns:
# Handle prosigns
morse_pattern = self.prosigns[char]
for j, element in enumerate(morse_pattern):
if element == '.':
audio_segments.append(self.generate_dit())
elif element == '-':
audio_segments.append(self.generate_dah())
if j < len(morse_pattern) - 1:
audio_segments.append(self.generate_silence(self.element_gap))
# Combine all audio segments
if audio_segments:
return np.concatenate(audio_segments)
else:
return np.array([])
def generate_tone(self, duration: float, frequency: float = None) -> np.ndarray:
"""Generate a sine wave tone for specified duration"""
if frequency is None:
frequency = self.frequency
# Calculate number of samples
num_samples = int(self.sample_rate * duration)
# Generate time array
t = np.linspace(0, duration, num_samples, False)
# Generate sine wave with envelope
tone = np.sin(2 * np.pi * frequency * t)
# Apply smooth envelope to prevent clicks
envelope_samples = int(0.005 * self.sample_rate) # 5ms ramp
if num_samples > 2 * envelope_samples:
# Fade in
tone[:envelope_samples] *= np.linspace(0, 1, envelope_samples)
# Fade out
tone[-envelope_samples:] *= np.linspace(1, 0, envelope_samples)
return tone
def generate_silence(self, duration: float) -> np.ndarray:
"""Generate silence for specified duration"""
num_samples = int(self.sample_rate * duration)
return np.zeros(num_samples)
def generate_dit(self) -> np.ndarray:
"""Generate dit (dot) audio signal"""
return self.generate_tone(self.dit_duration)
def generate_dah(self) -> np.ndarray:
"""Generate dah (dash) audio signal"""
return self.generate_tone(self.dah_duration)
def text_to_morse_audio(self, text: str) -> np.ndarray:
"""Convert text to complete Morse code audio signal"""
audio_segments = []
text = text.upper().strip()
for i, char in enumerate(text):
if char in self.morse_code:
morse_pattern = self.morse_code[char]
# Handle space (word separator)
if char == ' ':
audio_segments.append(self.generate_silence(self.word_gap))
continue
# Generate Morse elements for character
for j, element in enumerate(morse_pattern):
if element == '.':
audio_segments.append(self.generate_dit())
elif element == '-':
audio_segments.append(self.generate_dah())
elif element == '/': # Word separator in Morse
audio_segments.append(self.generate_silence(self.word_gap))
continue
# Add element gap between dots/dashes
if j < len(morse_pattern) - 1:
audio_segments.append(self.generate_silence(self.element_gap))
# Add letter gap after character (except last character)
if i < len(text) - 1 and text[i + 1] != ' ':
audio_segments.append(self.generate_silence(self.letter_gap))
elif char in self.prosigns:
# Handle prosigns
morse_pattern = self.prosigns[char]
for j, element in enumerate(morse_pattern):
if element == '.':
audio_segments.append(self.generate_dit())
elif element == '-':
audio_segments.append(self.generate_dah())
if j < len(morse_pattern) - 1:
audio_segments.append(self.generate_silence(self.element_gap))
# Combine all audio segments
if audio_segments:
return np.concatenate(audio_segments)
else:
return np.array([])
3. Real-time Audio Playback
def play_morse_audio(self, text: str, blocking: bool = True) -> None:
"""Play Morse code audio in real-time"""
if self.is_playing:
self.stop_playback()
def audio_worker():
try:
self.is_playing = True
self.stop_signal.clear()
# Generate audio signal
audio_data = self.text_to_morse_audio(text)
if len(audio_data) == 0:
return
# Convert to bytes
audio_bytes = (audio_data * 32767).astype(np.int16).tobytes()
# Open audio stream
stream = self.audio.open(
format=pyaudio.paInt16,
channels=1,
rate=self.sample_rate,
output=True
)
# Play audio in chunks
chunk_size = 1024
for i in range(0, len(audio_bytes), chunk_size * 2): # *2 for 16-bit samples
if self.stop_signal.is_set():
break
chunk = audio_bytes[i:i + chunk_size * 2]
stream.write(chunk)
stream.stop_stream()
stream.close()
except Exception as e:
print(f"Audio playback error: {e}")
finally:
self.is_playing = False
if blocking:
audio_worker()
else:
thread = threading.Thread(target=audio_worker, daemon=True)
thread.start()
def stop_playback(self) -> None:
"""Stop current audio playback"""
self.stop_signal.set()
while self.is_playing:
time.sleep(0.01)
def play_morse_audio(self, text: str, blocking: bool = True) -> None:
"""Play Morse code audio in real-time"""
if self.is_playing:
self.stop_playback()
def audio_worker():
try:
self.is_playing = True
self.stop_signal.clear()
# Generate audio signal
audio_data = self.text_to_morse_audio(text)
if len(audio_data) == 0:
return
# Convert to bytes
audio_bytes = (audio_data * 32767).astype(np.int16).tobytes()
# Open audio stream
stream = self.audio.open(
format=pyaudio.paInt16,
channels=1,
rate=self.sample_rate,
output=True
)
# Play audio in chunks
chunk_size = 1024
for i in range(0, len(audio_bytes), chunk_size * 2): # *2 for 16-bit samples
if self.stop_signal.is_set():
break
chunk = audio_bytes[i:i + chunk_size * 2]
stream.write(chunk)
stream.stop_stream()
stream.close()
except Exception as e:
print(f"Audio playback error: {e}")
finally:
self.is_playing = False
if blocking:
audio_worker()
else:
thread = threading.Thread(target=audio_worker, daemon=True)
thread.start()
def stop_playback(self) -> None:
"""Stop current audio playback"""
self.stop_signal.set()
while self.is_playing:
time.sleep(0.01)
4. Visual Morse Code Display
class MorseVisualizer:
def __init__(self, morse_audio: MorseCodeAudio):
self.morse_audio = morse_audio
self.is_visualizing = False
self.stop_visual = threading.Event()
def text_to_visual_morse(self, text: str, callback=None) -> None:
"""Display Morse code visually with LED-style indicators"""
def visual_worker():
try:
self.is_visualizing = True
self.stop_visual.clear()
text = text.upper().strip()
for char in text:
if self.stop_visual.is_set():
break
if char in self.morse_audio.morse_code:
morse_pattern = self.morse_audio.morse_code[char]
if char == ' ':
# Word gap
if callback:
callback('WORD_GAP', self.morse_audio.word_gap)
time.sleep(self.morse_audio.word_gap)
continue
# Display character and its Morse pattern
if callback:
callback('CHARACTER', char, morse_pattern)
for element in morse_pattern:
if self.stop_visual.is_set():
break
if element == '.':
if callback:
callback('DIT_ON', self.morse_audio.dit_duration)
time.sleep(self.morse_audio.dit_duration)
if callback:
callback('DIT_OFF', self.morse_audio.element_gap)
time.sleep(self.morse_audio.element_gap)
elif element == '-':
if callback:
callback('DAH_ON', self.morse_audio.dah_duration)
time.sleep(self.morse_audio.dah_duration)
if callback:
callback('DAH_OFF', self.morse_audio.element_gap)
time.sleep(self.morse_audio.element_gap)
# Letter gap
if callback:
callback('LETTER_GAP', self.morse_audio.letter_gap)
time.sleep(self.morse_audio.letter_gap)
if callback:
callback('COMPLETE')
except Exception as e:
print(f"Visual display error: {e}")
finally:
self.is_visualizing = False
thread = threading.Thread(target=visual_worker, daemon=True)
thread.start()
def stop_visualization(self) -> None:
"""Stop visual Morse code display"""
self.stop_visual.set()
while self.is_visualizing:
time.sleep(0.01)
class MorseVisualizer:
def __init__(self, morse_audio: MorseCodeAudio):
self.morse_audio = morse_audio
self.is_visualizing = False
self.stop_visual = threading.Event()
def text_to_visual_morse(self, text: str, callback=None) -> None:
"""Display Morse code visually with LED-style indicators"""
def visual_worker():
try:
self.is_visualizing = True
self.stop_visual.clear()
text = text.upper().strip()
for char in text:
if self.stop_visual.is_set():
break
if char in self.morse_audio.morse_code:
morse_pattern = self.morse_audio.morse_code[char]
if char == ' ':
# Word gap
if callback:
callback('WORD_GAP', self.morse_audio.word_gap)
time.sleep(self.morse_audio.word_gap)
continue
# Display character and its Morse pattern
if callback:
callback('CHARACTER', char, morse_pattern)
for element in morse_pattern:
if self.stop_visual.is_set():
break
if element == '.':
if callback:
callback('DIT_ON', self.morse_audio.dit_duration)
time.sleep(self.morse_audio.dit_duration)
if callback:
callback('DIT_OFF', self.morse_audio.element_gap)
time.sleep(self.morse_audio.element_gap)
elif element == '-':
if callback:
callback('DAH_ON', self.morse_audio.dah_duration)
time.sleep(self.morse_audio.dah_duration)
if callback:
callback('DAH_OFF', self.morse_audio.element_gap)
time.sleep(self.morse_audio.element_gap)
# Letter gap
if callback:
callback('LETTER_GAP', self.morse_audio.letter_gap)
time.sleep(self.morse_audio.letter_gap)
if callback:
callback('COMPLETE')
except Exception as e:
print(f"Visual display error: {e}")
finally:
self.is_visualizing = False
thread = threading.Thread(target=visual_worker, daemon=True)
thread.start()
def stop_visualization(self) -> None:
"""Stop visual Morse code display"""
self.stop_visual.set()
while self.is_visualizing:
time.sleep(0.01)
5. Learning and Practice System
class MorseLearningSystem:
def __init__(self, morse_audio: MorseCodeAudio):
self.morse_audio = morse_audio
self.current_level = 1
self.max_level = 5
self.score = 0
self.session_stats = {
'correct': 0,
'incorrect': 0,
'total_time': 0,
'average_speed': 0
}
# Learning progression - characters by difficulty
self.learning_levels = {
1: ['E', 'T', 'I', 'A', 'N'], # Common letters
2: ['M', 'S', 'U', 'R', 'W', 'D', 'K', 'G'], # Medium letters
3: ['O', 'H', 'V', 'F', 'L', 'P', 'J', 'B'], # Advanced letters
4: ['X', 'C', 'Y', 'Z', 'Q'], # Difficult letters
5: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'] # Numbers
}
def generate_practice_text(self, level: int = None, length: int = 10) -> str:
"""Generate practice text for specified level"""
import random
if level is None:
level = self.current_level
# Get characters for current and previous levels
available_chars = []
for l in range(1, level + 1):
if l in self.learning_levels:
available_chars.extend(self.learning_levels[l])
if not available_chars:
available_chars = ['E', 'T'] # Fallback
# Generate random text
practice_text = ''.join(random.choices(available_chars, k=length))
return practice_text
def start_practice_session(self, level: int = None, session_length: int = 20) -> dict:
"""Start a practice session"""
if level is None:
level = self.current_level
self.session_stats = {
'correct': 0,
'incorrect': 0,
'total_time': 0,
'average_speed': 0,
'level': level,
'session_length': session_length
}
session_results = []
start_time = time.time()
for i in range(session_length):
# Generate single character or short sequence
practice_char = self.generate_practice_text(level, 1)
# Play Morse code
char_start_time = time.time()
self.morse_audio.play_morse_audio(practice_char, blocking=True)
# Wait for user input (in real implementation, this would be interactive)
user_input = self.simulate_user_input(practice_char) # Simulated for demo
char_end_time = time.time()
response_time = char_end_time - char_start_time
# Check answer
correct = user_input.upper() == practice_char.upper()
if correct:
self.session_stats['correct'] += 1
else:
self.session_stats['incorrect'] += 1
session_results.append({
'character': practice_char,
'user_input': user_input,
'correct': correct,
'response_time': response_time
})
self.session_stats['total_time'] += response_time
# Calculate final statistics
end_time = time.time()
total_session_time = end_time - start_time
accuracy = (self.session_stats['correct'] / session_length) * 100
average_response_time = self.session_stats['total_time'] / session_length
wpm = self.calculate_words_per_minute(session_length, total_session_time)
self.session_stats.update({
'accuracy': accuracy,
'average_response_time': average_response_time,
'words_per_minute': wpm,
'session_results': session_results,
'total_session_time': total_session_time
})
# Level progression
if accuracy >= 90 and self.current_level < self.max_level:
self.current_level += 1
self.session_stats['level_up'] = True
return self.session_stats
def simulate_user_input(self, correct_answer: str) -> str:
"""Simulate user input for demonstration"""
import random
# 85% chance of correct answer for demo
if random.random() < 0.85:
return correct_answer
else:
# Return random wrong answer
wrong_chars = ['E', 'T', 'I', 'A', 'N', 'M', 'S']
return random.choice([c for c in wrong_chars if c != correct_answer])
def calculate_words_per_minute(self, characters: int, time_seconds: float) -> float:
"""Calculate words per minute (standard: 5 characters = 1 word)"""
words = characters / 5
minutes = time_seconds / 60
return words / minutes if minutes > 0 else 0
def get_progress_report(self) -> dict:
"""Get detailed progress report"""
return {
'current_level': self.current_level,
'max_level': self.max_level,
'available_characters': self.learning_levels.get(self.current_level, []),
'session_stats': self.session_stats,
'level_progress': f"{self.current_level}/{self.max_level}",
'next_level_chars': self.learning_levels.get(self.current_level + 1, [])
}
class MorseLearningSystem:
def __init__(self, morse_audio: MorseCodeAudio):
self.morse_audio = morse_audio
self.current_level = 1
self.max_level = 5
self.score = 0
self.session_stats = {
'correct': 0,
'incorrect': 0,
'total_time': 0,
'average_speed': 0
}
# Learning progression - characters by difficulty
self.learning_levels = {
1: ['E', 'T', 'I', 'A', 'N'], # Common letters
2: ['M', 'S', 'U', 'R', 'W', 'D', 'K', 'G'], # Medium letters
3: ['O', 'H', 'V', 'F', 'L', 'P', 'J', 'B'], # Advanced letters
4: ['X', 'C', 'Y', 'Z', 'Q'], # Difficult letters
5: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'] # Numbers
}
def generate_practice_text(self, level: int = None, length: int = 10) -> str:
"""Generate practice text for specified level"""
import random
if level is None:
level = self.current_level
# Get characters for current and previous levels
available_chars = []
for l in range(1, level + 1):
if l in self.learning_levels:
available_chars.extend(self.learning_levels[l])
if not available_chars:
available_chars = ['E', 'T'] # Fallback
# Generate random text
practice_text = ''.join(random.choices(available_chars, k=length))
return practice_text
def start_practice_session(self, level: int = None, session_length: int = 20) -> dict:
"""Start a practice session"""
if level is None:
level = self.current_level
self.session_stats = {
'correct': 0,
'incorrect': 0,
'total_time': 0,
'average_speed': 0,
'level': level,
'session_length': session_length
}
session_results = []
start_time = time.time()
for i in range(session_length):
# Generate single character or short sequence
practice_char = self.generate_practice_text(level, 1)
# Play Morse code
char_start_time = time.time()
self.morse_audio.play_morse_audio(practice_char, blocking=True)
# Wait for user input (in real implementation, this would be interactive)
user_input = self.simulate_user_input(practice_char) # Simulated for demo
char_end_time = time.time()
response_time = char_end_time - char_start_time
# Check answer
correct = user_input.upper() == practice_char.upper()
if correct:
self.session_stats['correct'] += 1
else:
self.session_stats['incorrect'] += 1
session_results.append({
'character': practice_char,
'user_input': user_input,
'correct': correct,
'response_time': response_time
})
self.session_stats['total_time'] += response_time
# Calculate final statistics
end_time = time.time()
total_session_time = end_time - start_time
accuracy = (self.session_stats['correct'] / session_length) * 100
average_response_time = self.session_stats['total_time'] / session_length
wpm = self.calculate_words_per_minute(session_length, total_session_time)
self.session_stats.update({
'accuracy': accuracy,
'average_response_time': average_response_time,
'words_per_minute': wpm,
'session_results': session_results,
'total_session_time': total_session_time
})
# Level progression
if accuracy >= 90 and self.current_level < self.max_level:
self.current_level += 1
self.session_stats['level_up'] = True
return self.session_stats
def simulate_user_input(self, correct_answer: str) -> str:
"""Simulate user input for demonstration"""
import random
# 85% chance of correct answer for demo
if random.random() < 0.85:
return correct_answer
else:
# Return random wrong answer
wrong_chars = ['E', 'T', 'I', 'A', 'N', 'M', 'S']
return random.choice([c for c in wrong_chars if c != correct_answer])
def calculate_words_per_minute(self, characters: int, time_seconds: float) -> float:
"""Calculate words per minute (standard: 5 characters = 1 word)"""
words = characters / 5
minutes = time_seconds / 60
return words / minutes if minutes > 0 else 0
def get_progress_report(self) -> dict:
"""Get detailed progress report"""
return {
'current_level': self.current_level,
'max_level': self.max_level,
'available_characters': self.learning_levels.get(self.current_level, []),
'session_stats': self.session_stats,
'level_progress': f"{self.current_level}/{self.max_level}",
'next_level_chars': self.learning_levels.get(self.current_level + 1, [])
}
GUI Interface
1. Main Application Window
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkinter
class MorseCodeGUI:
def __init__(self):
self.morse_audio = MorseCodeAudio()
self.visualizer = MorseVisualizer(self.morse_audio)
self.learning_system = MorseLearningSystem(self.morse_audio)
self.setup_gui()
self.visual_canvas = None
self.practice_session_active = False
def setup_gui(self):
"""Setup the main GUI window"""
self.root = tk.Tk()
self.root.title("Morse Code Audio Player & Trainer")
self.root.geometry("800x600")
# Create notebook for tabs
self.notebook = ttk.Notebook(self.root)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Create tabs
self.create_translator_tab()
self.create_practice_tab()
self.create_settings_tab()
self.create_reference_tab()
def create_translator_tab(self):
"""Create the text-to-Morse translator tab"""
translator_frame = ttk.Frame(self.notebook)
self.notebook.add(translator_frame, text="Translator")
# Input section
input_frame = ttk.LabelFrame(translator_frame, text="Text Input", padding="10")
input_frame.pack(fill=tk.X, padx=10, pady=5)
self.text_input = tk.Text(input_frame, height=4, wrap=tk.WORD)
self.text_input.pack(fill=tk.X, pady=5)
# Control buttons
button_frame = ttk.Frame(input_frame)
button_frame.pack(fill=tk.X, pady=5)
ttk.Button(button_frame, text="Play Audio",
command=self.play_text_audio).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Show Visual",
command=self.show_visual_morse).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Save Audio",
command=self.save_audio_file).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Stop",
command=self.stop_all).pack(side=tk.LEFT, padx=5)
# Output section
output_frame = ttk.LabelFrame(translator_frame, text="Morse Code Output", padding="10")
output_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.morse_output = tk.Text(output_frame, height=4, wrap=tk.WORD, state=tk.DISABLED)
self.morse_output.pack(fill=tk.X, pady=5)
# Visual display
visual_frame = ttk.LabelFrame(translator_frame, text="Visual Display", padding="10")
visual_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.visual_display = tk.Canvas(visual_frame, height=100, bg='black')
self.visual_display.pack(fill=tk.X, pady=5)
# Status
self.status_label = ttk.Label(translator_frame, text="Ready")
self.status_label.pack(pady=5)
def create_practice_tab(self):
"""Create the practice/learning tab"""
practice_frame = ttk.Frame(self.notebook)
self.notebook.add(practice_frame, text="Practice")
# Level selection
level_frame = ttk.LabelFrame(practice_frame, text="Practice Level", padding="10")
level_frame.pack(fill=tk.X, padx=10, pady=5)
self.level_var = tk.IntVar(value=self.learning_system.current_level)
for i in range(1, 6):
ttk.Radiobutton(level_frame, text=f"Level {i}", variable=self.level_var,
value=i).pack(side=tk.LEFT, padx=10)
# Practice controls
control_frame = ttk.LabelFrame(practice_frame, text="Practice Session", padding="10")
control_frame.pack(fill=tk.X, padx=10, pady=5)
ttk.Label(control_frame, text="Session Length:").pack(side=tk.LEFT, padx=5)
self.session_length_var = tk.IntVar(value=20)
session_spinbox = ttk.Spinbox(control_frame, from_=5, to=100,
textvariable=self.session_length_var, width=10)
session_spinbox.pack(side=tk.LEFT, padx=5)
ttk.Button(control_frame, text="Start Practice",
command=self.start_practice_session).pack(side=tk.LEFT, padx=10)
ttk.Button(control_frame, text="Stop Practice",
command=self.stop_practice_session).pack(side=tk.LEFT, padx=5)
# Practice display
practice_display_frame = ttk.LabelFrame(practice_frame, text="Current Character", padding="10")
practice_display_frame.pack(fill=tk.X, padx=10, pady=5)
self.current_char_label = ttk.Label(practice_display_frame, text="", font=("Arial", 24))
self.current_char_label.pack(pady=10)
self.morse_pattern_label = ttk.Label(practice_display_frame, text="", font=("Arial", 16))
self.morse_pattern_label.pack(pady=5)
# User input
input_frame = ttk.LabelFrame(practice_frame, text="Your Answer", padding="10")
input_frame.pack(fill=tk.X, padx=10, pady=5)
self.user_answer_var = tk.StringVar()
answer_entry = ttk.Entry(input_frame, textvariable=self.user_answer_var, font=("Arial", 16))
answer_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
ttk.Button(input_frame, text="Submit",
command=self.submit_answer).pack(side=tk.LEFT, padx=5)
# Statistics
stats_frame = ttk.LabelFrame(practice_frame, text="Session Statistics", padding="10")
stats_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.stats_text = tk.Text(stats_frame, height=8, state=tk.DISABLED)
self.stats_text.pack(fill=tk.BOTH, expand=True)
def create_settings_tab(self):
"""Create the settings configuration tab"""
settings_frame = ttk.Frame(self.notebook)
self.notebook.add(settings_frame, text="Settings")
# Audio settings
audio_frame = ttk.LabelFrame(settings_frame, text="Audio Settings", padding="10")
audio_frame.pack(fill=tk.X, padx=10, pady=5)
# Frequency
ttk.Label(audio_frame, text="Frequency (Hz):").grid(row=0, column=0, sticky=tk.W, pady=5)
self.frequency_var = tk.IntVar(value=self.morse_audio.frequency)
frequency_scale = ttk.Scale(audio_frame, from_=300, to=1200, variable=self.frequency_var,
orient=tk.HORIZONTAL, length=300, command=self.update_frequency)
frequency_scale.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=10)
self.frequency_label = ttk.Label(audio_frame, text=f"{self.frequency_var.get()} Hz")
self.frequency_label.grid(row=0, column=2, padx=5)
# Speed (WPM)
ttk.Label(audio_frame, text="Speed (WPM):").grid(row=1, column=0, sticky=tk.W, pady=5)
self.wpm_var = tk.IntVar(value=20)
wpm_scale = ttk.Scale(audio_frame, from_=5, to=40, variable=self.wpm_var,
orient=tk.HORIZONTAL, length=300, command=self.update_speed)
wpm_scale.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=10)
self.wpm_label = ttk.Label(audio_frame, text=f"{self.wpm_var.get()} WPM")
self.wpm_label.grid(row=1, column=2, padx=5)
# Configure grid weights
audio_frame.columnconfigure(1, weight=1)
# Test audio
test_frame = ttk.Frame(audio_frame)
test_frame.grid(row=2, column=0, columnspan=3, pady=10)
ttk.Button(test_frame, text="Test Audio",
command=self.test_audio).pack(side=tk.LEFT, padx=5)
ttk.Button(test_frame, text="Apply Settings",
command=self.apply_settings).pack(side=tk.LEFT, padx=5)
def create_reference_tab(self):
"""Create the Morse code reference tab"""
reference_frame = ttk.Frame(self.notebook)
self.notebook.add(reference_frame, text="Reference")
# Create scrollable text widget with Morse code reference
reference_text = tk.Text(reference_frame, wrap=tk.WORD, state=tk.DISABLED)
reference_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Add Morse code reference content
reference_content = self.generate_reference_content()
reference_text.config(state=tk.NORMAL)
reference_text.insert(tk.END, reference_content)
reference_text.config(state=tk.DISABLED)
def play_text_audio(self):
"""Play audio for input text"""
text = self.text_input.get("1.0", tk.END).strip()
if text:
self.status_label.config(text="Playing audio...")
# Convert to Morse and display
morse_text = self.text_to_morse_display(text)
self.morse_output.config(state=tk.NORMAL)
self.morse_output.delete("1.0", tk.END)
self.morse_output.insert("1.0", morse_text)
self.morse_output.config(state=tk.DISABLED)
# Play audio
self.morse_audio.play_morse_audio(text, blocking=False)
# Update status after delay
self.root.after(2000, lambda: self.status_label.config(text="Ready"))
else:
messagebox.showwarning("Warning", "Please enter text to convert")
def show_visual_morse(self):
"""Show visual Morse code representation"""
text = self.text_input.get("1.0", tk.END).strip()
if text:
def visual_callback(event_type, *args):
if event_type == 'DIT_ON':
self.visual_display.create_oval(50, 25, 100, 75, fill='yellow', tags='signal')
elif event_type == 'DAH_ON':
self.visual_display.create_rectangle(50, 25, 150, 75, fill='red', tags='signal')
elif event_type in ['DIT_OFF', 'DAH_OFF', 'LETTER_GAP', 'WORD_GAP']:
self.visual_display.delete('signal')
elif event_type == 'COMPLETE':
self.visual_display.delete('signal')
self.visualizer.text_to_visual_morse(text, visual_callback)
def text_to_morse_display(self, text: str) -> str:
"""Convert text to Morse code for display"""
result = []
for char in text.upper():
if char in self.morse_audio.morse_code:
morse = self.morse_audio.morse_code[char]
result.append(f"{char}: {morse}")
elif char in self.morse_audio.prosigns:
morse = self.morse_audio.prosigns[char]
result.append(f"{char}: {morse}")
return "\n".join(result)
def generate_reference_content(self) -> str:
"""Generate Morse code reference content"""
content = "INTERNATIONAL MORSE CODE REFERENCE\n\n"
content += "LETTERS:\n"
for char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
if char in self.morse_audio.morse_code:
content += f"{char}: {self.morse_audio.morse_code[char]}\n"
content += "\nNUMBERS:\n"
for char in "0123456789":
if char in self.morse_audio.morse_code:
content += f"{char}: {self.morse_audio.morse_code[char]}\n"
content += "\nPUNCTUATION:\n"
punctuation = [',', '.', '?', '/', '-', '(', ')']
for char in punctuation:
if char in self.morse_audio.morse_code:
content += f"{char}: {self.morse_audio.morse_code[char]}\n"
content += "\nPROSIGNS:\n"
for prosign, morse in self.morse_audio.prosigns.items():
content += f"{prosign}: {morse}\n"
return content
def run(self):
"""Run the GUI application"""
self.root.mainloop()
# GUI entry point
def run_gui():
app = MorseCodeGUI()
app.run()
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkinter
class MorseCodeGUI:
def __init__(self):
self.morse_audio = MorseCodeAudio()
self.visualizer = MorseVisualizer(self.morse_audio)
self.learning_system = MorseLearningSystem(self.morse_audio)
self.setup_gui()
self.visual_canvas = None
self.practice_session_active = False
def setup_gui(self):
"""Setup the main GUI window"""
self.root = tk.Tk()
self.root.title("Morse Code Audio Player & Trainer")
self.root.geometry("800x600")
# Create notebook for tabs
self.notebook = ttk.Notebook(self.root)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Create tabs
self.create_translator_tab()
self.create_practice_tab()
self.create_settings_tab()
self.create_reference_tab()
def create_translator_tab(self):
"""Create the text-to-Morse translator tab"""
translator_frame = ttk.Frame(self.notebook)
self.notebook.add(translator_frame, text="Translator")
# Input section
input_frame = ttk.LabelFrame(translator_frame, text="Text Input", padding="10")
input_frame.pack(fill=tk.X, padx=10, pady=5)
self.text_input = tk.Text(input_frame, height=4, wrap=tk.WORD)
self.text_input.pack(fill=tk.X, pady=5)
# Control buttons
button_frame = ttk.Frame(input_frame)
button_frame.pack(fill=tk.X, pady=5)
ttk.Button(button_frame, text="Play Audio",
command=self.play_text_audio).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Show Visual",
command=self.show_visual_morse).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Save Audio",
command=self.save_audio_file).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Stop",
command=self.stop_all).pack(side=tk.LEFT, padx=5)
# Output section
output_frame = ttk.LabelFrame(translator_frame, text="Morse Code Output", padding="10")
output_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.morse_output = tk.Text(output_frame, height=4, wrap=tk.WORD, state=tk.DISABLED)
self.morse_output.pack(fill=tk.X, pady=5)
# Visual display
visual_frame = ttk.LabelFrame(translator_frame, text="Visual Display", padding="10")
visual_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.visual_display = tk.Canvas(visual_frame, height=100, bg='black')
self.visual_display.pack(fill=tk.X, pady=5)
# Status
self.status_label = ttk.Label(translator_frame, text="Ready")
self.status_label.pack(pady=5)
def create_practice_tab(self):
"""Create the practice/learning tab"""
practice_frame = ttk.Frame(self.notebook)
self.notebook.add(practice_frame, text="Practice")
# Level selection
level_frame = ttk.LabelFrame(practice_frame, text="Practice Level", padding="10")
level_frame.pack(fill=tk.X, padx=10, pady=5)
self.level_var = tk.IntVar(value=self.learning_system.current_level)
for i in range(1, 6):
ttk.Radiobutton(level_frame, text=f"Level {i}", variable=self.level_var,
value=i).pack(side=tk.LEFT, padx=10)
# Practice controls
control_frame = ttk.LabelFrame(practice_frame, text="Practice Session", padding="10")
control_frame.pack(fill=tk.X, padx=10, pady=5)
ttk.Label(control_frame, text="Session Length:").pack(side=tk.LEFT, padx=5)
self.session_length_var = tk.IntVar(value=20)
session_spinbox = ttk.Spinbox(control_frame, from_=5, to=100,
textvariable=self.session_length_var, width=10)
session_spinbox.pack(side=tk.LEFT, padx=5)
ttk.Button(control_frame, text="Start Practice",
command=self.start_practice_session).pack(side=tk.LEFT, padx=10)
ttk.Button(control_frame, text="Stop Practice",
command=self.stop_practice_session).pack(side=tk.LEFT, padx=5)
# Practice display
practice_display_frame = ttk.LabelFrame(practice_frame, text="Current Character", padding="10")
practice_display_frame.pack(fill=tk.X, padx=10, pady=5)
self.current_char_label = ttk.Label(practice_display_frame, text="", font=("Arial", 24))
self.current_char_label.pack(pady=10)
self.morse_pattern_label = ttk.Label(practice_display_frame, text="", font=("Arial", 16))
self.morse_pattern_label.pack(pady=5)
# User input
input_frame = ttk.LabelFrame(practice_frame, text="Your Answer", padding="10")
input_frame.pack(fill=tk.X, padx=10, pady=5)
self.user_answer_var = tk.StringVar()
answer_entry = ttk.Entry(input_frame, textvariable=self.user_answer_var, font=("Arial", 16))
answer_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
ttk.Button(input_frame, text="Submit",
command=self.submit_answer).pack(side=tk.LEFT, padx=5)
# Statistics
stats_frame = ttk.LabelFrame(practice_frame, text="Session Statistics", padding="10")
stats_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.stats_text = tk.Text(stats_frame, height=8, state=tk.DISABLED)
self.stats_text.pack(fill=tk.BOTH, expand=True)
def create_settings_tab(self):
"""Create the settings configuration tab"""
settings_frame = ttk.Frame(self.notebook)
self.notebook.add(settings_frame, text="Settings")
# Audio settings
audio_frame = ttk.LabelFrame(settings_frame, text="Audio Settings", padding="10")
audio_frame.pack(fill=tk.X, padx=10, pady=5)
# Frequency
ttk.Label(audio_frame, text="Frequency (Hz):").grid(row=0, column=0, sticky=tk.W, pady=5)
self.frequency_var = tk.IntVar(value=self.morse_audio.frequency)
frequency_scale = ttk.Scale(audio_frame, from_=300, to=1200, variable=self.frequency_var,
orient=tk.HORIZONTAL, length=300, command=self.update_frequency)
frequency_scale.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=10)
self.frequency_label = ttk.Label(audio_frame, text=f"{self.frequency_var.get()} Hz")
self.frequency_label.grid(row=0, column=2, padx=5)
# Speed (WPM)
ttk.Label(audio_frame, text="Speed (WPM):").grid(row=1, column=0, sticky=tk.W, pady=5)
self.wpm_var = tk.IntVar(value=20)
wpm_scale = ttk.Scale(audio_frame, from_=5, to=40, variable=self.wpm_var,
orient=tk.HORIZONTAL, length=300, command=self.update_speed)
wpm_scale.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=10)
self.wpm_label = ttk.Label(audio_frame, text=f"{self.wpm_var.get()} WPM")
self.wpm_label.grid(row=1, column=2, padx=5)
# Configure grid weights
audio_frame.columnconfigure(1, weight=1)
# Test audio
test_frame = ttk.Frame(audio_frame)
test_frame.grid(row=2, column=0, columnspan=3, pady=10)
ttk.Button(test_frame, text="Test Audio",
command=self.test_audio).pack(side=tk.LEFT, padx=5)
ttk.Button(test_frame, text="Apply Settings",
command=self.apply_settings).pack(side=tk.LEFT, padx=5)
def create_reference_tab(self):
"""Create the Morse code reference tab"""
reference_frame = ttk.Frame(self.notebook)
self.notebook.add(reference_frame, text="Reference")
# Create scrollable text widget with Morse code reference
reference_text = tk.Text(reference_frame, wrap=tk.WORD, state=tk.DISABLED)
reference_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Add Morse code reference content
reference_content = self.generate_reference_content()
reference_text.config(state=tk.NORMAL)
reference_text.insert(tk.END, reference_content)
reference_text.config(state=tk.DISABLED)
def play_text_audio(self):
"""Play audio for input text"""
text = self.text_input.get("1.0", tk.END).strip()
if text:
self.status_label.config(text="Playing audio...")
# Convert to Morse and display
morse_text = self.text_to_morse_display(text)
self.morse_output.config(state=tk.NORMAL)
self.morse_output.delete("1.0", tk.END)
self.morse_output.insert("1.0", morse_text)
self.morse_output.config(state=tk.DISABLED)
# Play audio
self.morse_audio.play_morse_audio(text, blocking=False)
# Update status after delay
self.root.after(2000, lambda: self.status_label.config(text="Ready"))
else:
messagebox.showwarning("Warning", "Please enter text to convert")
def show_visual_morse(self):
"""Show visual Morse code representation"""
text = self.text_input.get("1.0", tk.END).strip()
if text:
def visual_callback(event_type, *args):
if event_type == 'DIT_ON':
self.visual_display.create_oval(50, 25, 100, 75, fill='yellow', tags='signal')
elif event_type == 'DAH_ON':
self.visual_display.create_rectangle(50, 25, 150, 75, fill='red', tags='signal')
elif event_type in ['DIT_OFF', 'DAH_OFF', 'LETTER_GAP', 'WORD_GAP']:
self.visual_display.delete('signal')
elif event_type == 'COMPLETE':
self.visual_display.delete('signal')
self.visualizer.text_to_visual_morse(text, visual_callback)
def text_to_morse_display(self, text: str) -> str:
"""Convert text to Morse code for display"""
result = []
for char in text.upper():
if char in self.morse_audio.morse_code:
morse = self.morse_audio.morse_code[char]
result.append(f"{char}: {morse}")
elif char in self.morse_audio.prosigns:
morse = self.morse_audio.prosigns[char]
result.append(f"{char}: {morse}")
return "\n".join(result)
def generate_reference_content(self) -> str:
"""Generate Morse code reference content"""
content = "INTERNATIONAL MORSE CODE REFERENCE\n\n"
content += "LETTERS:\n"
for char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
if char in self.morse_audio.morse_code:
content += f"{char}: {self.morse_audio.morse_code[char]}\n"
content += "\nNUMBERS:\n"
for char in "0123456789":
if char in self.morse_audio.morse_code:
content += f"{char}: {self.morse_audio.morse_code[char]}\n"
content += "\nPUNCTUATION:\n"
punctuation = [',', '.', '?', '/', '-', '(', ')']
for char in punctuation:
if char in self.morse_audio.morse_code:
content += f"{char}: {self.morse_audio.morse_code[char]}\n"
content += "\nPROSIGNS:\n"
for prosign, morse in self.morse_audio.prosigns.items():
content += f"{prosign}: {morse}\n"
return content
def run(self):
"""Run the GUI application"""
self.root.mainloop()
# GUI entry point
def run_gui():
app = MorseCodeGUI()
app.run()
Audio File Generation
1. WAV File Export
def save_to_wav(self, text: str, filename: str, sample_rate: int = None) -> bool:
"""Save Morse code audio to WAV file"""
try:
if sample_rate is None:
sample_rate = self.sample_rate
# Generate audio data
audio_data = self.text_to_morse_audio(text)
if len(audio_data) == 0:
return False
# Convert to 16-bit integers
audio_int16 = (audio_data * 32767).astype(np.int16)
# Write WAV file
with wave.open(filename, 'wb') as wav_file:
wav_file.setnchannels(1) # Mono
wav_file.setsampwidth(2) # 16-bit
wav_file.setframerate(sample_rate)
wav_file.writeframes(audio_int16.tobytes())
return True
except Exception as e:
print(f"Error saving WAV file: {e}")
return False
def batch_convert_to_audio(self, text_files: List[str], output_directory: str) -> dict:
"""Convert multiple text files to Morse code audio"""
results = {'successful': [], 'failed': []}
for text_file in text_files:
try:
# Read text file
with open(text_file, 'r', encoding='utf-8') as f:
content = f.read().strip()
# Generate output filename
base_name = os.path.splitext(os.path.basename(text_file))[0]
output_file = os.path.join(output_directory, f"{base_name}_morse.wav")
# Convert to audio
if self.save_to_wav(content, output_file):
results['successful'].append({
'input_file': text_file,
'output_file': output_file,
'content_length': len(content)
})
else:
results['failed'].append({
'input_file': text_file,
'error': 'Failed to generate audio'
})
except Exception as e:
results['failed'].append({
'input_file': text_file,
'error': str(e)
})
return results
def save_to_wav(self, text: str, filename: str, sample_rate: int = None) -> bool:
"""Save Morse code audio to WAV file"""
try:
if sample_rate is None:
sample_rate = self.sample_rate
# Generate audio data
audio_data = self.text_to_morse_audio(text)
if len(audio_data) == 0:
return False
# Convert to 16-bit integers
audio_int16 = (audio_data * 32767).astype(np.int16)
# Write WAV file
with wave.open(filename, 'wb') as wav_file:
wav_file.setnchannels(1) # Mono
wav_file.setsampwidth(2) # 16-bit
wav_file.setframerate(sample_rate)
wav_file.writeframes(audio_int16.tobytes())
return True
except Exception as e:
print(f"Error saving WAV file: {e}")
return False
def batch_convert_to_audio(self, text_files: List[str], output_directory: str) -> dict:
"""Convert multiple text files to Morse code audio"""
results = {'successful': [], 'failed': []}
for text_file in text_files:
try:
# Read text file
with open(text_file, 'r', encoding='utf-8') as f:
content = f.read().strip()
# Generate output filename
base_name = os.path.splitext(os.path.basename(text_file))[0]
output_file = os.path.join(output_directory, f"{base_name}_morse.wav")
# Convert to audio
if self.save_to_wav(content, output_file):
results['successful'].append({
'input_file': text_file,
'output_file': output_file,
'content_length': len(content)
})
else:
results['failed'].append({
'input_file': text_file,
'error': 'Failed to generate audio'
})
except Exception as e:
results['failed'].append({
'input_file': text_file,
'error': str(e)
})
return results
Real-time Communication
1. Morse Code Chat System
import socket
import threading
import json
from datetime import datetime
class MorseChatSystem:
def __init__(self, morse_audio: MorseCodeAudio):
self.morse_audio = morse_audio
self.socket = None
self.is_server = False
self.is_connected = False
self.chat_history = []
self.message_callbacks = []
def start_server(self, host: str = 'localhost', port: int = 9999):
"""Start as chat server"""
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((host, port))
self.socket.listen(1)
self.is_server = True
print(f"Morse chat server started on {host}:{port}")
# Accept connections
thread = threading.Thread(target=self._accept_connections, daemon=True)
thread.start()
return True
except Exception as e:
print(f"Error starting server: {e}")
return False
def connect_to_server(self, host: str, port: int = 9999):
"""Connect to chat server"""
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((host, port))
self.is_connected = True
print(f"Connected to Morse chat server at {host}:{port}")
# Start receiving messages
thread = threading.Thread(target=self._receive_messages, daemon=True)
thread.start()
return True
except Exception as e:
print(f"Error connecting to server: {e}")
return False
def send_morse_message(self, text: str, play_audio: bool = True):
"""Send text as Morse code message"""
if not self.is_connected:
print("Not connected to chat server")
return False
try:
# Convert to Morse code
morse_text = self.text_to_morse_string(text)
# Create message
message = {
'type': 'morse_message',
'text': text,
'morse': morse_text,
'timestamp': datetime.now().isoformat(),
'sender': 'local'
}
# Send message
message_json = json.dumps(message)
self.socket.send(f"{message_json}\n".encode('utf-8'))
# Add to chat history
self.chat_history.append(message)
# Play audio locally if requested
if play_audio:
self.morse_audio.play_morse_audio(text, blocking=False)
# Notify callbacks
for callback in self.message_callbacks:
callback('sent', message)
return True
except Exception as e:
print(f"Error sending message: {e}")
return False
def text_to_morse_string(self, text: str) -> str:
"""Convert text to Morse code string representation"""
result = []
for char in text.upper():
if char in self.morse_audio.morse_code:
result.append(self.morse_audio.morse_code[char])
elif char == ' ':
result.append('/')
return ' '.join(result)
def _accept_connections(self):
"""Accept incoming connections (server mode)"""
try:
while True:
client_socket, address = self.socket.accept()
print(f"Client connected from {address}")
self.client_socket = client_socket
self.is_connected = True
# Start receiving messages from client
thread = threading.Thread(target=self._receive_messages_from_client,
args=(client_socket,), daemon=True)
thread.start()
except Exception as e:
print(f"Error accepting connections: {e}")
def _receive_messages(self):
"""Receive messages from server (client mode)"""
try:
buffer = ""
while self.is_connected:
data = self.socket.recv(1024).decode('utf-8')
if not data:
break
buffer += data
while '\n' in buffer:
line, buffer = buffer.split('\n', 1)
if line.strip():
self._handle_received_message(line.strip())
except Exception as e:
print(f"Error receiving messages: {e}")
finally:
self.is_connected = False
def _receive_messages_from_client(self, client_socket):
"""Receive messages from client (server mode)"""
try:
buffer = ""
while self.is_connected:
data = client_socket.recv(1024).decode('utf-8')
if not data:
break
buffer += data
while '\n' in buffer:
line, buffer = buffer.split('\n', 1)
if line.strip():
self._handle_received_message(line.strip())
except Exception as e:
print(f"Error receiving messages from client: {e}")
finally:
self.is_connected = False
def _handle_received_message(self, message_json: str):
"""Handle received message"""
try:
message = json.loads(message_json)
message['sender'] = 'remote'
# Add to chat history
self.chat_history.append(message)
# Play Morse audio for received message
if message['type'] == 'morse_message':
self.morse_audio.play_morse_audio(message['text'], blocking=False)
# Notify callbacks
for callback in self.message_callbacks:
callback('received', message)
except Exception as e:
print(f"Error handling received message: {e}")
def add_message_callback(self, callback):
"""Add callback for message events"""
self.message_callbacks.append(callback)
def get_chat_history(self) -> List[dict]:
"""Get complete chat history"""
return self.chat_history.copy()
def disconnect(self):
"""Disconnect from chat"""
self.is_connected = False
if self.socket:
self.socket.close()
print("Disconnected from Morse chat")
import socket
import threading
import json
from datetime import datetime
class MorseChatSystem:
def __init__(self, morse_audio: MorseCodeAudio):
self.morse_audio = morse_audio
self.socket = None
self.is_server = False
self.is_connected = False
self.chat_history = []
self.message_callbacks = []
def start_server(self, host: str = 'localhost', port: int = 9999):
"""Start as chat server"""
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((host, port))
self.socket.listen(1)
self.is_server = True
print(f"Morse chat server started on {host}:{port}")
# Accept connections
thread = threading.Thread(target=self._accept_connections, daemon=True)
thread.start()
return True
except Exception as e:
print(f"Error starting server: {e}")
return False
def connect_to_server(self, host: str, port: int = 9999):
"""Connect to chat server"""
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((host, port))
self.is_connected = True
print(f"Connected to Morse chat server at {host}:{port}")
# Start receiving messages
thread = threading.Thread(target=self._receive_messages, daemon=True)
thread.start()
return True
except Exception as e:
print(f"Error connecting to server: {e}")
return False
def send_morse_message(self, text: str, play_audio: bool = True):
"""Send text as Morse code message"""
if not self.is_connected:
print("Not connected to chat server")
return False
try:
# Convert to Morse code
morse_text = self.text_to_morse_string(text)
# Create message
message = {
'type': 'morse_message',
'text': text,
'morse': morse_text,
'timestamp': datetime.now().isoformat(),
'sender': 'local'
}
# Send message
message_json = json.dumps(message)
self.socket.send(f"{message_json}\n".encode('utf-8'))
# Add to chat history
self.chat_history.append(message)
# Play audio locally if requested
if play_audio:
self.morse_audio.play_morse_audio(text, blocking=False)
# Notify callbacks
for callback in self.message_callbacks:
callback('sent', message)
return True
except Exception as e:
print(f"Error sending message: {e}")
return False
def text_to_morse_string(self, text: str) -> str:
"""Convert text to Morse code string representation"""
result = []
for char in text.upper():
if char in self.morse_audio.morse_code:
result.append(self.morse_audio.morse_code[char])
elif char == ' ':
result.append('/')
return ' '.join(result)
def _accept_connections(self):
"""Accept incoming connections (server mode)"""
try:
while True:
client_socket, address = self.socket.accept()
print(f"Client connected from {address}")
self.client_socket = client_socket
self.is_connected = True
# Start receiving messages from client
thread = threading.Thread(target=self._receive_messages_from_client,
args=(client_socket,), daemon=True)
thread.start()
except Exception as e:
print(f"Error accepting connections: {e}")
def _receive_messages(self):
"""Receive messages from server (client mode)"""
try:
buffer = ""
while self.is_connected:
data = self.socket.recv(1024).decode('utf-8')
if not data:
break
buffer += data
while '\n' in buffer:
line, buffer = buffer.split('\n', 1)
if line.strip():
self._handle_received_message(line.strip())
except Exception as e:
print(f"Error receiving messages: {e}")
finally:
self.is_connected = False
def _receive_messages_from_client(self, client_socket):
"""Receive messages from client (server mode)"""
try:
buffer = ""
while self.is_connected:
data = client_socket.recv(1024).decode('utf-8')
if not data:
break
buffer += data
while '\n' in buffer:
line, buffer = buffer.split('\n', 1)
if line.strip():
self._handle_received_message(line.strip())
except Exception as e:
print(f"Error receiving messages from client: {e}")
finally:
self.is_connected = False
def _handle_received_message(self, message_json: str):
"""Handle received message"""
try:
message = json.loads(message_json)
message['sender'] = 'remote'
# Add to chat history
self.chat_history.append(message)
# Play Morse audio for received message
if message['type'] == 'morse_message':
self.morse_audio.play_morse_audio(message['text'], blocking=False)
# Notify callbacks
for callback in self.message_callbacks:
callback('received', message)
except Exception as e:
print(f"Error handling received message: {e}")
def add_message_callback(self, callback):
"""Add callback for message events"""
self.message_callbacks.append(callback)
def get_chat_history(self) -> List[dict]:
"""Get complete chat history"""
return self.chat_history.copy()
def disconnect(self):
"""Disconnect from chat"""
self.is_connected = False
if self.socket:
self.socket.close()
print("Disconnected from Morse chat")
Command Line Interface
1. CLI Implementation
import argparse
import sys
class MorseCodeCLI:
def __init__(self):
self.morse_audio = MorseCodeAudio()
self.learning_system = MorseLearningSystem(self.morse_audio)
def main(self):
"""Main CLI entry point"""
parser = argparse.ArgumentParser(description='Morse Code Audio Player & Trainer')
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Play command
play_parser = subparsers.add_parser('play', help='Play text as Morse code')
play_parser.add_argument('text', help='Text to convert to Morse code')
play_parser.add_argument('--frequency', '-f', type=int, default=600,
help='Audio frequency in Hz')
play_parser.add_argument('--speed', '-s', type=int, default=20,
help='Speed in WPM')
# Convert command
convert_parser = subparsers.add_parser('convert', help='Convert text to Morse code')
convert_parser.add_argument('text', help='Text to convert')
convert_parser.add_argument('--output', '-o', help='Output file for audio')
# Practice command
practice_parser = subparsers.add_parser('practice', help='Start practice session')
practice_parser.add_argument('--level', '-l', type=int, default=1,
help='Practice level (1-5)')
practice_parser.add_argument('--length', type=int, default=20,
help='Session length')
# Reference command
reference_parser = subparsers.add_parser('reference', help='Show Morse code reference')
args = parser.parse_args()
if args.command == 'play':
self.cli_play(args)
elif args.command == 'convert':
self.cli_convert(args)
elif args.command == 'practice':
self.cli_practice(args)
elif args.command == 'reference':
self.cli_reference()
else:
parser.print_help()
def cli_play(self, args):
"""CLI play command"""
self.morse_audio.frequency = args.frequency
self.morse_audio.set_speed_wpm(args.speed)
print(f"Playing: {args.text}")
print(f"Frequency: {args.frequency} Hz")
print(f"Speed: {args.speed} WPM")
self.morse_audio.play_morse_audio(args.text, blocking=True)
print("Playback complete.")
def cli_convert(self, args):
"""CLI convert command"""
print(f"Text: {args.text}")
# Convert to Morse code display
morse_display = []
for char in args.text.upper():
if char in self.morse_audio.morse_code:
morse = self.morse_audio.morse_code[char]
morse_display.append(f"{char}: {morse}")
print("Morse Code:")
for line in morse_display:
print(f" {line}")
# Save audio if requested
if args.output:
if self.morse_audio.save_to_wav(args.text, args.output):
print(f"Audio saved to: {args.output}")
else:
print("Error saving audio file")
def cli_practice(self, args):
"""CLI practice command"""
print(f"Starting practice session - Level {args.level}")
print(f"Session length: {args.length} characters")
print("Listen to the Morse code and type the character you hear.")
print("Press Ctrl+C to quit early.\n")
try:
results = self.learning_system.start_practice_session(args.level, args.length)
# Display results
print("\n" + "="*50)
print("PRACTICE SESSION RESULTS")
print("="*50)
print(f"Level: {results['level']}")
print(f"Accuracy: {results['accuracy']:.1f}%")
print(f"Average Response Time: {results['average_response_time']:.2f} seconds")
print(f"Words Per Minute: {results['words_per_minute']:.1f}")
print(f"Correct: {results['correct']}")
print(f"Incorrect: {results['incorrect']}")
if results.get('level_up'):
print(f"\n🎉 Congratulations! You advanced to Level {results['level'] + 1}!")
except KeyboardInterrupt:
print("\nPractice session interrupted.")
def cli_reference(self):
"""CLI reference command"""
print("INTERNATIONAL MORSE CODE REFERENCE")
print("="*50)
print("\nLETTERS:")
for char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
if char in self.morse_audio.morse_code:
print(f" {char}: {self.morse_audio.morse_code[char]}")
print("\nNUMBERS:")
for char in "0123456789":
if char in self.morse_audio.morse_code:
print(f" {char}: {self.morse_audio.morse_code[char]}")
print("\nPUNCTUATION:")
punctuation = [',', '.', '?', '/', '-', '(', ')']
for char in punctuation:
if char in self.morse_audio.morse_code:
print(f" {char}: {self.morse_audio.morse_code[char]}")
if __name__ == "__main__":
if len(sys.argv) > 1:
# Command line mode
cli = MorseCodeCLI()
cli.main()
else:
# GUI mode
run_gui()
import argparse
import sys
class MorseCodeCLI:
def __init__(self):
self.morse_audio = MorseCodeAudio()
self.learning_system = MorseLearningSystem(self.morse_audio)
def main(self):
"""Main CLI entry point"""
parser = argparse.ArgumentParser(description='Morse Code Audio Player & Trainer')
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Play command
play_parser = subparsers.add_parser('play', help='Play text as Morse code')
play_parser.add_argument('text', help='Text to convert to Morse code')
play_parser.add_argument('--frequency', '-f', type=int, default=600,
help='Audio frequency in Hz')
play_parser.add_argument('--speed', '-s', type=int, default=20,
help='Speed in WPM')
# Convert command
convert_parser = subparsers.add_parser('convert', help='Convert text to Morse code')
convert_parser.add_argument('text', help='Text to convert')
convert_parser.add_argument('--output', '-o', help='Output file for audio')
# Practice command
practice_parser = subparsers.add_parser('practice', help='Start practice session')
practice_parser.add_argument('--level', '-l', type=int, default=1,
help='Practice level (1-5)')
practice_parser.add_argument('--length', type=int, default=20,
help='Session length')
# Reference command
reference_parser = subparsers.add_parser('reference', help='Show Morse code reference')
args = parser.parse_args()
if args.command == 'play':
self.cli_play(args)
elif args.command == 'convert':
self.cli_convert(args)
elif args.command == 'practice':
self.cli_practice(args)
elif args.command == 'reference':
self.cli_reference()
else:
parser.print_help()
def cli_play(self, args):
"""CLI play command"""
self.morse_audio.frequency = args.frequency
self.morse_audio.set_speed_wpm(args.speed)
print(f"Playing: {args.text}")
print(f"Frequency: {args.frequency} Hz")
print(f"Speed: {args.speed} WPM")
self.morse_audio.play_morse_audio(args.text, blocking=True)
print("Playback complete.")
def cli_convert(self, args):
"""CLI convert command"""
print(f"Text: {args.text}")
# Convert to Morse code display
morse_display = []
for char in args.text.upper():
if char in self.morse_audio.morse_code:
morse = self.morse_audio.morse_code[char]
morse_display.append(f"{char}: {morse}")
print("Morse Code:")
for line in morse_display:
print(f" {line}")
# Save audio if requested
if args.output:
if self.morse_audio.save_to_wav(args.text, args.output):
print(f"Audio saved to: {args.output}")
else:
print("Error saving audio file")
def cli_practice(self, args):
"""CLI practice command"""
print(f"Starting practice session - Level {args.level}")
print(f"Session length: {args.length} characters")
print("Listen to the Morse code and type the character you hear.")
print("Press Ctrl+C to quit early.\n")
try:
results = self.learning_system.start_practice_session(args.level, args.length)
# Display results
print("\n" + "="*50)
print("PRACTICE SESSION RESULTS")
print("="*50)
print(f"Level: {results['level']}")
print(f"Accuracy: {results['accuracy']:.1f}%")
print(f"Average Response Time: {results['average_response_time']:.2f} seconds")
print(f"Words Per Minute: {results['words_per_minute']:.1f}")
print(f"Correct: {results['correct']}")
print(f"Incorrect: {results['incorrect']}")
if results.get('level_up'):
print(f"\n🎉 Congratulations! You advanced to Level {results['level'] + 1}!")
except KeyboardInterrupt:
print("\nPractice session interrupted.")
def cli_reference(self):
"""CLI reference command"""
print("INTERNATIONAL MORSE CODE REFERENCE")
print("="*50)
print("\nLETTERS:")
for char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
if char in self.morse_audio.morse_code:
print(f" {char}: {self.morse_audio.morse_code[char]}")
print("\nNUMBERS:")
for char in "0123456789":
if char in self.morse_audio.morse_code:
print(f" {char}: {self.morse_audio.morse_code[char]}")
print("\nPUNCTUATION:")
punctuation = [',', '.', '?', '/', '-', '(', ')']
for char in punctuation:
if char in self.morse_audio.morse_code:
print(f" {char}: {self.morse_audio.morse_code[char]}")
if __name__ == "__main__":
if len(sys.argv) > 1:
# Command line mode
cli = MorseCodeCLI()
cli.main()
else:
# GUI mode
run_gui()
Running the Application
Command Line Usage
# Play text as Morse code
python morseaudio.py play "Hello World" --frequency 800 --speed 25
# Convert text and save audio
python morseaudio.py convert "SOS" --output sos.wav
# Start practice session
python morseaudio.py practice --level 2 --length 30
# Show Morse code reference
python morseaudio.py reference
# Play text as Morse code
python morseaudio.py play "Hello World" --frequency 800 --speed 25
# Convert text and save audio
python morseaudio.py convert "SOS" --output sos.wav
# Start practice session
python morseaudio.py practice --level 2 --length 30
# Show Morse code reference
python morseaudio.py reference
GUI Usage
# Run the GUI interface
python morseaudio.py
# Run the GUI interface
python morseaudio.py
API Usage
# Create Morse audio instance
morse = MorseCodeAudio()
# Play text
morse.play_morse_audio("Hello World")
# Save to file
morse.save_to_wav("SOS", "emergency.wav")
# Start learning session
learning = MorseLearningSystem(morse)
results = learning.start_practice_session(level=2, session_length=20)
# Create Morse audio instance
morse = MorseCodeAudio()
# Play text
morse.play_morse_audio("Hello World")
# Save to file
morse.save_to_wav("SOS", "emergency.wav")
# Start learning session
learning = MorseLearningSystem(morse)
results = learning.start_practice_session(level=2, session_length=20)
Sample Output
CLI Output
=== Morse Code Audio Player ===
Playing: Hello World
Frequency: 600 Hz
Speed: 20 WPM
Morse Code:
H: ....
E: .
L: .-..
L: .-..
O: ---
W: .--
O: ---
R: .-.
L: .-..
D: -..
Playback complete.
=== Morse Code Audio Player ===
Playing: Hello World
Frequency: 600 Hz
Speed: 20 WPM
Morse Code:
H: ....
E: .
L: .-..
L: .-..
O: ---
W: .--
O: ---
R: .-.
L: .-..
D: -..
Playback complete.
Practice Session Results
PRACTICE SESSION RESULTS
==================================================
Level: 2
Accuracy: 87.5%
Average Response Time: 2.34 seconds
Words Per Minute: 12.5
Correct: 17
Incorrect: 3
Recent mistakes:
- M (--): You answered 'N'
- G (--): You answered 'M'
- K (-.-): You answered 'C'
PRACTICE SESSION RESULTS
==================================================
Level: 2
Accuracy: 87.5%
Average Response Time: 2.34 seconds
Words Per Minute: 12.5
Correct: 17
Incorrect: 3
Recent mistakes:
- M (--): You answered 'N'
- G (--): You answered 'M'
- K (-.-): You answered 'C'
Chat System Output
Morse Chat Server started on localhost:9999
Client connected from ('127.0.0.1', 54321)
[2024-01-15 14:30:15] You: Hello
[2024-01-15 14:30:18] Remote: .... . .-.. .-.. ---
[2024-01-15 14:30:25] Remote: Hi there
[2024-01-15 14:30:28] You: .... .. - .... . .-. .
Morse Chat Server started on localhost:9999
Client connected from ('127.0.0.1', 54321)
[2024-01-15 14:30:15] You: Hello
[2024-01-15 14:30:18] Remote: .... . .-.. .-.. ---
[2024-01-15 14:30:25] Remote: Hi there
[2024-01-15 14:30:28] You: .... .. - .... . .-. .
Advanced Features
1. Adaptive Learning Algorithm
def calculate_adaptive_difficulty(self, performance_history: List[dict]) -> int:
"""Calculate next difficulty level based on performance"""
if len(performance_history) < 5:
return self.current_level
recent_performance = performance_history[-10:] # Last 10 sessions
# Calculate metrics
avg_accuracy = sum(p['accuracy'] for p in recent_performance) / len(recent_performance)
avg_speed = sum(p['words_per_minute'] for p in recent_performance) / len(recent_performance)
consistency = 1 - (max(p['accuracy'] for p in recent_performance) -
min(p['accuracy'] for p in recent_performance)) / 100
# Adaptive algorithm
performance_score = (avg_accuracy * 0.5 + avg_speed * 0.3 + consistency * 0.2)
if performance_score > 85 and self.current_level < self.max_level:
return self.current_level + 1
elif performance_score < 60 and self.current_level > 1:
return self.current_level - 1
else:
return self.current_level
def calculate_adaptive_difficulty(self, performance_history: List[dict]) -> int:
"""Calculate next difficulty level based on performance"""
if len(performance_history) < 5:
return self.current_level
recent_performance = performance_history[-10:] # Last 10 sessions
# Calculate metrics
avg_accuracy = sum(p['accuracy'] for p in recent_performance) / len(recent_performance)
avg_speed = sum(p['words_per_minute'] for p in recent_performance) / len(recent_performance)
consistency = 1 - (max(p['accuracy'] for p in recent_performance) -
min(p['accuracy'] for p in recent_performance)) / 100
# Adaptive algorithm
performance_score = (avg_accuracy * 0.5 + avg_speed * 0.3 + consistency * 0.2)
if performance_score > 85 and self.current_level < self.max_level:
return self.current_level + 1
elif performance_score < 60 and self.current_level > 1:
return self.current_level - 1
else:
return self.current_level
2. Advanced Audio Processing
def apply_audio_effects(self, audio_data: np.ndarray, effect: str) -> np.ndarray:
"""Apply audio effects to Morse code signal"""
if effect == 'echo':
# Add echo effect
delay_samples = int(0.2 * self.sample_rate)
echo = np.zeros_like(audio_data)
echo[delay_samples:] = audio_data[:-delay_samples] * 0.3
return audio_data + echo
elif effect == 'filter':
# Apply bandpass filter
from scipy import signal
nyquist = self.sample_rate / 2
low = (self.frequency - 100) / nyquist
high = (self.frequency + 100) / nyquist
b, a = signal.butter(4, [low, high], btype='band')
return signal.filtfilt(b, a, audio_data)
elif effect == 'noise':
# Add background noise
noise = np.random.normal(0, 0.05, len(audio_data))
return audio_data + noise
return audio_data
def apply_audio_effects(self, audio_data: np.ndarray, effect: str) -> np.ndarray:
"""Apply audio effects to Morse code signal"""
if effect == 'echo':
# Add echo effect
delay_samples = int(0.2 * self.sample_rate)
echo = np.zeros_like(audio_data)
echo[delay_samples:] = audio_data[:-delay_samples] * 0.3
return audio_data + echo
elif effect == 'filter':
# Apply bandpass filter
from scipy import signal
nyquist = self.sample_rate / 2
low = (self.frequency - 100) / nyquist
high = (self.frequency + 100) / nyquist
b, a = signal.butter(4, [low, high], btype='band')
return signal.filtfilt(b, a, audio_data)
elif effect == 'noise':
# Add background noise
noise = np.random.normal(0, 0.05, len(audio_data))
return audio_data + noise
return audio_data
3. Performance Analytics
def generate_performance_report(self, user_id: str) -> dict:
"""Generate comprehensive performance analytics"""
user_sessions = self.get_user_sessions(user_id)
if not user_sessions:
return {'error': 'No session data available'}
# Calculate trends
accuracy_trend = [s['accuracy'] for s in user_sessions[-20:]]
speed_trend = [s['words_per_minute'] for s in user_sessions[-20:]]
# Character analysis
character_performance = {}
for session in user_sessions:
for result in session.get('session_results', []):
char = result['character']
if char not in character_performance:
character_performance[char] = {'correct': 0, 'total': 0}
character_performance[char]['total'] += 1
if result['correct']:
character_performance[char]['correct'] += 1
# Identify problem characters
problem_chars = []
for char, stats in character_performance.items():
accuracy = stats['correct'] / stats['total'] * 100
if accuracy < 70:
problem_chars.append((char, accuracy))
problem_chars.sort(key=lambda x: x[1]) # Sort by accuracy (worst first)
return {
'total_sessions': len(user_sessions),
'current_level': self.current_level,
'accuracy_trend': accuracy_trend,
'speed_trend': speed_trend,
'average_accuracy': sum(accuracy_trend) / len(accuracy_trend),
'average_speed': sum(speed_trend) / len(speed_trend),
'problem_characters': problem_chars[:5], # Top 5 problem characters
'character_performance': character_performance,
'improvement_suggestions': self.generate_suggestions(problem_chars)
}
def generate_performance_report(self, user_id: str) -> dict:
"""Generate comprehensive performance analytics"""
user_sessions = self.get_user_sessions(user_id)
if not user_sessions:
return {'error': 'No session data available'}
# Calculate trends
accuracy_trend = [s['accuracy'] for s in user_sessions[-20:]]
speed_trend = [s['words_per_minute'] for s in user_sessions[-20:]]
# Character analysis
character_performance = {}
for session in user_sessions:
for result in session.get('session_results', []):
char = result['character']
if char not in character_performance:
character_performance[char] = {'correct': 0, 'total': 0}
character_performance[char]['total'] += 1
if result['correct']:
character_performance[char]['correct'] += 1
# Identify problem characters
problem_chars = []
for char, stats in character_performance.items():
accuracy = stats['correct'] / stats['total'] * 100
if accuracy < 70:
problem_chars.append((char, accuracy))
problem_chars.sort(key=lambda x: x[1]) # Sort by accuracy (worst first)
return {
'total_sessions': len(user_sessions),
'current_level': self.current_level,
'accuracy_trend': accuracy_trend,
'speed_trend': speed_trend,
'average_accuracy': sum(accuracy_trend) / len(accuracy_trend),
'average_speed': sum(speed_trend) / len(speed_trend),
'problem_characters': problem_chars[:5], # Top 5 problem characters
'character_performance': character_performance,
'improvement_suggestions': self.generate_suggestions(problem_chars)
}
Troubleshooting
Common Issues
1. Audio Device Problems
def test_audio_devices(self):
"""Test and list available audio devices"""
print("Available audio devices:")
for i in range(self.audio.get_device_count()):
info = self.audio.get_device_info_by_index(i)
print(f"Device {i}: {info['name']} - {info['maxOutputChannels']} channels")
# Test default device
try:
stream = self.audio.open(format=pyaudio.paInt16, channels=1,
rate=44100, output=True)
stream.close()
print("Default audio device is working")
return True
except Exception as e:
print(f"Audio device error: {e}")
return False
def test_audio_devices(self):
"""Test and list available audio devices"""
print("Available audio devices:")
for i in range(self.audio.get_device_count()):
info = self.audio.get_device_info_by_index(i)
print(f"Device {i}: {info['name']} - {info['maxOutputChannels']} channels")
# Test default device
try:
stream = self.audio.open(format=pyaudio.paInt16, channels=1,
rate=44100, output=True)
stream.close()
print("Default audio device is working")
return True
except Exception as e:
print(f"Audio device error: {e}")
return False
2. Performance Issues
def optimize_performance(self):
"""Optimize audio performance for real-time playback"""
# Reduce latency
self.chunk_size = 512 # Smaller chunks for lower latency
# Use appropriate audio format
self.audio_format = pyaudio.paInt16 # 16-bit for efficiency
# Optimize thread priority
if hasattr(os, 'nice'):
os.nice(-5) # Higher priority for audio thread
def optimize_performance(self):
"""Optimize audio performance for real-time playback"""
# Reduce latency
self.chunk_size = 512 # Smaller chunks for lower latency
# Use appropriate audio format
self.audio_format = pyaudio.paInt16 # 16-bit for efficiency
# Optimize thread priority
if hasattr(os, 'nice'):
os.nice(-5) # Higher priority for audio thread
3. Memory Management
def cleanup_audio_resources(self):
"""Clean up audio resources"""
if hasattr(self, 'audio') and self.audio:
self.audio.terminate()
# Clear large audio arrays
if hasattr(self, 'audio_buffer'):
del self.audio_buffer
import gc
gc.collect() # Force garbage collection
def cleanup_audio_resources(self):
"""Clean up audio resources"""
if hasattr(self, 'audio') and self.audio:
self.audio.terminate()
# Clear large audio arrays
if hasattr(self, 'audio_buffer'):
del self.audio_buffer
import gc
gc.collect() # Force garbage collection
Next Steps
After mastering this Morse code system, consider:
- SDR Integration: Software Defined Radio for real radio communication
- CW Keyer: Hardware interface for traditional Morse key
- Contest Logging: Integration with amateur radio contest software
- Mobile Apps: iOS/Android versions for portable learning
- Web Interface: Browser-based Morse code trainer
Resources
- International Morse Code Standard
- Amateur Radio Morse Code
- PyAudio Documentation
- NumPy Audio Processing
- Morse Code Learning Resources
Conclusion
This Morse code audio player and trainer provides a comprehensive platform for learning and practicing Morse code. It demonstrates advanced audio processing, real-time communication, adaptive learning algorithms, and professional software development practices.
The system combines education, entertainment, and practical communication tools to create a complete Morse code learning environment. The modular design allows for easy extension and integration with other amateur radio applications. 📻🐍
Was this page helpful?
Let us know how we did