PySimpleGUI

モールス信号発生器をつくる

PySimpleGUI

詳しい説明は後程・・・

 

import numpy as np
import pyaudio
import time
import PySimpleGUI as sg
import threading

モールス信号辞書

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': '--..',
'0': '-----', '1': '.----', '2': '..---', '3': '…--', '4': '….-',
'5': '…..', '6': '-….', '7': '--…', '8': '---..', '9': '----.',
'?': '..--..', ',': '--..--', '.': '.-.-.-', '/': '-..-.', '@': '.--.-.',
'-': '-….-', '(': '-.--.', ')': '-.--.-'
}

PyAudioの初期化

p = pyaudio.PyAudio()

再生設定

VOLUME = 0.5 # 音量
SAMPLE_RATE = 44100 # サンプリングレート
playback_stopped = False # 再生停止フラグ

クリック音を減少させるエンベロープ

def apply_envelope(signal, fade_in_duration=0.01, fade_out_duration=0.01):
fade_in_samples = int(fade_in_duration * SAMPLE_RATE)
fade_out_samples = int(fade_out_duration * SAMPLE_RATE)
envelope = np.ones_like(signal)
envelope[:fade_in_samples] = np.linspace(0, 1, fade_in_samples)
envelope[-fade_out_samples:] = np.linspace(1, 0, fade_out_samples)
return signal * envelope

正弦波トーンの生成

def generate_tone(duration, frequency):
t = np.linspace(0, duration, int(SAMPLE_RATE * duration), False)
signal = np.sin(2 * np.pi * frequency * t)
signal = apply_envelope(signal)
return signal

音を再生

def play_tone(tone):
samples = (tone * VOLUME * np.iinfo(np.int16).max).astype(np.int16)
stream = p.open(format=pyaudio.paInt16,
channels=1,
rate=SAMPLE_RATE,
output=True)
stream.write(samples.tobytes())
stream.stop_stream()
stream.close()

ドットとダッシュの再生

def play_dot(frequency, dot_duration):
play_tone(generate_tone(dot_duration, frequency))

def play_dash(frequency, dot_duration):
play_tone(generate_tone(10 * dot_duration, frequency))

モールス信号の再生

def play_morse_code(input_text, wpm, frequency, window):
global playback_stopped
playback_stopped = False

# ドットの持続時間を計算(50単位=1単語を基準)
dot_duration = 60 / (50 * wpm)  # WPMに基づく1つのドットの持続時間

# 文字間の間隔と単語間の間隔
char_gap = 3 * dot_duration  # 文字間の間隔
word_gap = 7 * dot_duration  # 単語間の間隔

sent_text = ''  # 送信済みの文字列

# 各文字をモールス信号に変換して発声
for i, char in enumerate(input_text.upper()):
    if playback_stopped:
        break
    if char in MORSE_CODE_DICT:
        morse_code = MORSE_CODE_DICT[char]
        for symbol in morse_code:
            if playback_stopped:
                break
            if symbol == '.':
                play_dot(frequency, dot_duration)
            elif symbol == '-':
                play_dash(frequency, dot_duration)
            time.sleep(dot_duration)  # ドット間の間隔
        time.sleep(char_gap)  # 文字間の間隔
    elif char == ' ':
        time.sleep(word_gap)  # 単語間の間隔

    # 送信済みのテキストを更新
    sent_text += char
    window['SENT_OUTPUT'].update(value=sent_text)
    window.refresh()

再生スレッドの開始

def start_playback(text, wpm, frequency, window):
global playback_stopped
playback_stopped = False
play_morse_code(text, wpm, frequency, window)

再生の停止

def stop_playback():
global playback_stopped
playback_stopped = True

GUIレイアウト

sg.theme('NeutralBlue')
layout = [
[sg.Text('文字列を入力してください:')],
[sg.Input(key='INPUT', size=(50, 1))],
[sg.Text('送信済み:'), sg.Input('', size=(45, 1), key='SENT_OUTPUT')],
#[sg.Text('送信済み:', size=(40, 1)), sg.Text('', size=(40, 1), key='SENT_OUTPUT')],
[sg.Text('速度(Word/m):'), sg.Slider(range=(10, 30), default_value=20, orientation='h', key='WPM')],
[sg.Text('周波数(Hz):'), sg.Slider(range=(300, 1000), default_value=800, orientation='h', key='FREQ')],
[sg.Button('発声'), sg.Button('stop'), sg.Button('クリアー'), sg.Button('終了')],
[sg.Text('JA1FML ver 1.0', justification='right', size=(40, 1))]
]

ウィンドウを作成

window = sg.Window('モールス信号発生器', layout, size=(400, 300))

イベントループ

while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == '終了':
break
elif event == '発声':
text = values['INPUT']
wpm = values['WPM']
frequency = values['FREQ']
window['SENT_OUTPUT'].update('') # 送信済み表示をクリア
threading.Thread(target=start_playback, args=(text, wpm, frequency, window), daemon=True).start()
elif event == 'stop':
stop_playback() # 再生の停止
elif event == 'クリアー':
window['INPUT'].update('')
window['SENT_OUTPUT'].update('')

ウィンドウを閉じる

window.close()
p.terminate()

 

PAGE TOP