File size: 4,376 Bytes
c8a4052
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import gradio as gr
import numpy as np
import time
from scipy.io import wavfile
import io

# Morse code dictionary (ITU standard)
MORSE_CODE_DICT = {
    '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': '-----', ' ': ' '
}

# Reverse dictionary for decoding
MORSE_TO_CHAR = {v: k for k, v in MORSE_CODE_DICT.items()}

# Function to process audio and detect Morse code (simplified)
def decode_morse_from_audio(audio_data):
    if audio_data is None:
        return "", ""
    
    # Audio comes as (sample_rate, data) from Gradio
    sample_rate, data = audio_data
    if len(data.shape) > 1:  # Convert stereo to mono if needed
        data = data.mean(axis=1)
    
    # Normalize audio data
    data = data / np.max(np.abs(data))
    
    # Threshold for detecting signal (tweak as needed)
    threshold = 0.1
    signal = data > threshold
    
    # Timing parameters (in samples, adjust based on sample_rate)
    dit_length = int(sample_rate * 0.1)  # 100ms for a dit
    dah_length = dit_length * 3         # 300ms for a dah
    space_length = dit_length * 7       # 700ms for word space
    
    morse_code = ""
    decoded_text = ""
    i = 0
    
    while i < len(signal) - dit_length:
        if signal[i]:
            # Measure signal duration
            start = i
            while i < len(signal) and signal[i]:
                i += 1
            duration = i - start
            
            # Classify as dit or dah
            if duration >= dah_length:
                morse_code += "-"
            elif duration >= dit_length:
                morse_code += "."
        
        # Check for spaces (pauses)
        else:
            start = i
            while i < len(signal) and not signal[i]:
                i += 1
            pause = i - start
            
            if pause >= space_length:
                if morse_code:
                    decoded_text += MORSE_TO_CHAR.get(morse_code, "?")
                decoded_text += " "
                morse_code = ""
            elif pause >= dit_length and morse_code:
                decoded_text += MORSE_TO_CHAR.get(morse_code, "?")
                morse_code = ""
        
        i += 1
    
    # Handle any remaining code
    if morse_code:
        decoded_text += MORSE_TO_CHAR.get(morse_code, "?")
    
    return morse_code, decoded_text.strip()

# Function to generate highlighted alphabet UI
def generate_alphabet_html(decoded_text):
    html = "<div style='font-family: monospace; font-size: 16px;'>"
    for char in MORSE_CODE_DICT.keys():
        color = "red" if char in decoded_text.upper() else "black"
        html += f"<span style='color: {color}; margin: 5px;'>{char}: {MORSE_CODE_DICT[char]}</span>"
        if char in "AEIMQUZ":
            html += "<br>"
    html += "</div>"
    return html

# Gradio streaming function
def stream_morse_decoder(audio):
    morse, text = decode_morse_from_audio(audio)
    alphabet_html = generate_alphabet_html(text)
    return morse, text, alphabet_html

# Gradio UI with Blocks
with gr.Blocks(title="Morse Code Decoder") as demo:
    gr.Markdown("# Morse Code Decoder")
    gr.Markdown("Speak or play Morse code into your microphone to decode it live!")
    
    with gr.Row():
        audio_input = gr.Audio(source="microphone", type="numpy", streaming=True, label="Live Audio Input")
    
    with gr.Row():
        with gr.Column():
            morse_output = gr.Textbox(label="Detected Morse Code", interactive=False)
            text_output = gr.Textbox(label="Decoded Text", interactive=False)
        with gr.Column():
            alphabet_display = gr.HTML(label="Morse Alphabet (Highlighted)")
    
    # Event listener for streaming audio
    audio_input.stream(
        fn=stream_morse_decoder,
        inputs=[audio_input],
        outputs=[morse_output, text_output, alphabet_display],
        _js="() => { return [navigator.mediaDevices.getUserMedia({ audio: true })];"
    )

# Launch the app
demo.launch()